NAGA & PIOWIND 2014 APP应用攻防竞赛第二阶段第一题题解
Java层用于传字符串,输入用户名和密码到Native层校验
protected void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(2130903040);
this.txt_name = this.findViewById(2131165184);
this.txt_passwd = this.findViewById(2131165185);
this.btn_login = this.findViewById(2131165186);
this.btn_reset = this.findViewById(2131165187);
this.txt_result = this.findViewById(2131165188);
this.btn_login.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg7) {
String v0 = MainActivity.this.txt_name.getText().toString();
String v1 = MainActivity.this.txt_passwd.getText().toString();
if("".equals(v0)) {
System.out.println("name is null or \'\'");
MainActivity.this.txt_result.setText("账户为空");
}
else if("".equals(v1)) {
System.out.println("passwd is null or \'\'");
MainActivity.this.txt_result.setText("密码为空");
}
else {
System.out.println("name:" + v0);
System.out.println("passwd:" + v1);
System.out.println("Please treat me gently, you have to go a long way.");
MainActivity.this.txt_result.setText(MainActivity.this.crackme(v0, v1));
}
}
});
this.btn_reset.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg3) {
MainActivity.this.txt_name.setText("");
MainActivity.this.txt_passwd.setText("");
MainActivity.this.txt_result.setText("");
}
});
}
使用IDA查看so,发现加密了
动态调试把解密后的so文件dump出来
先查看加载的内存基址
dump脚本如下,地址需要根据自己的调试环境确定
auto fp, dex_addr, end_addr;
fp = fopen("E:\\libcrackme.so", "wb");
for(dex_addr = 0xA35C8000; dex_addr < 0xA3609000; dex_addr++)
fputc(Byte(dex_addr), fp);
然后再打开,可以看到代码已经还原了
先传入用户名和密码,然后转为char *
类型的字符串,,接着调用两个函数sub_536C()
和sub_597C()
LOAD:00005B50 ; jstring __fastcall Java_com_crackme_MainActivity_crackme(JNIEnv *env, int a2, jstring a3, jstring a4)
LOAD:00005B50 EXPORT Java_com_crackme_MainActivity_crackme
LOAD:00005B50 Java_com_crackme_MainActivity_crackme
LOAD:00005B50 F8 B5 PUSH {R3-R7,LR}
LOAD:00005B52 ; 7: v4 = a4;
LOAD:00005B52 1E 1C MOVS R6, R3 ; R6 = R3 = password
LOAD:00005B54 ; 9: v6 = (*env)->GetStringUTFChars(env, a3, 0);
LOAD:00005B54 03 68 LDR R3, [R0] ; R3 = [R0] = *env
LOAD:00005B56 A9 25 AD 00 MOVS R5, #0x2A4 ; R5 = 0x2A4
LOAD:00005B5A 5B 59 LDR R3, [R3,R5] ; R3 = GetStringUTFChars()
LOAD:00005B5C 11 1C MOVS R1, R2 ; R1 = R2 = username
LOAD:00005B5E ; 8: vEnv = env;
LOAD:00005B5E 00 22 MOVS R2, #0 ; R2 = 0
LOAD:00005B60 04 1C MOVS R4, R0 ; R4 = R0 = env
LOAD:00005B62 98 47 BLX R3 ; R0 = (*env)->GetStringUTFChars(env, username, 0)
LOAD:00005B64 23 68 LDR R3, [R4] ; R3 = [R4] = *env
LOAD:00005B66 07 1C MOVS R7, R0 ; R7 = R0 = szUserName
LOAD:00005B68 ; 10: v7 = (*vEnv)->GetStringUTFChars(vEnv, v4, 0);
LOAD:00005B68 31 1C MOVS R1, R6 ; R1 = R6 = Password
LOAD:00005B6A 5B 59 LDR R3, [R3,R5] ; R3 = GetStringUTFChars()
LOAD:00005B6C 20 1C MOVS R0, R4 ; R0 = R4 = env
LOAD:00005B6E 00 22 MOVS R2, #0 ; R2 = 0
LOAD:00005B70 98 47 BLX R3 ; R0 = (*env)->GetStringUTFChars(env, password, 0)
LOAD:00005B72 ; 11: sub_536C("Failure", v6, v7);
LOAD:00005B72 09 4D LDR R5, =(dword_15220 - 0x5B7C) ; R5 = pFailure - 0x5B7C
LOAD:00005B74 39 1C MOVS R1, R7 ; R1 = szUserName
LOAD:00005B76 02 1C MOVS R2, R0 ; R2 = R0 = szPassword
LOAD:00005B78 7D 44 ADD R5, PC ; dword_15220
LOAD:00005B7A 04 35 ADDS R5, #4 ; R5 = pFailure
LOAD:00005B7C 28 1C MOVS R0, R5 ; R0 = R5 = pFailure
LOAD:00005B7E FF F7 F5 FB BL sub_536C ; R0 = sub_536C("Failure", szUserName, szPassword)
LOAD:00005B82 ; 12: sub_597C((int)"Failure");
LOAD:00005B82 28 1C MOVS R0, R5 ; R0 = R5 = pFailure
LOAD:00005B84 FF F7 FA FE BL sub_597C ; R0 = sub_597C(pFailure)
LOAD:00005B88 ; 13: return (*vEnv)->NewStringUTF(vEnv, "Failure");
LOAD:00005B88 22 68 LDR R2, [R4] ; R2 = [R4] = *env
LOAD:00005B8A A7 23 9B 00 MOVS R3, #0x29C ; R3 = 0x29C
LOAD:00005B8E 29 1C MOVS R1, R5 ; R1 = R5 = pFailure
LOAD:00005B90 D3 58 LDR R3, [R2,R3] ; R3 = NewStringUTF()
LOAD:00005B92 20 1C MOVS R0, R4 ; R0 = R4 = env
LOAD:00005B94 98 47 BLX R3 ; R0 = (*env)->NewStringUTF(env, "Failure")
LOAD:00005B96 F8 BD POP {R3-R7,PC}
LOAD:00005B96 ; End of function Java_com_crackme_MainActivity_crackm
跟入sub_536C("Failure", szUserName, szPassword)
这个函数比较简单
先传进来一个字符串指针,这个指针非常重要,后续的栈变量要使用这个字符串指针作为基址来寻找
LOAD:0000536C ; const JNINativeInterface *__fastcall malloc_Heap(int a1, const char *a2, const char *a3)
LOAD:0000536C malloc_Heap
LOAD:0000536C
LOAD:0000536C n= -0x24
LOAD:0000536C len_UserName= -0x20
LOAD:0000536C len_Password= -0x1C
LOAD:0000536C
LOAD:0000536C F0 B5 PUSH {R4-R7,LR}
LOAD:0000536E 85 B0 SUB SP, SP, #0x14 ; 抬高栈顶
LOAD:00005370 ; 15: v3 = a3;
LOAD:00005370 16 1C MOVS R6, R2 ; R6 = R2 = szPassword
LOAD:00005372 ; 16: v4 = a1;
LOAD:00005372 04 1C MOVS R4, R0 ; R4 = R0 = "Failure"
LOAD:00005374 ; 17: v5 = a2;
LOAD:00005374 0D 1C MOVS R5, R1 ; R5 = R1 = szUserName
LOAD:00005376 ; 18: result = (const JNINativeInterface *)sub_5328(a1);
LOAD:00005376 FF F7 D7 FF BL sub_5328 ; R0 = sub_5328(pFailure)
函数sub_5328()
用于初始化某些栈空间
然后有两处判断,判断传入的两个字符串是否为空
判断密码是否为空
LOAD:0000537A ; 19: if ( v3 )
LOAD:0000537A 00 2E CMP R6, #0 ; if(szPassword == 0)
LOAD:0000537C 2F D0 BEQ loc_53DE
判断用户名是否为空
LOAD:0000537E ; 21: if ( v5 )
LOAD:0000537E 00 2D CMP R5, #0 ; if(szUserName == 0)
LOAD:00005380 2D D0 BEQ loc_53DE
申请空间
LOAD:00005382 ; 23: v7 = strlen(v5);
LOAD:00005382 28 1C MOVS R0, R5 ; s
LOAD:00005384 FF F7 1C EF BLX strlen ; R0 = strlen(szUserName)
LOAD:00005388 ; 24: len_UserName = v7;
LOAD:00005388 02 90 STR R0, [SP,#0x28+len_UserName] ; len_UserName = strlen(szUserName)
LOAD:0000538A ; 25: v8 = v7;
LOAD:0000538A 07 1C MOVS R7, R0 ; R7 = R0 = len_UserName
LOAD:0000538C ; 26: v9 = strlen(v3);
LOAD:0000538C 30 1C MOVS R0, R6 ; s
LOAD:0000538E FF F7 18 EF BLX strlen ; R0 = strlen(szPassword)
LOAD:00005392 ; 27: v10 = v8 + 1;
LOAD:00005392 01 37 ADDS R7, #1 ; R7 = len_UserName + 1
LOAD:00005394 ; 28: v14 = v9;
LOAD:00005394 03 1C MOVS R3, R0 ; R3 = R0 = len_Password
LOAD:00005396 01 33 ADDS R3, #1 ; R3 = len_Password + 1
LOAD:00005398 03 90 STR R0, [SP,#0x28+len_Password] ; len_Password = R0
LOAD:00005398 ; R0 = len_UserName + 1
LOAD:0000539A ; 30: *(_DWORD *)(v4 + 52) = operator new[](v10);
LOAD:0000539A 38 1C MOVS R0, R7 ; unsigned int
LOAD:0000539C ; 29: n = v9 + 1;
LOAD:0000539C 01 93 STR R3, [SP,#0x28+n] ; n = len_Password + 1
LOAD:0000539E FF F7 16 EF BLX _Znaj ; operator new[](len_UserName + 1) // 申请空间
LOAD:000053A2 60 63 STR R0, [R4,#0x34] ; R0为新UserName存储堆地址
LOAD:000053A4 ; 31: result = (const JNINativeInterface *)operator new[](n);
LOAD:000053A4 01 98 LDR R0, [SP,#0x28+n] ; unsigned int
LOAD:000053A6 FF F7 12 EF BLX _Znaj ; operator new[](uint)
LOAD:000053AA ; 32: v11 = *(void **)(v4 + 52);
LOAD:000053AA 63 6B LDR R3, [R4,#0x34] ; R3 = pUserName
LOAD:000053AC ; 33: *(_DWORD *)(v4 + 56) = result;
LOAD:000053AC A0 63 STR R0, [R4,#0x38] ; R0为新Password存储堆地址
通过返回的内存分配地址来判断是否申请成功
LOAD:000053AE ; 34: if ( v11 )
LOAD:000053AE 00 2B CMP R3, #0 ; 判断UserName内存空间是否申请成功
LOAD:000053B0 15 D0 BEQ loc_53DE
第二处判断
LOAD:000053B2 ; 36: if ( result )
LOAD:000053B2 00 28 CMP R0, #0 ; 判断Password内存空间是否申请成功
LOAD:000053B4 13 D0 BEQ loc_53DE
接下来进行拷贝操作,存储用户名和密码,需要注意到新申请的两个变量的寻址方式为[pFailure + offset]
LOAD:000053B6 ; 38: memset(v11, 0, v10);
LOAD:000053B6 18 1C MOVS R0, R3 ; s
LOAD:000053B8 00 21 MOVS R1, #0 ; c
LOAD:000053BA 3A 1C MOVS R2, R7 ; n
LOAD:000053BC FF F7 FA EE BLX memset ; memset(pUserName, 0, len_UserName + 1) //初始化UserName内存空间
LOAD:000053C0 ; 39: memset(*(void **)(v4 + 56), 0, n);
LOAD:000053C0 00 21 MOVS R1, #0 ; c
LOAD:000053C2 01 9A LDR R2, [SP,#0x28+n] ; n
LOAD:000053C4 A0 6B LDR R0, [R4,#0x38] ; s
LOAD:000053C6 FF F7 F6 EE BLX memset ; memset(pPassword, 0, len_Password + 1) //初始化Password内存空间
LOAD:000053CA ; 40: memcpy(*(void **)(v4 + 52), v5, len_UserName);
LOAD:000053CA 29 1C MOVS R1, R5 ; src
LOAD:000053CC 02 9A LDR R2, [SP,#0x28+len_UserName] ; n
LOAD:000053CE 60 6B LDR R0, [R4,#0x34] ; dest
LOAD:000053D0 FF F7 02 EF BLX memcpy ; memcpy(pUserName, szUserName, len_UserName) //拷贝数据到内存空间
LOAD:000053D4 ; 41: result = (const JNINativeInterface *)memcpy(*(void **)(v4 + 56), v3, v14);
LOAD:000053D4 A0 6B LDR R0, [R4,#0x38] ; dest
LOAD:000053D6 31 1C MOVS R1, R6 ; src
LOAD:000053D8 03 9A LDR R2, [SP,#0x28+len_Password] ; n
LOAD:000053DA FF F7 FE EE BLX memcpy ; memcpy(pPassword, szPassword, len_Password) //拷贝数据到内存空间
此时两个关键的变量在栈中的位置
pUserName = [pFailure + 0x34]
pPassword = [pFailure + 0x38]
初始化完栈空间以及相应的内存空间后,进入校验逻辑
LOAD:00005B82 28 1C MOVS R0, R5 ; R0 = R5 = pFailure
LOAD:00005B84 FF F7 FA FE BL sub_597C ; sub_597C(pFailure)
传入"Failure"
字符串的指针,该函数稍微有点长
存储"pFailure"
后调用函数sub_53E4()
LOAD:0000597C sub_597C
LOAD:0000597C
LOAD:0000597C var_34= -0x34
LOAD:0000597C var_30= -0x30
LOAD:0000597C var_28= -0x28
LOAD:0000597C var_24= -0x24
LOAD:0000597C var_1C= -0x1C
LOAD:0000597C
LOAD:0000597C F0 B5 PUSH {R4-R7,LR}
LOAD:0000597E 89 B0 SUB SP, SP, #0x24
LOAD:00005980 05 1C MOVS R5, R0 ; R5 = R0 = pFailure = "Failure"
LOAD:00005982 FF F7 2F FD BL sub_53E4
sub_53E4()
主要是校验用户名和密码的长度合法性
从中我们得出用户名和密码的长度范围
用户名:[6, 20]
密码:[12, 30]
校验密码的合法性,格式为xxx-xxx-xxx-xxx
调用sub_5430()
LOAD:000059B0 loc_59B0
LOAD:000059B0 28 1C MOVS R0, R5
LOAD:000059B2 FF F7 3D FD BL sub_5430
这个函数的作用是将密码中的-
去掉
获取一个Table,此Table一开始是空的
LOAD:000059BA 7B 44 ADD R3, PC ; Base64Table
LOAD:000059BC 1A 78 LDRB R2, [R3]
LOAD:000059BE 00 2A CMP R2, #0
LOAD:000059C0 31 D1 BNE loc_5A26
全部都是00
动态运行时会填充数据,第一次运行时会进行Table的生成,通过对这个Table第一个字节的判断,如果是00
,表示未生成,如果是01
,表示Table已生成,则跳过初始化Table的代码段
动态运行时进行初始化
接下来逐步进行计算,将Table的[2, 256]字节赋值为0x80
LOAD:000059C2 80 20 MOVS R0, #0x80 ; '?' ; R0 = 0x80
LOAD:000059C4 01 33 ADDS R3, #1 ; 从Table的第二位开始赋值
LOAD:000059C6 41 00 LSLS R1, R0, #1 ; R1 = 0x80 * 2 = 256
开始循环赋值
LOAD:000059C8 ; 41: byte_15121[v4++] = -128;
LOAD:000059C8
LOAD:000059C8 loc_59C8 ; Table = 0x80
LOAD:000059C8 D0 54 STRB R0, [R2,R3]
LOAD:000059CA ; 42: while ( v4 != 256 );
LOAD:000059CA 01 32 ADDS R2, #1 ; R2++
LOAD:000059CC 8A 42 CMP R2, R1
LOAD:000059CE FB D1 BNE loc_59C8 ; Table = 0x80
赋值完成后开始处理Table,初始化一些值
LOAD:000059D0 ; 43: v5 = 0;
LOAD:000059D0 5B 4A LDR R2, =(Base64Table - 0x59D8)
LOAD:000059D2 00 23 MOVS R3, #0 ; R3 = 0
LOAD:000059D4 7A 44 ADD R2, PC ; Base64Table
LOAD:000059D6 51 1C ADDS R1, R2, #1 ; R1 = Base64Tabl + 1
从Table偏移65
的位置开始赋值0
,长度为26
,整个表应该是偏移第67
位,因为第一个字节跳过,下标从0
开始
LOAD:000059D8 ; 46: byte_15121[v5 + 65] = v5;
LOAD:000059D8
LOAD:000059D8 loc_59D8 ;
LOAD:000059D8 C8 18 ADDS R0, R1, R3 ; R0 = Base64Table + i
LOAD:000059DA 41 30 ADDS R0, #65 ; R0 = Base64Table + i + 65
LOAD:000059DC 03 70 STRB R3, [R0] ; Base64Table[i + 65] = R3
LOAD:000059DE ; 47: ++v5;
LOAD:000059DE 01 33 ADDS R3, #1 ; R3++
LOAD:000059E0 ; 49: while ( v5 != 26 );
LOAD:000059E0 1A 2B CMP R3, #26
LOAD:000059E2 F9 D1 BNE loc_59D8 ;
LOAD:000059E2 ; R0 = Base64Table + i
取第98
位
LOAD:000059E4 ; 50: v6 = &byte_15182;
LOAD:000059E4 62 32 ADDS R2, #98 ; R2 = Base64Table[98]
开始赋值,赋值的数据跟着上面的R3
后面继续,上面赋值到0x19
,这里从0x1A
开始
LOAD:000059E6 ; 53: *v6 = v5;
LOAD:000059E6
LOAD:000059E6 loc_59E6
LOAD:000059E6 13 70 STRB R3, [R2]
LOAD:000059E8 ; 54: v5 = (v5 + 1) & 0xFF;
LOAD:000059E8 01 33 ADDS R3, #1 ; R3++
LOAD:000059EA 1B 06 LSLS R3, R3, #0x18
LOAD:000059EC 1B 0E LSRS R3, R3, #0x18 ; R3 = R3 & 0xFF
LOAD:000059EE ; 55: ++v6;
LOAD:000059EE 01 32 ADDS R2, #1 ; R2 = Base64Table + i
LOAD:000059F0 ; 57: while ( v5 != 52 );
LOAD:000059F0 34 2B CMP R3, #52
LOAD:000059F2 F8 D1 BNE loc_59E6
再次定位到49
的位置
LOAD:000059F4 ; 58: v7 = &byte_15151;
LOAD:000059F4 53 4A LDR R2, =(Base64Table - 0x59FA)
LOAD:000059F6 7A 44 ADD R2, PC ; Base64Table
LOAD:000059F8 31 32 ADDS R2, #49
再次赋值
LOAD:000059FA ; 61: *v7 = v5;
LOAD:000059FA
LOAD:000059FA loc_59FA
LOAD:000059FA 13 70 STRB R3, [R2]
LOAD:000059FC ; 62: v5 = (v5 + 1) & 0xFF;
LOAD:000059FC 01 33 ADDS R3, #1
LOAD:000059FE 1B 06 LSLS R3, R3, #0x18
LOAD:00005A00 1B 0E LSRS R3, R3, #0x18
LOAD:00005A02 ; 63: ++v7;
LOAD:00005A02 01 32 ADDS R2, #1
LOAD:00005A04 ; 65: while ( v5 != 62 );
LOAD:00005A04 3E 2B CMP R3, #62
LOAD:00005A06 F8 D1 BNE loc_59FA
最后处理几个单个的位置
LOAD:00005A08 ; 66: byte_1514C = 62;
LOAD:00005A08 4F 4A LDR R2, =(Base64Table - 0x5A0E)
LOAD:00005A0A 7A 44 ADD R2, PC ; Base64Table
LOAD:00005A0C 11 1C MOVS R1, R2 ; R1 = R2 = Base64Table
LOAD:00005A0E 2C 31 ADDS R1, #44 ; R1 = R1 + 44
LOAD:00005A10 0B 70 STRB R3, [R1] ; Base64Table[44] = 0x3E
LOAD:00005A12 ; 67: byte_15150 = 63;
LOAD:00005A12 13 1C MOVS R3, R2 ; R3 = R2 = Base64Table
LOAD:00005A14 30 33 ADDS R3, #48 ; R3 = R3 + 48
LOAD:00005A16 3F 21 MOVS R1, #63 ; R1 = 63
LOAD:00005A18 19 70 STRB R1, [R3] ; Base64Table[48] = 63
LOAD:00005A1A ; 68: byte_1515E = 0;
LOAD:00005A1A 13 1C MOVS R3, R2 ; R3 = R2 = Base64Table
LOAD:00005A1C 3E 33 ADDS R3, #62 ; R3 = R3 + 62
LOAD:00005A1E 00 21 MOVS R1, #0 ; R1 = 0
LOAD:00005A20 19 70 STRB R1, [R3] ; Base64Table[62] = 0
LOAD:00005A22 ; 69: Base64Table = 1;
LOAD:00005A22 01 23 MOVS R3, #1 ; R3 = 1
LOAD:00005A24 13 70 STRB R3, [R2] ; Base64Table[0] = 1 //设置已初始化Table标志
整个表处理完是下面这样的,因为最开始是判断是否初始化的标志,所以整个表长度为257,由于多次调试,所以下面的内存地址和上面图中可能不一样
A35DD120 01 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 .???????????????
A35DD130 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD140 80 80 80 80 80 80 80 80 80 80 80 80 3E 80 80 80 ????????????>???
A35DD150 3F 34 35 36 37 38 39 3A 3B 3C 3D 80 80 80 00 80 ?456789:;<=???.?
A35DD160 80 80 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D ??..............
A35DD170 0E 0F 10 11 12 13 14 15 16 17 18 19 80 80 80 80 ............????
A35DD180 80 80 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 ??...... !"#$%&'
A35DD190 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 80 80 80 80 ()*+,-./0123????
A35DD1A0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD1B0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD1C0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD1D0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD1E0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD1F0 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD200 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD210 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????????????
A35DD220 80 ?
判断处理后的密码是否为空,前面去除了密码中的-
LOAD:00005A26 ; 71: if ( v3 )
LOAD:00005A26
LOAD:00005A26 loc_5A26 ; R4为密码寄存器,判断是否为0
LOAD:00005A26 00 2C CMP R4, #0
LOAD:00005A28 07 D0 BEQ loc_5A3A
再申请一个存储密码的内存空间
LOAD:00005A2A ; 73: v8 = strlen(v3);
LOAD:00005A2A 20 1C MOVS R0, R4 ; s
LOAD:00005A2C FF F7 C8 EB BLX strlen ; R0 = strlen(pPassword)
LOAD:00005A30 ; 74: v9 = (const void *)operator new[](v8 + 1);
LOAD:00005A30 01 30 ADDS R0, #1 ; unsigned int
LOAD:00005A32 FF F7 CC EB BLX _Znaj ; R0 = operator new[](len_Password + 1)
LOAD:00005A36 06 1C MOVS R6, R0 ; R6 = R0 = new_pPassword
LOAD:00005A38 00 E0 B loc_5A3C
这里其实可以猜出来是Base64,因为判断3位长度,这个比较看经验了
LOAD:00005A3C ; 80: v10 = strlen(v3);
LOAD:00005A3C
LOAD:00005A3C loc_5A3C ; s
LOAD:00005A3C 20 1C MOVS R0, R4
LOAD:00005A3E FF F7 C0 EB BLX strlen ; R0 = R4 = pPassword
LOAD:00005A3E ; R0 = strlen(pPassword)
LOAD:00005A42 ; 81: v11 = 0;
LOAD:00005A42 42 49 LDR R1, =(Base64Table - 0x5A4C)
LOAD:00005A44 03 38 SUBS R0, #3 ; R0 = R0 - 3
LOAD:00005A46 00 23 MOVS R3, #0 ; R3 = 0
LOAD:00005A48 ; 85: while ( v11 < (signed int)(v10 - 3) )
LOAD:00005A48 79 44 ADD R1, PC ; Base64Table
LOAD:00005A4A 01 31 ADDS R1, #1 ; R1 = Base64Table + 1
LOAD:00005A4C 04 90 STR R0, [SP,#0x38+var_28] ; len_Password - 3
LOAD:00005A4E ; 82: v12 = v9;
LOAD:00005A4E 32 1C MOVS R2, R6 ; R2 = R6 = new_pPassword
LOAD:00005A50 ; 83: v13 = 0;
LOAD:00005A50 1F 1C MOVS R7, R3 ; R7 = R3 = 0
LOAD:00005A52 ; 84: v14 = v3;
LOAD:00005A52 05 91 STR R1, [SP,#0x38+Base64Tableoff1] ; Base64Table + 1
LOAD:00005A54 A4 46 MOV R12, R4 ; R12 = R4 = pPassword
LOAD:00005A56 26 E0 B loc_5AA6 ;
如果没看出来,我们可以手动分析,前提是清楚Base64的计算过程,编码过程是3位转4位,还原过程是4位转3位
比如ABCD
,以3个字符为一组,计算每个的ASCII十六进制
01000001 01000010 01000011
01000100
连起来
010000010100001001000011
01000100
以三字节为单位切开,这样3个字符就变成了4个字符每组
010000 010100 001001 000011
010001 00
前面补00
,最后除了补零,最后的两个不做处理
00010000 00010100 00001001 00000011
00010001 00000000
转为十进制数字
16 20 09 03
17 00
然后到Base64编码Table里寻找对应的下标
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
计算出来,最后没有数据的补上=
,以4字节为一组补
QUJD
RA==
我为什么又要写一遍。。。。。。
第一题的理解程度对于后续的解题很重要,所以我们多写点,反正都是我写。。。。。。
入口判断了长度跟3的关系,长度如果不够说明已经计算到结尾,所以进入特殊处理的分支
接下来手动分析,进入解码前先进行长度的判断
LOAD:00005AA6 loc_5AA6 ;
LOAD:00005AA6 04 99 LDR R1, [SP,#0x38+var_28] ; R1 = len_Password - 3
LOAD:00005AA8 8B 42 CMP R3, R1
LOAD:00005AAA D5 DB BLT loc_5A58 ;
初始化一个下标
LOAD:00005A58 loc_5A58 ;
LOAD:00005A58 00 21 MOVS R1, #0 ; R
然后进入计算的循环,以4字节为一组进行循环获取,获取到的4字节每字节进行查表,这个表就是前面初始化的Table
LOAD:00005A5A loc_5A5A ;
LOAD:00005A5A 64 46 MOV R4, R12 ; R4 = R12 = pPassword
LOAD:00005A5C E0 18 ADDS R0, R4, R3 ; R0 = R4 + R3 = pPassword + i
LOAD:00005A5E 44 5C LDRB R4, [R0,R1] ; R4 = Password[i + j],这里以四字节为单位进行循环遍历
LOAD:00005A60 05 98 LDR R0, [SP,#0x38+Base64Tableoff1] ; R0 = Base64Table + 1
LOAD:00005A62 04 5D LDRB R4, [R0,R4] ; R4 = Base64Table[Password[i + j] + 1]
LOAD:00005A64 ; 91: *(&v21 + v15) = v16;
LOAD:00005A64 07 A8 ADD R0, SP, #0x38+buffer ; 4字节临时存储
LOAD:00005A66 0C 54 STRB R4, [R1,R0] ; 循环取字节进行存储
LOAD:00005A68 ; 92: if ( v16 & 0x80 )
LOAD:00005A68 24 06 LSLS R4, R4, #0x18
LOAD:00005A6A 01 D5 BPL loc_5A70
通过一个变量进行判断4字节每组内部取表操作是否完成
LOAD:00005A70 loc_5A70 ;
LOAD:00005A70 01 31 ADDS R1, #1 ; R1++,4字节每组内部循环
LOAD:00005A72 ; 96: while ( v15 != 4 );
LOAD:00005A72 04 29 CMP R1, #4 ; 判断是否读取完成4字节
LOAD:00005A74 F1 D1 BNE loc_5A5A ;
4字节取表完成后,进行计算
LOAD:00005A76 ; 97: v13 += 3;
LOAD:00005A76 07 A9 ADD R1, SP, #0x38+buffer ; R1 = buffer //获取每组4字节数组基址
LOAD:00005A78 48 78 LDRB R0, [R1,#1] ; R0 = buffer[1]
LOAD:00005A7A 03 37 ADDS R7, #3 ; R7 = 0 + 3 = 3
LOAD:00005A7C ; 98: v11 += 4;
LOAD:00005A7C 04 33 ADDS R3, #4 ; R3 = pPassword + 4 //定位下一组起始地址,跳4字节
LOAD:00005A7E ; 99: v17 = v22;
LOAD:00005A7E 02 90 STR R0, [SP,#0x38+var_30] ; R0(buffer[1])存储到var_30
LOAD:00005A80 ; 100: *(_BYTE *)v12 = ((signed int)v22 >> 4) | 4 * v21;
LOAD:00005A80 0C 78 LDRB R4, [R1] ; R4 = buffer[0]
LOAD:00005A82 00 11 ASRS R0, R0, #4 ; R0 = buffer[1] >> 4
LOAD:00005A84 A4 00 LSLS R4, R4, #2 ; R4 = buffer[0] << 2
LOAD:00005A86 20 43 ORRS R0, R4 ; R0 = R0 | R4 = (buffer[0] << 2) | (buffer[1] >> 4)
LOAD:00005A88 10 70 STRB R0, [R2] ; new_Password[0] = (buffer[0] << 2) | (buffer[1] >> 4)
LOAD:00005A8A ; 101: v18 = v23;
LOAD:00005A8A 8C 78 LDRB R4, [R1,#2] ; R4 = buffer[2]
LOAD:00005A8C 01 94 STR R4, [SP,#0x38+var_34] ; 将R4(buffer[2])存储到var_34
LOAD:00005A8E ; 102: *((_BYTE *)v12 + 1) = 16 * v17 | ((signed int)v23 >> 2);
LOAD:00005A8E A0 10 ASRS R0, R4, #2 ; R0 = R4 >> 2
LOAD:00005A90 02 9C LDR R4, [SP,#0x38+var_30] ; R4 = buffer[1]
LOAD:00005A92 24 01 LSLS R4, R4, #4 ; R4 = buffer[1] << 4
LOAD:00005A94 02 94 STR R4, [SP,#0x38+var_30] ; buffer[1] << 4的结果存储到var_30
LOAD:00005A96 04 43 ORRS R4, R0 ; R4 = R0 | R4 = (buffer[1] << 4) | (buffer[2] >> 2)
LOAD:00005A98 54 70 STRB R4, [R2,#1] ; new_Password[1] = (buffer[1] << 4) | (buffer[2] >> 2)
LOAD:00005A9A ; 103: *((_BYTE *)v12 + 2) = (v18 << 6) | v24;
LOAD:00005A9A 01 98 LDR R0, [SP,#0x38+var_34] ; R0 = buffer[2]
LOAD:00005A9C C9 78 LDRB R1, [R1,#3] ; R1 = buffer[3]
LOAD:00005A9E 84 01 LSLS R4, R0, #6 ; R4 = buffer[2] << 6
LOAD:00005AA0 0C 43 ORRS R4, R1 ; R4 = R1 | R4 = (buffer[1] << 6) | buffer[3]
LOAD:00005AA2 94 70 STRB R4, [R2,#2] ; new_Password[2] = (buffer[1] << 6) | buffer[3]
LOAD:00005AA4 ; 104: v12 = (char *)v12 + 3;
LOAD:00005AA4 03 32 ADDS R2, #3 ; 解码后的数据存储偏移加3,结合上面就是每4位计算出来变成3位
关键的三句,这已经是很明显的Base64解码操作了
new_Password[0] = (buffer[0] << 2) | (buffer[1] >> 4)
new_Password[1] = (buffer[1] << 4) | (buffer[2] >> 2)
new_Password[2] = (buffer[1] << 6) | buffer[3]
接着又进行循环操作,解码完成退出循环,进入数据的存储
LOAD:00005AAC ; 106: v19 = (void *)operator new[](v13);
LOAD:00005AAC 38 1C MOVS R0, R7 ; R0 = R7 = 解码后的密码长度
LOAD:00005AAE FF F7 8E EB BLX _Znaj ; R0 = operator new[](strlen(new_pPassword))
LOAD:00005AB2 ; 107: memmove(v19, v9, v13);
LOAD:00005AB2 31 1C MOVS R1, R6 ; src
LOAD:00005AB4 3A 1C MOVS R2, R7 ; n
LOAD:00005AB6 04 1C MOVS R4, R0 ; R4为新解码后的内存地址
LOAD:00005AB8 FF F7 A0 EB BLX memmove ; memmove(R0, new_pPassword, R7)
LOAD:00005ABC ; 108: if ( v9 )
LOAD:00005ABC 00 2E CMP R6, #0
LOAD:00005ABE 02 D0 BEQ loc_5AC6 ; memmove函数虽然是移动的意思,但是并不是真正的移动
LOAD:00005ABE ; 所以原来的内存还是存在着数据的
清理一下临时空间
LOAD:00005AC0 ; 109: operator delete[]((void *)v9);
LOAD:00005AC0 30 1C MOVS R0, R6 ; 这里进行内存的删除操作
LOAD:00005AC2 FF F7 72 EB BLX _ZdaPv ; operator delete[](new_pPassword)
再次存储数据
LOAD:00005AC6 ; 110: memcpy((void *)(v1 + 60), v19, v13);
LOAD:00005AC6
LOAD:00005AC6 loc_5AC6 ;
LOAD:00005AC6 28 1C MOVS R0, R5 ; R5是一个偏移基址的作用
LOAD:00005AC8 3C 30 ADDS R0, #0x3C ; '<' ; R5+0x3C为最终解码数据的内存地址
LOAD:00005ACA 21 1C MOVS R1, R4 ; src
LOAD:00005ACC 3A 1C MOVS R2, R7 ; n
LOAD:00005ACE FF F7 84 EB BLX memcpy ; 再拷贝解码后的密码到一个结构体里
LOAD:00005AD2 ; 111: if ( v19 )
LOAD:00005AD2 00 2C CMP R4, #0
LOAD:00005AD4 02 D0 BEQ loc_5ADC
再清理内存
LOAD:00005AD6 ; 112: operator delete[](v19);
LOAD:00005AD6 20 1C MOVS R0, R4 ; void *
LOAD:00005AD8 FF F7 66 EB BLX _ZdaPv ; 再次进行内存的清理操作,删除解码后的密码
最后进入一个对比函数
LOAD:00005ADC ; 113: sub_548C(v1);
LOAD:00005ADC
LOAD:00005ADC loc_5ADC
LOAD:00005ADC 28 1C MOVS R0, R5
LOAD:00005ADE FF F7 D5 FC BL sub_548C
LOAD:00005AE2 ; 114: return 1;
LOAD:00005AE2 01 20 MOVS R0, #1
sub_548C()
将用户名和解码后的数据进行对比
LOAD:0000548C sub_548C
LOAD:0000548C
LOAD:0000548C var_1C= -0x1C
LOAD:0000548C
LOAD:0000548C F7 B5 PUSH {R0-R2,R4-R7,LR}
LOAD:0000548E 41 6B LDR R1, [R0,#0x34] ; R1 = pUserName
LOAD:00005490 06 1C MOVS R6, R0 ; R6 = R0 = 结构体基址
LOAD:00005492 1A 4D LDR R5, =(_GLOBAL_OFFSET_TABLE_ - 0x54AA)
LOAD:00005494 08 1C MOVS R0, R1 ; R0 = R1 = pUserName
LOAD:00005496 01 91 STR R1, [SP,#0x20+var_1C] ; 将pUserName存储到var_1C
LOAD:00005498 FF F7 92 EE BLX strlen ; R0 = strlen(pUserName)
LOAD:0000549C 07 1C MOVS R7, R0 ; R7 = R0 = 用户名长度
LOAD:0000549E 30 1C MOVS R0, R6 ; R0 = R6 = 结构体基址
LOAD:000054A0 3C 30 ADDS R0, #0x3C ; '<' ; R0 = 解码后的数据,此处命名为pPassDecoded
LOAD:000054A2 FF F7 8E EE BLX strlen ; R0 = strlen(pPassDecoded)
LOAD:000054A6 7D 44 ADD R5, PC ; _GLOBAL_OFFSET_TABLE_ ; R5 = 全局偏移表
LOAD:000054A8 00 24 MOVS R4, #0 ; R4 = 0
LOAD:000054AA 87 42 CMP R7, R0 ; R7 = 用户名长度,此处在判断用户名长度是否为0
LOAD:000054AC 1A D0 BEQ loc_54E4
循环对比
LOAD:000054C0 loc_54C0 ;
LOAD:000054C0 01 99 LDR R1, [SP,#0x20+var_1C] ; R1 = pUserName
LOAD:000054C2 33 19 ADDS R3, R6, R4
LOAD:000054C4 3C 33 ADDS R3, #0x3C ; '<' ; R3 = PassDecoded + i
LOAD:000054C6 0A 5D LDRB R2, [R1,R4] ; R2 = UserName
LOAD:000054C8 1B 78 LDRB R3, [R3] ; R3 = PassDecoded
LOAD:000054CA 9A 42 CMP R2, R3 ; 对比用户名和解码后的数据
LOAD:000054CC 09 D0 BEQ loc_54E2 ;
LOAD:000054CC ; 相等继续对比,i++
不相等则异常退出
所以整个校验逻辑就是,输入用户名以及用户名的Base64编码作为密码即可,编码后的数据需要每3位插入一个-
长度也需要注意范围的校验,所以简单写个Java程序来计算即可,代码写的挫,不贴了
大概就是这样
NAGA & PIOWIND 2014 APP应用攻防竞赛第二阶段第二题题解
Java层加了不知道是什么的花指令
其实认真看一下大概还是能看出来的,我闲着无聊给处理了一下
package com.crackme;
import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.PrintStream;
public class MainActivity extends Activity {
private Button btn_login;
private Button btn_reset;
public static MainActivity m_lpThisBen;
private EditText txt_name;
private EditText txt_passwd;
private TextView txt_result;
static {
String v0 = "crackme";
System.loadLibrary(v0);
}
public MainActivity() {
super();
}
static EditText access$0(MainActivity arg2) {
EditText v0 = arg2.txt_name;
return v0;
}
static EditText access$1(MainActivity arg2) {
EditText v0 = arg2.txt_passwd;
return v0;
}
static TextView access$2(MainActivity arg2) {
TextView v0 = arg2.txt_result;
return v0;
}
static String access$3(MainActivity arg2, String arg3, String arg4) {
String v0 = arg2.crackme(arg3, arg4);
return v0;
}
private native String crackme(String arg1, String arg2) {
}
protected void onCreate(Bundle arg4) {
super.onCreate(arg4);
int v0 = 2130903040;
this.setContentView(v0);
v0 = 2131165184;
View v0_1 = this.findViewById(v0);
this.txt_name = ((EditText)v0_1);
v0 = 2131165185;
v0_1 = this.findViewById(v0);
this.txt_passwd = ((EditText)v0_1);
v0 = 2131165186;
v0_1 = this.findViewById(v0);
this.btn_login = ((Button)v0_1);
v0 = 2131165187;
v0_1 = this.findViewById(v0);
this.btn_reset = ((Button)v0_1);
v0 = 2131165188;
v0_1 = this.findViewById(v0);
this.txt_result = ((TextView)v0_1);
Button v0_2 = this.btn_login;
com.crackme.MainActivity$1 v1 = new View$OnClickListener() {
public void onClick(View arg8) {
TextView v3_6;
String v4;
PrintStream v3_5;
MainActivity v3 = MainActivity.this;
EditText v3_1 = MainActivity.access$0(v3);
Editable v3_2 = v3_1.getText();
String v0 = v3_2.toString();
v3 = MainActivity.this;
v3_1 = MainActivity.access$1(v3);
v3_2 = v3_1.getText();
String v1 = v3_2.toString();
String v3_3 = "";
boolean v3_4 = v3_3.equals(v0);
if(v3_4) {
v3_5 = System.out;
v4 = "name is null or \'\'";
v3_5.println(v4);
v3 = MainActivity.this;
v3_6 = MainActivity.access$2(v3);
v4 = "账户为空";
v3_6.setText(((CharSequence)v4));
}
else {
v3_3 = "";
v3_4 = v3_3.equals(v1);
if(v3_4) {
v3_5 = System.out;
v4 = "passwd is null or \'\'";
v3_5.println(v4);
v3 = MainActivity.this;
v3_6 = MainActivity.access$2(v3);
v4 = "密码为空";
v3_6.setText(((CharSequence)v4));
while(true) {
if(98 >= 0) {
goto label_152;
}
}
}
v3_5 = System.out;
String v5 = "name:";
StringBuilder v4_1 = new StringBuilder(v5);
v4_1 = v4_1.append(v0);
v4 = v4_1.toString();
v3_5.println(v4);
v3_5 = System.out;
v5 = "passwd:";
v4_1 = new StringBuilder(v5);
v4_1 = v4_1.append(v1);
v4 = v4_1.toString();
v3_5.println(v4);
v3_5 = System.out;
v4 = "Please treat me gently, you have to go a long way.";
v3_5.println(v4);
v3 = MainActivity.this;
String v2 = MainActivity.access$3(v3, v0, v1);
v3 = MainActivity.this;
v3_6 = MainActivity.access$2(v3);
v3_6.setText(((CharSequence)v2));
}
label_152:
}
};
v0_2.setOnClickListener(((View$OnClickListener)v1));
v0_2 = this.btn_reset;
com.crackme.MainActivity$2 v1_1 = new View$OnClickListener() {
public void onClick(View arg4) {
MainActivity v0 = MainActivity.this;
EditText v0_1 = MainActivity.access$0(v0);
String v1 = "";
v0_1.setText(((CharSequence)v1));
v0 = MainActivity.this;
v0_1 = MainActivity.access$1(v0);
v1 = "";
v0_1.setText(((CharSequence)v1));
v0 = MainActivity.this;
TextView v0_2 = MainActivity.access$2(v0);
v1 = "";
v0_2.setText(((CharSequence)v1));
}
};
v0_2.setOnClickListener(((View$OnClickListener)v1_1));
}
}
和第一题大概是一样的,再看so文件,同样加密了,使用第一题的方法动态调试
撸回本地
auto fp, dex_addr, end_addr;
fp = fopen("E:\\libcrackme.so", "wb");
for(dex_addr = 0xA350E000; dex_addr < 0xA355B000; dex_addr++)
fputc(Byte(dex_addr), fp);
但是我们使用IDA打开的时候,发现出错,使用010editor分析,发现文件头没了
我们找个正常的so文件的文件头拷贝回去,然后再使用IDA打开,发现可以打开了
我们继续分析,整个程序的结构和第一题是一样的
新建内存空间
Base64解码
但是我们在最后校验的地方发现了不一样的地方,它校验的方式是反过来的
突然界面就酷炫了起来
那么就比较简单了,注册码就是用户名倒序的Base64编码再插入-
注册算法
public class MyKeyGen {
public static void main(String[] args) throws Exception {
String userName = "wnagzihxain";
StringBuilder re_userName = new StringBuilder(userName).reverse();
StringBuilder temp = new StringBuilder(Base64.getBase64(re_userName.toString()));
System.out.println(temp);
StringBuilder regCode = new StringBuilder();
for (int i = 0; i < temp.length(); i++) {
regCode.append(temp.charAt(i));
if (temp.charAt(i + 1) == '=') {
break;
}
if ((i + 1) % 3 == 0) {
regCode.append('-');
}
}
System.out.println(regCode);
}
}
NAGA & PIOWIND 2014 APP应用攻防竞赛第二阶段第三题题解
一开始用模拟器跑起来就崩溃,我以为是模拟器系统版本的问题,后来看了配置文件,发现没有问题,猜测可能是有反调试,第三题了应该出现反调试了
于是使用调试模式启动应用
先修改android_server
的名称和监听端口
root@generic:/data/local/tmp # ./as -p23333
相应的端口转发也要修改
C:\Users\Luyu>adb forward tcp:23946 tcp:23333
然后调试模式启动
adb shell am start -D -n com.crackme/.MainActivity
开启DDMS,选中待调试的应用,前面出现小虫子
然后输入
C:\Users\Luyu>jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
设置未捕获的java.lang.Throwable
设置延迟的未捕获的java.lang.Throwable
正在初始化jdb...
>
如果出现如下信息,先关掉Android Studio等玩意
C:\Users\Luyu>jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at com.sun.tools.jdi.SocketTransportService.attach(SocketTransportService.java:222)
at com.sun.tools.jdi.GenericAttachingConnector.attach(GenericAttachingConnector.java:116)
at com.sun.tools.jdi.SocketAttachingConnector.attach(SocketAttachingConnector.java:90)
at com.sun.tools.example.debug.tty.VMConnection.attachTarget(VMConnection.java:519)
at com.sun.tools.example.debug.tty.VMConnection.open(VMConnection.java:328)
at com.sun.tools.example.debug.tty.Env.init(Env.java:63)
at com.sun.tools.example.debug.tty.TTY.main(TTY.java:1082)
致命错误:
无法附加到目标 VM。
修改调试选项,然后使用IDA attach
挂上去后,点击运行,或者F9,断在ELF的入口
按照前两题的方法dump解密后的so即可
auto fp, dex_addr, end_addr;
fp = fopen("E:\\libcrackme.so", "wb");
for(dex_addr = 0xA33AC000; dex_addr < 0xA3406000; dex_addr++)
fputc(Byte(dex_addr), fp);
文件头依旧是空的,使用正常的ELF文件头覆盖回去,使用IDA打开,找到_Z9fdog_initv
,这里实现了反调试
跟入该函数,该函数创建了很多的子线程进行反调试,我们一个个跟
这里有一个问题,创建子线程时的函数地址是动态调试时内存的地址,如果不清楚我们看伪代码
所有子线程的函数参数全是内存地址,有两种方法,第一种是动态调试,但是需要过掉反调试,第二种方法,可以看到我在上面的截图里已经体现出来了,so加载到内存里的地址一般都是0x*****000
,所以我们可以根据地址的后三位来搜索函数,而且IDA识别函数后会将函数命名为sub_****
,而且有地址的偏移等信息
那么我们来一个个找
第一个函数是_Z13debuggdb_scanv
,实现了循环检测
跟进其中调用的函数,检测的是进程名,分别检测了gdb
,gdbserver
,android_server
,xposed
第二个函数_Z26file_pro_thread_strengthenv
,这个函数我不知道是什么反调试操作,比较猥琐的感觉
先放着,我找其他师傅再问问
第三个函数_Z18prevent_attach_onev
,看起来好像是调用了上了锁的函数的样子
这个函数读取了进程的/proc/%d/task/%s/stat
文件夹
判断了标志位
第四个函数_Z18prevent_attach_twov
好像也是调用了上了锁的函数
反调试部分大概就是这样
接下来我们来看校验部分
进入校验,先初始化内存空间存储用户名和注册码,然后进入校验
三处-
判断,然后进入校验
变量的初始化
将解码后的数据前n - 2
位进行奇数位和偶数位的交换
最后正常对比
最后测试
注册代码
public class MyKeyGen {
public static void main(String[] args) throws Exception {
String userName = "wnagzihxain";
StringBuilder re_userName = new StringBuilder();
for (int i = 0; i < userName.length() - 2; i += 2) {
re_userName.append(userName.charAt(i + 1));
re_userName.append(userName.charAt(i));
}
re_userName.append(userName.charAt(userName.length() - 1));
// System.out.println(re_userName.toString());
StringBuilder temp = new StringBuilder(Base64.getBase64(re_userName.toString()));
System.out.println(temp);
StringBuilder regCode = new StringBuilder();
for (int i = 0; i < temp.length(); i++) {
regCode.append(temp.charAt(i));
if (temp.charAt(i + 1) == '=') {
break;
}
if ((i + 1) % 3 == 0) {
regCode.append('-');
}
}
System.out.println(regCode);
}
}
NAGA & PIOWIND 2014 APP应用攻防竞赛第二阶段第四题题解
Java层比较简单
查看so,发现加密,依旧dump,IDA调试时未发现有反调试,不过有那么一瞬间看到了inotify
,没具体看
auto fp, dex_addr, end_addr;
fp = fopen("E:\\libcrackme.so", "wb");
for(dex_addr = 0xA357D000; dex_addr < 0xA35DE000; dex_addr++)
fputc(Byte(dex_addr), fp);
修复dump后的so文件头,使用IDA打开,关键的依旧是这个函数
但是我们跟入后,发现壳好像没有脱干净(后来发现其实不是没脱干净)
再次动态调试脱壳,这次我们找到校验函数,单步跟下去看看具体是什么情况
我们需要先找到校验函数的地址,使用给dvmUseJNIBridge
函数下断点的方法
我们使用调试模式启动应用,IDA挂上去,找到libdvm.so
的dvmUseJNIBridge
函数,下断点
然后把IDA跑起来,在应用界面输入账号密码,点击登录,就可以发现断在这里了,我们注意观察参数,第二个参数就是我们的crackme
函数
跟过去,可以看到确实是校验函数
找到我们看到是跳转地址的地方
双击过去
再次双击过去,发现是关键加解密点了,此时我们记录一下这个地址
再次dump这个so文件
动静结合,接下来看能力了
入口的数据初始化,然后调用_Unwind_GetCFAB
,这和前几题是类似的
跟入,开始做了一些参数的存储操作,然后存储了_Unwind_GetCFAB
函数的指针到栈中
LOAD:0002DB28 STMFD SP!, {R4,R11,LR}
LOAD:0002DB2C ADD R11, SP, #8
LOAD:0002DB30 SUB SP, SP, #0x24
LOAD:0002DB34 LDR R4, =(off_46CC8 - 0x2DB40)
LOAD:0002DB38 ADD R4, PC, R4 ; off_46CC8 ; 神奇的地址,暂时不知道干什么的
LOAD:0002DB3C STR R0, [R11,#var_20] ; R0 = var_20 = szUserName
LOAD:0002DB40 STR R1, [R11,#var_24] ; R1 = var_24 = szRegCode
LOAD:0002DB44 MOV R3, #0 ; R3 = 0
LOAD:0002DB48 STR R3, [R11,#var_18] ; R3 = var_18 = 0
LOAD:0002DB4C LDR R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB50 LDR R3, [R4,R3]
LOAD:0002DB54 LDR R3, [R3]
LOAD:0002DB58 STR R3, [R11,#var_14] ; var_14为_Unwind_GetCFAB函数指针
一开始并没有看出来,所以使用了动态调试来确定
接下来的操作是为了调用tdog_decrypt
而做参数的计算
LOAD:0002DB5C LDR R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB60 LDR R3, [R4,R3]
LOAD:0002DB64 LDR R3, [R3] ; R3 = _Unwind_GetCFAB
LOAD:0002DB68 ADD R3, R3, #0x14 ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB6C MOV R1, R3 ; R1 = R3 = R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB70 LDR R3, =dword_2B0 ; R3 = 0x2B0
LOAD:0002DB74 LDR R3, [R4,R3]
LOAD:0002DB78 LDR R3, [R3] ; 动调 : R3 = 0x104
LOAD:0002DB7C SUB R2, R3, #0x14 ; R2 = 0xF0
LOAD:0002DB80 LDR R3, =dword_2CC ; R3 = 0x2CC
LOAD:0002DB84 LDR R3, [R4,R3]
LOAD:0002DB88 LDR R3, [R3] ; R3 = _Unwind_GetCFAB
LOAD:0002DB8C ADD R3, R3, #0x14 ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DB90 LDR R0, =dword_1D4 ; R0 = 0x1D4
LOAD:0002DB94 LDR R0, [R4,R0]
LOAD:0002DB98 LDR R0, [R0]
LOAD:0002DB9C STR R0, [SP,#0x2C+var_2C] ; var_2C = abs_export_function_key
LOAD:0002DBA0 MOV R0, R1 ; R0 = _Unwind_GetCFAB + 0x14
LOAD:0002DBA4 MOV R1, R2 ; R1 = R2 = 0xF0
LOAD:0002DBA8 MOV R2, R3 ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0002DBAC LDR R3, =dword_314 ; R3 = 0x314
LOAD:0002DBB0 LDR R3, [R4,R3] ; 指向内存
LOAD:0002DBB4 BL tdog_decrypt
看名字就可以猜到这个函数很重要了
tdog_decrypt(_Unwind_GetCFAB + 0x14, 0xF0, _Unwind_GetCFAB + 0x14, 某内存变量指针)
双击跟入,发现其调用了一个XorArray()
函数
LOAD:000387F8 STMFD SP!, {R11,LR}
LOAD:000387FC ADD R11, SP, #4
LOAD:00038800 SUB SP, SP, #0x10
LOAD:00038804 ; 5: v5 = a2;
LOAD:00038804 STR R0, [R11,#var_8] ; R0 = var_8 = _Unwind_GetCFAB + 0x14
LOAD:00038808 STR R1, [R11,#var_C] ; R1 = var_C = 0xF0
LOAD:0003880C ; 6: v6 = a4;
LOAD:0003880C STR R2, [R11,#var_10] ; R2 = var_10 = _Unwind_GetCFAB + 0x14
LOAD:00038810 STR R3, [R11,#var_14] ; R3 = var_14 = 第四个参数,为一个指针
LOAD:00038814 ; 7: XorArray(a5, a1, a1, a2);
LOAD:00038814 LDR R2, [R11,#var_8] ; R2 = _Unwind_GetCFAB + 0x14
LOAD:00038818 LDR R3, [R11,#var_8] ; R3 = _Unwind_GetCFAB + 0x14
LOAD:0003881C LDR R0, [R11,#arg_0] ; 动调 : R0 = 0x5F7C8B38
LOAD:00038820 MOV R1, R2 ; R1 = R2 = _Unwind_GetCFAB + 0x14
LOAD:00038824 MOV R2, R3 ; R2 = R3 = _Unwind_GetCFAB + 0x14
LOAD:00038828 LDR R3, [R11,#var_C] ; R3 = 0xF0
LOAD:0003882C BL _Z8XorArrayjPhS_j ; XorArray(uint,uchar *,uchar *,uint)
在XorArray
函数里有一个PolyXorKey
函数,用于生成秘钥,这个函数在后续的娜迦壳里面是一个比较重要的特征,后续的类抽取技术里就有用到这个函数进行秘钥的计算
我一直觉得这里加了junk code,前面有些指令反复做同样的操作时我就感觉出来了,但是加的junk code并不是很多,比如这该函数的第一个函数块后面的几句
LOAD:00038D7C STMFD SP!, {R11,LR}
LOAD:00038D80 ADD R11, SP, #4
LOAD:00038D84 SUB SP, SP, #0x20
LOAD:00038D88 ; 10: v6 = a2;
LOAD:00038D88 STR R0, [R11,#var_18] ; var_18 = 0X5F7C8B38 //神秘变量
LOAD:00038D8C STR R1, [R11,#var_1C] ; var_1C = _Unwind_GetCFAB + 0x14
LOAD:00038D90 ; 11: v5 = a3;
LOAD:00038D90 STR R2, [R11,#var_20] ; var_20 = _Unwind_GetCFAB + 0x14
LOAD:00038D94 ; 12: v4 = a4;
LOAD:00038D94 STR R3, [R11,#var_24] ; var_24 = 0xF0
LOAD:00038D98 ; 13: v7 = result;
LOAD:00038D98 LDR R3, [R11,#var_18] ; R3 = 0X5F7C8B38 //神秘,神秘
LOAD:00038D9C STR R3, [R11,#var_14] ; var_14 = 0X5F7C8B38 //又是神秘
LOAD:00038DA0 ; 14: v8 = &v7;
LOAD:00038DA0 SUB R3, R11, #-var_14
LOAD:00038DA4 STR R3, [R11,#var_10] ; R3存储的是神秘变量0X5F7C8B38的指针
LOAD:00038DA8 ; 15: v10 = 0;
LOAD:00038DA8 MOV R3, #0 ; R3 = 0
LOAD:00038DAC STR R3, [R11,#var_C] ; var_C = 0
LOAD:00038DB0 MOV R3, #0 ; R3 = 0
LOAD:00038DB4 STR R3, [R11,#var_8] ; var_8 = 0
LOAD:00038DB8 ; 16: for ( i = 0; v4 > i; ++i )
LOAD:00038DB8 MOV R3, #0 ; R3 = 0
LOAD:00038DBC STR R3, [R11,#var_C] ; R3 = 0
LOAD:00038DC0 B loc_38E40
开始进入循环
LOAD:00038E40 loc_38E40 ;
LOAD:00038E40 LDR R2, [R11,#var_24] ; R2 = 0xF0 = 240
LOAD:00038E44 LDR R3, [R11,#var_C] ; R3 = i
LOAD:00038E48 CMP R2, R3
LOAD:00038E4C MOVLE R3, #0
LOAD:00038E50 MOVGT R3, #1 ; 循环中使用这一句 : i < 240 --- > R3 = 1
LOAD:00038E54 AND R3, R3, #0xFF ; 用于判断是否到达退出条件
LOAD:00038E58 CMP R3, #0
LOAD:00038E5C BNE loc_38DC4
两个基址获取字节数据,进行异或操作,异或后的数据,存在_Unwind_GetCFAB + 0x14 + i
指向的字节
LOAD:00038DC4 ; 19: v5 = v6 ^ *((_BYTE *)v8 + v10);
LOAD:00038DC4 loc_38DC4 ;
LOAD:00038DC4 LDR R3, [R11,#var_C] ; R3 = i
LOAD:00038DC8 LDR R2, [R11,#var_20] ; R2 = _Unwind_GetCFAB + 0x14
LOAD:00038DCC ADD R3, R2, R3 ; R3 = _Unwind_GetCFAB + 0x14 + i
LOAD:00038DD0 LDR R2, [R11,#var_C] ; R2 = i
LOAD:00038DD4 LDR R1, [R11,#var_1C] ; R1 = _Unwind_GetCFAB + 0x14
LOAD:00038DD8 ADD R2, R1, R2 ; R2 = _Unwind_GetCFAB + 0x14 + i
LOAD:00038DDC ; 18: result = (int)v8;
LOAD:00038DDC LDRB R1, [R2] ; 取第一个字节,动调 : R1 = 0xBB
LOAD:00038DE0 LDR R2, [R11,#var_8] ; R2 = j
LOAD:00038DE4 LDR R0, [R11,#var_10] ; R0为神秘变量指针
LOAD:00038DE8 ADD R2, R0, R2 ; R2 = 神秘变量指针 + j
LOAD:00038DEC LDRB R2, [R2] ; 取第一个字节,动调 : 0x38
LOAD:00038DF0 EOR R2, R1, R2 ; 两个取出来的字节异或
LOAD:00038DF4 AND R2, R2, #0xFF
LOAD:00038DF8 STRB R2, [R3]
LOAD:00038DFC ; 20: if ( v10 == 3 )
LOAD:00038DFC LDR R3, [R11,#var_8]
LOAD:00038E00 CMP R3, #3
LOAD:00038E04 BNE loc_38E28
这里是在计算一个四字节的数据
我们在内存中跟随,可以看到这四个字节的数据已经修改成了83 93 00 23
,不清楚的同学可以在异或的地方下个断点循环调试看看
接着是调用PolyXorKey
,参数是神秘变量自身
LOAD:00038E08 ; 22: result = PolyXorKey(v7);
LOAD:00038E08 LDR R3, [R11,#var_14] ; 取出神秘变量
LOAD:00038E0C MOV R0, R3 ; 神秘变量作为参数R0
LOAD:00038E10 BL _Z10PolyXorKeyj ; PolyXorKey(
先使用异或操作对神秘变量进行修改
LOAD:00038BC8 STR R11, [SP,#-4+var_s0]!
LOAD:00038BCC ADD R11, SP, #0
LOAD:00038BD0 SUB SP, SP, #0x24
LOAD:00038BD4 ; 11: v4 = 0;
LOAD:00038BD4 STR R0, [R11,#var_20] ; var_20 = 神秘变量
LOAD:00038BD8 MOV R3, #0 ; R3 = 0
LOAD:00038BDC STR R3, [R11,#var_18] ; var_18 = 0
LOAD:00038BE0 MOV R3, #0 ; R3 = 0
LOAD:00038BE4 STR R3, [R11,#var_14] ; var_14 = 0
LOAD:00038BE8 ; 12: v5 = 0;
LOAD:00038BE8 MOV R3, #0 ; R3 = 0
LOAD:00038BEC STR R3, [R11,#var_10] ; var_10 = 0
LOAD:00038BF0 ; 13: v6 = (char *)&v2;
LOAD:00038BF0 SUB R3, R11, #-var_20
LOAD:00038BF4 STR R3, [R11,#var_C] ; var_C为神秘变量指针
LOAD:00038BF8 ; 14: v7 = 0;
LOAD:00038BF8 MOV R3, #0 ; R3 = 0
LOAD:00038BFC STRB R3, [R11,#var_7] ; var_7指向的byte为0
LOAD:00038C00 ; 15: v8 = 0;
LOAD:00038C00 MOV R3, #0
LOAD:00038C04 STRB R3, [R11,#var_6] ; var_6指向的byte为0
LOAD:00038C08 ; 16: v9 = 0;
LOAD:00038C08 MOV R3, #0
LOAD:00038C0C STRB R3, [R11,#var_5] ; var_5指向的byte为0
LOAD:00038C10 ; 17: v2 = a1 ^ 0xDF138530;
LOAD:00038C10 LDR R3, [R11,#var_20] ; R3 = 神秘变量
LOAD:00038C14 MOV R2, R3 ; R2 = R3 = 神秘变量
LOAD:00038C18 LDR R3, =0xDF138530 ; R3 = 0xDF138530
LOAD:00038C1C EOR R3, R2, R3 ; 神秘变量异或 ---> R3 = 0x5F7C8B38 ^ 0xDF138530
LOAD:00038C20 STR R3, [R11,#var_20] ; 修改神秘变量为0x806F0E08
LOAD:00038C24 ; 18: v3 = 0;
LOAD:00038C24 MOV R3, #0 ; R3 = 0
LOAD:00038C28 STR R3, [R11,#var_18] ; var_18 = 0
LOAD:00038C2C B loc_38D48
进入大循环,整个大循环就是循环计算神秘变量的四个字节,但是内部又有很多的循环计算
LOAD:00038D48 ; 19: while ( v3 <= 3 )
LOAD:00038D48 loc_38D48 ; 开始循环计算
LOAD:00038D48 LDR R3, [R11,#var_18] ; R3 = i
LOAD:00038D4C CMP R3, #3 ; 条件判断 i < 4
LOAD:00038D50 MOVGT R3, #0
LOAD:00038D54 MOVLE R3, #1
LOAD:00038D58 AND R3, R3, #0xFF
LOAD:00038D5C CMP R3, #0
LOAD:00038D60 BNE loc_38C30
取字节,这里的var_C
会在后面自加一
LOAD:00038C30 ; 21: v7 = *v6;
LOAD:00038C30 loc_38C30 ;
LOAD:00038C30 LDR R3, [R11,#var_C] ; R3为神秘变量指针
LOAD:00038C34 LDRB R3, [R3] ; 获取计算后的神秘变量的字节
LOAD:00038C38 STRB R3, [R11,#var_7] ; var_7 = 08
LOAD:00038C3C ; 22: v4 = 128;
LOAD:00038C3C MOV R3, #0x80 ; '?' ; R3 = 0x80
LOAD:00038C40 STR R3, [R11,#var_14] ; var_14 = 0x80
LOAD:00038C44 ; 23: v5 = 7;
LOAD:00038C44 MOV R3, #7 ; R3 = 0x7
LOAD:00038C48 STR R3, [R11,#var_10] ; var_10 = 0x7
LOAD:00038C4C B loc_38CE0
内部的循环
LOAD:00038CE0 ; 24: while ( v4 > 1 )
LOAD:00038CE0
LOAD:00038CE0 loc_38CE0 ;
LOAD:00038CE0 LDR R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038CE4 CMP R3, #1
LOAD:00038CE8 MOVLE R3, #0
LOAD:00038CEC MOVGT R3, #1
LOAD:00038CF0 AND R3, R3, #0xFF
LOAD:00038CF4 CMP R3, #0
LOAD:00038CF8 BNE loc_38C50
接下来的循环计算可以还原出C代码,但是具体是什么数学算法之类的就不是很清楚了,可能只是个计算,这个函数最终的功能目测应该是计算一个四字节的数据作为返回值
LOAD:00038C50 ; 27: v8 = ((signed int)(unsigned __int8)(v7 & v4) >> v5) ^ v9;
LOAD:00038C50 loc_38C50 ; 进入循环,重命名神秘变量为sec
LOAD:00038C50 LDRB R2, [R11,#var_7] ; R2 = 0x08
LOAD:00038C54 LDR R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038C58 AND R2, R2, R3 ; R2 = sec & 0x80
LOAD:00038C5C LDR R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038C60 MOV R3, R2,ASR R3 ; R3 = (sec & 0x80) / 0x07
LOAD:00038C64 ; 26: v9 = (v7 & v4 / 2) >> (v5 - 1);
LOAD:00038C64 STRB R3, [R11,#var_6] ; var_6 = (sec & 0x80) / 0x07
LOAD:00038C68 LDRB R2, [R11,#var_7] ; R2 = sec
LOAD:00038C6C LDR R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038C70 MOV R1, R3,LSR#31 ; R1 = 0x00000080 >> 31 = 0
LOAD:00038C74 ADD R3, R1, R3 ; R3 = 0x80
LOAD:00038C78 MOV R3, R3,ASR#1 ; R3 = 0x80 / 2
LOAD:00038C7C AND R2, R2, R3 ; R2 = sec & (0x80 / 2)
LOAD:00038C80 LDR R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038C84 SUB R3, R3, #1 ; R3 = 0x07 - 1
LOAD:00038C88 MOV R3, R2,ASR R3 ; R3 = (sec & (0x80 / 2)) / (0x07 - 1)
LOAD:00038C8C STRB R3, [R11,#var_5] ; var_5存储计算后的结果
LOAD:00038C90 LDRB R2, [R11,#var_6] ; R2 = (sec & 0x80) / 0x07
LOAD:00038C94 LDRB R3, [R11,#var_5] ; R3 = (sec & (0x80 / 2)) / (0x07 - 1)
LOAD:00038C98 EOR R3, R2, R3 ; 上面两个进行异或,存储到R3
LOAD:00038C9C STRB R3, [R11,#var_6] ; 异或结果存储到var_6
LOAD:00038CA0 ; 28: v8 <<= v5;
LOAD:00038CA0 LDRB R2, [R11,#var_6] ; R2 = 异或结果
LOAD:00038CA4 LDR R3, [R11,#var_10] ; R3 = 0x07
LOAD:00038CA8 MOV R3, R2,LSL R3 ; R3 = 异或结果 << 0x07
LOAD:00038CAC STRB R3, [R11,#var_6] ; var_6 = 异或结果 << 0x07
LOAD:00038CB0 ; 29: v7 |= v8;
LOAD:00038CB0 LDRB R2, [R11,#var_7] ; R2 = sec
LOAD:00038CB4 LDRB R3, [R11,#var_6] ; R3 = 异或结果 << 0x07
LOAD:00038CB8 ORR R3, R2, R3 ; R3 = sec | (异或结果 << 0x07)
LOAD:00038CBC STRB R3, [R11,#var_7] ; var_7 = sec | (异或结果 << 0x07)
LOAD:00038CC0 ; 30: v4 /= 2;
LOAD:00038CC0 LDR R3, [R11,#var_14] ; R3 = 0x80
LOAD:00038CC4 MOV R2, R3,LSR#31 ; R2 = 0
LOAD:00038CC8 ADD R3, R2, R3 ; R3 = 0x80
LOAD:00038CCC MOV R3, R3,ASR#1 ; R3 = 0x80 / 2
LOAD:00038CD0 STR R3, [R11,#var_14] ; var_14 = 0x80 / 2 //这里应该是这个变量循环除2
LOAD:00038CD4 ; 31: --v5;
LOAD:00038CD4 LDR R3, [R11,#var_10] ; 0x07-- //这里也是这个变量循环自减一结果作为下次循环的值
LOAD:00038CD8 SUB R3, R3, #1
LOAD:00038CDC STR R3, [R11,#var_10]
直接在最后面下个断点跑完这个函数,可以看到返回值是0x80FF1E18
这是整个大循环
回到上一层函数,这个值应该是固定的,暂时没有看到有其它参数对这个计算过程造成了影响
一边分析一边写的,估计有些地方会分析错
这个函数整个大循环是0xF0
次,也就是240
次,我们来验证一下PolyXorKey
函数是否每次都是生成一样的数据
0xFFFCBF78
0x60FF7ED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
0x60FFFED8
0xFFFCFFF8
..........
0xFFFCFFF8
开始变成两个常数的交替出现,难道是动态调试出问题了
先放一边好了
这里非常绕,跟了好几次都没有找到关键的地方,后来半猜半想,根据调用operator new[]()
的函数往回找,找到了和前几题一样的函数,虽然这里算法不一样,但是对于用户名和注册码的存储还是一样的
接下来是校验的地方,单步走一遍先,找到关键的地方,可以看到这里调用了四个函数
但是在静态时这位置我是手动找的,这个费劲,有的函数没有识别出来,红色的。。。。。。
其实还有非常多的函数未识别出来,不过并不是很重要
由于前面没有完整的跟过来,所以这里的一些偏移需要根据动态调试确定指向的数据是什么
那么0x34
偏移指向的就是用户名
并且有长度的限制,用户名长度应该在[8, 24]
之间
LOAD:00005E66 MOVS R0, R3 ; s
LOAD:00005E68 BLX strlen ; 获取用户名长度
LOAD:00005E6C MOVS R6, R0 ; R6 = R0 = 用户名长度
LOAD:00005E6E SUBS R6, #8 ; R6 = strlen(UserName) - 8
LOAD:00005E70 MOVS R0, R5 ; s
LOAD:00005E72 BLX strlen ; 获取注册码长度
LOAD:00005E76 CMP R6, #0x16 ; 对比strlen(UserName) - 8和0x16
LOAD:00005E78 BHI loc_5E80
偏移0x38
指向的是注册码,注册码长度需要在[12, 100]
之间
LOAD:00005E7A SUBS R0, #0xC
LOAD:00005E7C CMP R0, #0x58 ; 'X' ; 对比strlen(RegCode) - 0xC和0x58
LOAD:00005E7E BLS loc_5E94
第二个函数比较长
获取用户名
LOAD:00005D48 PUSH {LR}
LOAD:00005D4A SUB SP, SP, #0x2C
LOAD:00005D4C STR R0, [SP,#0x30+var_20] ; 结构体基址
LOAD:00005D4E LDR R0, [R0,#0x34] ; 获取用户名
LOAD:00005D50 BLX strlen ; R0 = 用户名长度
LOAD:00005D54 CMP R0, #7 ; 用户名长度与7进行对比
LOAD:00005D56 BGT loc_5D6E ; 用户名长度需要大于7 ---> strlen(UserMame) >= 8
存储一下中间变量
LOAD:00005D84 loc_5D84
LOAD:00005D84 LDR R3, =(dword_16AF8 - 0x5D8A)
LOAD:00005D86 ADD R3, PC ; dword_16AF8
LOAD:00005D88 ADDS R3, #0x30 ; '0' ; env
LOAD:00005D8A STR R3, [SP,#0x30+env] ; 将env变量存储到栈中
LOAD:00005D8C STR R3, [SP,#0x30+var_C] ; var_C = env
进入一个0x08 * 0x100
次的循环,循环获取用户名的前八位数据
LOAD:00005D9E loc_5D9E ;
LOAD:00005D9E LDR R1, [SP,#0x30+var_20] ; R1为结构体基址
LOAD:00005DA0 LDR R2, [SP,#0x30+var_28] ; R2 = 0
LOAD:00005DA2 LDR R3, [R1,#0x34] ; R3 = pUserName
LOAD:00005DA4 LDRB R3, [R3,R2] ; 循环取用户名的字节数据
LOAD:00005DA6 STR R3, [SP,#0x30+var_2C] ; 获取的数据暂存栈中
获取一个关键偏移
LOAD:00005DA8 loc_5DA8 ;
LOAD:00005DA8 MOVS R3, #0 ; R3 = 0
LOAD:00005DAA STR R3, [SP,#0x30+var_24] ; var_24 = 0
LOAD:00005DAC LDR R3, =(dword_16AF8 - 0x5DB4)
LOAD:00005DAE LDR R1, =0x104B2 ; R1 = 0x104B2
LOAD:00005DB0 ADD R3, PC ; dword_16AF8 ; 定位结构体基址
LOAD:00005DB2 ADDS R3, #0x30 ; '0' ; env
LOAD:00005DB4 STR R3, [SP,#0x30+var_14] ; var_14 = env
LOAD:00005DB6 STR R1, [SP,#0x30+var_10] ; var_10 = 0x104B2
这个偏移在这里的作用是重定位一个Table,通过和用户名相同的偏移来进行数据获取,然后两者异或
LOAD:00005DC8 loc_5DC8 ;
LOAD:00005DC8 LDR R3, [SP,#0x30+var_10] ; R3 = 0x104B2
LOAD:00005DCA LDR R2, [SP,#0x30+var_24] ; R2 = i
LOAD:00005DCC LDR R1, [SP,#0x30+var_2C] ; R1 = UserName
LOAD:00005DCE ADD R3, PC ; 动调 : R3 = 0xA33E8284
LOAD:00005DD0 ADDS R3, #0x38 ; '8'
LOAD:00005DD2 LDRB R3, [R2,R3] ; 同一偏移取某地址字节数据
LOAD:00005DD4 EORS R3, R1 ; 两个地址同偏移数据异或
LOAD:00005DD6 LSLS R3, R3, #0x18 ; 这两句效果等效&0xFF
LOAD:00005DD8 LSRS R3, R3, #0x18
LOAD:00005DDA STR R3, [SP,#0x30+var_2C]
大概就是
var_2C = ((byte) UserName ^ (byte) Table) & 0xFF
最后进行次数的判断
LOAD:00005DDC loc_5DDC ;
LOAD:00005DDC LDR R2, [SP,#0x30+var_24] ; var_24 = i
LOAD:00005DDE MOVS R3, #0x100 ; R3 = 0x100
LOAD:00005DE2 ADDS R2, #1 ; i++
LOAD:00005DE4 STR R2, [SP,#0x30+var_24]
LOAD:00005DE6 CMP R2, R3 ; i < 0x100 //循环0x100次
LOAD:00005DE8 BNE loc_5DB8
每个字节一共是0x100
次,动态调试把整个表dump出来
1A B7 00 3A 19 B7 00 2A 20 00 9D E5 C7 97 00 AA
C6 97 00 BA 91 03 03 E0 C8 3D 00 1A C7 3D 00 0A
48 60 9D E5 03 C2 00 CA 02 C2 00 DA 3C 40 8D E5
B4 3F 00 2A B3 3F 00 3A E7 76 27 E2 F0 31 00 6A
EF 31 00 7A DA 2C 4C E2 AE A2 00 1A AD A2 00 0A
0A 6B 86 E2 04 C1 00 4A 03 C1 00 5A B4 20 9D E5
B2 18 00 AA B1 18 00 BA 06 30 8A E0 83 0C 00 0A
82 0C 00 1A 0A 80 88 E0 FA BE 00 3A F9 BE 00 2A
0C 10 21 E0 4A 9D 00 0A 49 9D 00 1A 21 5A 8F E2
DE 5E 85 E2 00 50 95 E5 63 84 00 AA 62 84 00 BA
D8 70 9D E5 E6 09 00 CA E5 09 00 DA 91 02 02 E0
88 6B 00 6A 87 6B 00 7A 02 20 86 E0 DE 10 00 2A
DD 10 00 3A 87 3C 83 E2 31 C2 00 9A 30 C2 00 8A
9C B0 9D E5 EE A4 00 9A ED A4 00 8A DD 06 00 1A
8C 8A 00 3A 8B 8A 00 2A 71 25 82 E2 F0 0C 00 6A
EF 0C 00 7A FA 19 21 E2 65 AF 00 6A 64 AF 00 7A
补充一点,这个表其实不是动态生成的,静态分析时就可以dump出来
因为异或的计算比较有意思,整个表循环异或一遍其实可以等效于异或一个值,这个值我们可以通过计算来确定,输入为0x00
,看输出是什么即可
在IDA里将这个表保存为文件,使用WinHex打开,拷贝存为C Source
unsigned AnsiChar data[256] = {
0x1A, 0xB7, 0x00, 0x3A, 0x19, 0xB7, 0x00, 0x2A, 0x20, 0x00, 0x9D, 0xE5, 0xC7, 0x97, 0x00, 0xAA,
0xC6, 0x97, 0x00, 0xBA, 0x91, 0x03, 0x03, 0xE0, 0xC8, 0x3D, 0x00, 0x1A, 0xC7, 0x3D, 0x00, 0x0A,
0x48, 0x60, 0x9D, 0xE5, 0x03, 0xC2, 0x00, 0xCA, 0x02, 0xC2, 0x00, 0xDA, 0x3C, 0x40, 0x8D, 0xE5,
0xB4, 0x3F, 0x00, 0x2A, 0xB3, 0x3F, 0x00, 0x3A, 0xE7, 0x76, 0x27, 0xE2, 0xF0, 0x31, 0x00, 0x6A,
0xEF, 0x31, 0x00, 0x7A, 0xDA, 0x2C, 0x4C, 0xE2, 0xAE, 0xA2, 0x00, 0x1A, 0xAD, 0xA2, 0x00, 0x0A,
0x0A, 0x6B, 0x86, 0xE2, 0x04, 0xC1, 0x00, 0x4A, 0x03, 0xC1, 0x00, 0x5A, 0xB4, 0x20, 0x9D, 0xE5,
0xB2, 0x18, 0x00, 0xAA, 0xB1, 0x18, 0x00, 0xBA, 0x06, 0x30, 0x8A, 0xE0, 0x83, 0x0C, 0x00, 0x0A,
0x82, 0x0C, 0x00, 0x1A, 0x0A, 0x80, 0x88, 0xE0, 0xFA, 0xBE, 0x00, 0x3A, 0xF9, 0xBE, 0x00, 0x2A,
0x0C, 0x10, 0x21, 0xE0, 0x4A, 0x9D, 0x00, 0x0A, 0x49, 0x9D, 0x00, 0x1A, 0x21, 0x5A, 0x8F, 0xE2,
0xDE, 0x5E, 0x85, 0xE2, 0x00, 0x50, 0x95, 0xE5, 0x63, 0x84, 0x00, 0xAA, 0x62, 0x84, 0x00, 0xBA,
0xD8, 0x70, 0x9D, 0xE5, 0xE6, 0x09, 0x00, 0xCA, 0xE5, 0x09, 0x00, 0xDA, 0x91, 0x02, 0x02, 0xE0,
0x88, 0x6B, 0x00, 0x6A, 0x87, 0x6B, 0x00, 0x7A, 0x02, 0x20, 0x86, 0xE0, 0xDE, 0x10, 0x00, 0x2A,
0xDD, 0x10, 0x00, 0x3A, 0x87, 0x3C, 0x83, 0xE2, 0x31, 0xC2, 0x00, 0x9A, 0x30, 0xC2, 0x00, 0x8A,
0x9C, 0xB0, 0x9D, 0xE5, 0xEE, 0xA4, 0x00, 0x9A, 0xED, 0xA4, 0x00, 0x8A, 0xDD, 0x06, 0x00, 0x1A,
0x8C, 0x8A, 0x00, 0x3A, 0x8B, 0x8A, 0x00, 0x2A, 0x71, 0x25, 0x82, 0xE2, 0xF0, 0x0C, 0x00, 0x6A,
0xEF, 0x0C, 0x00, 0x7A, 0xFA, 0x19, 0x21, 0xE2, 0x65, 0xAF, 0x00, 0x6A, 0x64, 0xAF, 0x00, 0x7A
};
写个程序跑一下
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
unsigned char xor_table[256] = {
0x1A, 0xB7, 0x00, 0x3A, 0x19, 0xB7, 0x00, 0x2A, 0x20, 0x00, 0x9D, 0xE5, 0xC7, 0x97, 0x00, 0xAA,
0xC6, 0x97, 0x00, 0xBA, 0x91, 0x03, 0x03, 0xE0, 0xC8, 0x3D, 0x00, 0x1A, 0xC7, 0x3D, 0x00, 0x0A,
0x48, 0x60, 0x9D, 0xE5, 0x03, 0xC2, 0x00, 0xCA, 0x02, 0xC2, 0x00, 0xDA, 0x3C, 0x40, 0x8D, 0xE5,
0xB4, 0x3F, 0x00, 0x2A, 0xB3, 0x3F, 0x00, 0x3A, 0xE7, 0x76, 0x27, 0xE2, 0xF0, 0x31, 0x00, 0x6A,
0xEF, 0x31, 0x00, 0x7A, 0xDA, 0x2C, 0x4C, 0xE2, 0xAE, 0xA2, 0x00, 0x1A, 0xAD, 0xA2, 0x00, 0x0A,
0x0A, 0x6B, 0x86, 0xE2, 0x04, 0xC1, 0x00, 0x4A, 0x03, 0xC1, 0x00, 0x5A, 0xB4, 0x20, 0x9D, 0xE5,
0xB2, 0x18, 0x00, 0xAA, 0xB1, 0x18, 0x00, 0xBA, 0x06, 0x30, 0x8A, 0xE0, 0x83, 0x0C, 0x00, 0x0A,
0x82, 0x0C, 0x00, 0x1A, 0x0A, 0x80, 0x88, 0xE0, 0xFA, 0xBE, 0x00, 0x3A, 0xF9, 0xBE, 0x00, 0x2A,
0x0C, 0x10, 0x21, 0xE0, 0x4A, 0x9D, 0x00, 0x0A, 0x49, 0x9D, 0x00, 0x1A, 0x21, 0x5A, 0x8F, 0xE2,
0xDE, 0x5E, 0x85, 0xE2, 0x00, 0x50, 0x95, 0xE5, 0x63, 0x84, 0x00, 0xAA, 0x62, 0x84, 0x00, 0xBA,
0xD8, 0x70, 0x9D, 0xE5, 0xE6, 0x09, 0x00, 0xCA, 0xE5, 0x09, 0x00, 0xDA, 0x91, 0x02, 0x02, 0xE0,
0x88, 0x6B, 0x00, 0x6A, 0x87, 0x6B, 0x00, 0x7A, 0x02, 0x20, 0x86, 0xE0, 0xDE, 0x10, 0x00, 0x2A,
0xDD, 0x10, 0x00, 0x3A, 0x87, 0x3C, 0x83, 0xE2, 0x31, 0xC2, 0x00, 0x9A, 0x30, 0xC2, 0x00, 0x8A,
0x9C, 0xB0, 0x9D, 0xE5, 0xEE, 0xA4, 0x00, 0x9A, 0xED, 0xA4, 0x00, 0x8A, 0xDD, 0x06, 0x00, 0x1A,
0x8C, 0x8A, 0x00, 0x3A, 0x8B, 0x8A, 0x00, 0x2A, 0x71, 0x25, 0x82, 0xE2, 0xF0, 0x0C, 0x00, 0x6A,
0xEF, 0x0C, 0x00, 0x7A, 0xFA, 0x19, 0x21, 0xE2, 0x65, 0xAF, 0x00, 0x6A, 0x64, 0xAF, 0x00, 0x7A
};
int main()
{
unsigned char test =0x00;
for (int i = 0; i < 256; i++)
{
test ^= xor_table;
}
printf("0x%x\n", test);
return 0;
}
可以看到整个异或表的异或效果和单独异或0x93
的效果是一样的
计算完后会判断计算后的数据是否为0
LOAD:00005DFE loc_5DFE ;
LOAD:00005DFE LDR R1, [SP,#0x30+var_1C] ; R1 = 异或后的数据
LOAD:00005E00 CMP R1, #0 ; 判断异或后的数据是否为0
LOAD:00005E02 BNE loc_5E1E
如果是0,则会改为0x99
LOAD:00005E04 LDR R0, =(dword_16AF8 - 0x5E0A)
LOAD:00005E06 ADD R0, PC ; dword_16AF8
LOAD:00005E08 ADDS R0, #0x30 ; '0' ; env
LOAD:00005E0A BLX setjmp_0
LOAD:00005E0E MOVS R2, #0x99 ; '
LOAD:00005E10 STR R2, [SP,#0x30+var_2C] ; 如果计算后的数据为0,则改为0x99
LOAD:00005E12 CMP R0, #0
LOAD:00005E14 BEQ loc_5E1E
每个字节计算完成存储到栈中,一共八次
LOAD:00005E1E loc_5E1E ;
LOAD:00005E1E LDR R2, [SP,#0x30+var_28] ; R2 = i
LOAD:00005E20 LDR R1, [SP,#0x30+var_20] ; R1 = 结构体基址
LOAD:00005E22 ADDS R3, R1, R2
LOAD:00005E24 LDR R2, [SP,#0x30+var_28]
LOAD:00005E26 MOV R1, SP
LOAD:00005E28 LDRB R1, [R1,#0x30+var_2C]
LOAD:00005E2A ADDS R3, #0x5A ; 'Z' ; 0x5A为计算后数据存储偏移
LOAD:00005E2C ADDS R2, #1
LOAD:00005E2E STRB R1, [R3] ; 将计算后的值存储到栈中
LOAD:00005E30 STR R2, [SP,#0x30+var_28]
LOAD:00005E32 CMP R2, #8 ; 计算8字节,那么取的就是用户名前8位
LOAD:00005E34 BNE loc_5D8E
最终我们可以看到生成的8字节数据
在上图的位置下个断点,数据区跟随R3
,可以看到完整的生成过程
第三个函数,就一个小循环,应该比较简单
后来分析下来是我错了,它不简单,参数之类的预处理
LOAD:000060A0 PUSH {R4-R6,LR}
LOAD:000060A2 LDR R3, =(off_15E9C - 0x60AC)
LOAD:000060A4 SUB SP, SP, #0x18
LOAD:000060A6 STR R0, [SP,#0x28+var_24] ; var_24 = 结构体基址
LOAD:000060A8 ADD R3, PC ; off_15E9C
LOAD:000060AA LDR R3, [R3] ; __stack_chk_guard
LOAD:000060AC ADD R0, SP, #0x28+s ; s
LOAD:000060AE MOVS R1, #0 ; R1 = 0
LOAD:000060B0 LDR R3, [R3]
LOAD:000060B2 MOVS R2, #0xA ; R2 = 0x0A
LOAD:000060B4 STR R3, [SP,#0x28+var_14] ; 栈保护
LOAD:000060B6 BLX memset ; memset(s, 0, 0x0A)
LOAD:000060BA LDR R0, =(dword_16AF8 - 0x60C0)
LOAD:000060BC ADD R0, PC ; dword_16AF8
LOAD:000060BE ADDS R0, #0x30 ; '0' ; env
LOAD:000060C0 BLX setjmp_0
LOAD:000060C4 SUBS R4, R0, #0 ; R4 = 0 //影响了标志位,这里用于判断函数的执行
LOAD:000060C6 BEQ loc_60CE
调用了一个函数,这个函数可复杂了
LOAD:000060CE loc_60CE ;
LOAD:000060CE LDR R0, [SP,#0x28+var_24] ; R0 = 结构体基址
LOAD:000060D0 ADDS R0, #0x5A ; 'Z' ; 取出计算后的8字节数据
LOAD:000060D2 BL sub_57D4
参数是计算后的8字节数据,里面有五个函数的调用,继续一个个跟
开始做参数的存储,重定位了一个Table
LOAD:000057D4 PUSH {R0-R2,R4-R7,LR}
LOAD:000057D6 LDR R4, =(dword_165F8 - 0x57E0)
LOAD:000057D8 MOVS R1, R0 ; R1 = R0 = 计算后的8字节数据
LOAD:000057DA MOVS R2, #0x40 ; '@' ; R2 = 0x40
LOAD:000057DC ADD R4, PC ; dword_165F8
这个Table在动态调试的过程中是有值的
但是在静态分析的时候是空的,这里有一个0x30
的偏移
接下来以为我会继续分析下去吗?
不,其实这里是初始化秘钥的地方,我还分析下去,神经病啊
我看到了后面一层又一层的,而且明显的跟其它数据分开了
我发现不对劲,而且这么多计算我都看不懂,于是开启猜测模式
这里应该是某加密,前面那个8字节应该是秘钥,然后后面的函数一个个看,看看有没有什么Table,现代加密算法一般都有各种Table去做计算
运气不错,发现了DES加密算法的S盒,要是不知道S盒是啥的。。。。。。
它是八个二维数组,规格就是8 * 4 * 16
可以自行对比一下,当然也可以靠其它Table的特征
当然AES也有S盒,但是这两者的S盒是有很多区别的,比如AES的S盒如下
unsigned char sBox[] =
{ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, /*0*/
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, /*1*/
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, /*2*/
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, /*3*/
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, /*4*/
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, /*5*/
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, /*6*/
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, /*7*/
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, /*8*/
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, /*9*/
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, /*a*/
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, /*b*/
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, /*c*/
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, /*d*/
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, /*e*/
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 /*f*/
};
首先是规模不一样,其实是数量不一样,输入输出的值也都不一样
那么这里可以确定是DES加密算法,但是它是加密还是解密就需要再考量一下了
先放着,我们接着看代码,在初始化完秘钥后,开始给两个数组进行初始化操作
LOAD:000060D6 LDR R2, [SP,#0x28+var_24] ; R2 = 结构体基址
LOAD:000060D8 LDR R0, [R2,#0x38] ; R0 = s1 //结构体偏移0x38,为注册码
LOAD:000060DA BLX strlen ; R0 = strlen(s)
LOAD:000060DE LDR R5, [SP,#0x28+var_24] ; R5 = 结构体基址
LOAD:000060E0 LSRS R6, R0, #4 ; R6 = strlen(s) >> 4 //长度一定是非负,等效于除16
LOAD:000060E2 MOVS R1, R4 ; R1 = R4 = 0
LOAD:000060E4 ADDS R5, #0x3C ; '<' ; R5 = s2 //结构体偏移0x3C
LOAD:000060E6 MOVS R0, R5 ; R0 = R5 = s2
LOAD:000060E8 MOVS R2, #0x1E ; R2 = 0x1E
LOAD:000060EA BLX memset ; memset(s2, 0, 0x1E)
LOAD:000060EE B loc_6108
顺带把注册码分为16字节每组,每组进行循环解密
LOAD:00006108 loc_6108
LOAD:00006108 CMP R4, R6
LOAD:0000610A BLT loc
解密后的数据存储到s2
,结构体偏移0x3C
LOAD:000060F0 loc_60F0 ;
LOAD:000060F0 LDR R2, [SP,#0x28+var_24] ; R2 = 结构体基址
LOAD:000060F2 LSLS R3, R4, #4 ; i << 4 //此处用于注册码偏移的跳转
LOAD:000060F4 ADD R0, SP, #0x28+s ; R0 = s = buffer
LOAD:000060F6 LDR R1, [R2,#0x38] ; R1 = s1 = 注册码
LOAD:000060F8 ADDS R4, #1
LOAD:000060FA ADDS R1, R1, R3
LOAD:000060FC BL sub_58F8 ; 此处入口对数据进行解密
LOAD:00006100 MOVS R0, R5 ; dest
LOAD:00006102 ADD R1, SP, #0x28+s ; src
LOAD:00006104 BLX strcat_0 ; 解密后的数据存储到s2
最后第四个函数就是解密后的注册码和用户名进行对比,红色表示异常分支,蓝色表示正常循环,最后由两个灰色的代码块结束循环
那么,那么,那么
我们来计算一组有效的KEY
不过好像出了点问题,哪里不对的样子
因为在分析的时候我注意到了取了用户名前8位进行计算秘钥,而且后续使用了十六位进行分组解密
所以这里单纯的使用了一个八字节字符串当做用户名进行输入
竟然出错了。。。。。。
再次打个断点进行调试,看看解密后的数据是个啥
首先获取注册码
然后两组计算完后,得到解密后的数据
那这个就很尴尬了,怎么会多出八位
百撕不得姐,于是找老司机求教
发现用Java的加解密库计算出来的数据并不正确,其实可能是校验的过程改了
正常情况下解密出来的数据应该是这样的
而我上面那个是个啥玩意。。。。。。
搞得我很尴尬啊。。。。。。
既然这样,那我就不客气了,去网上找DES的C代码实现
随意找了个代码,看到了S盒,想起刚才也是S盒,会不会S盒动了手脚,于是对比了一波S盒
首先把正常DES算法的S盒准备好
static char S_Box[8][4][16] = {
// S1
14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13,
// S2
15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9,
// S3
10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12,
// S4
7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14,
// S5
2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3,
// S6
12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13,
// S7
4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12,
// S8
13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
};
然后将动态调试时的S盒dump出来
跟前面拷贝出xor_table
一样,使用保存为文件,然后WinHex转为C Source
unsigned AnsiChar data[512] = {
0x0E, 0x04, 0x0D, 0x01, 0x02, 0x0F, 0x0B, 0x08, 0x03, 0x0A, 0x06, 0x0C, 0x05, 0x09, 0x00, 0x07,
0x00, 0x0F, 0x07, 0x04, 0x0E, 0x02, 0x0D, 0x01, 0x0A, 0x06, 0x0C, 0x0B, 0x09, 0x05, 0x03, 0x08,
0x04, 0x01, 0x0E, 0x08, 0x0D, 0x06, 0x02, 0x0B, 0x0F, 0x0C, 0x09, 0x07, 0x03, 0x0A, 0x05, 0x00,
0x0F, 0x0C, 0x08, 0x02, 0x04, 0x09, 0x01, 0x07, 0x05, 0x0B, 0x03, 0x0E, 0x0A, 0x00, 0x06, 0x0D,
0x0F, 0x01, 0x08, 0x0E, 0x06, 0x0B, 0x03, 0x04, 0x09, 0x07, 0x02, 0x0D, 0x0C, 0x00, 0x05, 0x0A,
0x03, 0x0D, 0x04, 0x07, 0x0F, 0x02, 0x08, 0x0E, 0x0C, 0x00, 0x01, 0x0A, 0x06, 0x09, 0x0B, 0x05,
0x00, 0x0E, 0x07, 0x0B, 0x0A, 0x04, 0x0D, 0x01, 0x05, 0x08, 0x0C, 0x06, 0x09, 0x03, 0x02, 0x0F,
0x0D, 0x08, 0x0A, 0x01, 0x03, 0x0F, 0x04, 0x02, 0x0B, 0x06, 0x07, 0x0C, 0x00, 0x05, 0x0E, 0x09,
0x0A, 0x00, 0x09, 0x0E, 0x06, 0x03, 0x0F, 0x05, 0x01, 0x0D, 0x0C, 0x07, 0x0B, 0x04, 0x02, 0x08,
0x0D, 0x07, 0x00, 0x09, 0x03, 0x04, 0x06, 0x0A, 0x02, 0x08, 0x05, 0x0E, 0x0C, 0x0B, 0x0F, 0x01,
0x0D, 0x06, 0x04, 0x09, 0x08, 0x0F, 0x03, 0x00, 0x0B, 0x01, 0x02, 0x0C, 0x05, 0x0A, 0x0E, 0x07,
0x01, 0x0A, 0x0D, 0x00, 0x06, 0x09, 0x08, 0x07, 0x04, 0x0F, 0x0E, 0x03, 0x0B, 0x05, 0x02, 0x0C,
0x07, 0x0D, 0x0E, 0x03, 0x00, 0x06, 0x09, 0x0A, 0x01, 0x02, 0x08, 0x05, 0x0B, 0x0C, 0x04, 0x0F,
0x0D, 0x08, 0x0B, 0x05, 0x06, 0x0F, 0x00, 0x03, 0x04, 0x07, 0x02, 0x0C, 0x01, 0x0A, 0x0E, 0x09,
0x0A, 0x06, 0x09, 0x00, 0x0C, 0x0B, 0x07, 0x0D, 0x0F, 0x01, 0x03, 0x0E, 0x05, 0x02, 0x08, 0x04,
0x03, 0x0F, 0x00, 0x06, 0x0A, 0x01, 0x0D, 0x08, 0x09, 0x04, 0x05, 0x0B, 0x0C, 0x07, 0x02, 0x0E,
0x02, 0x0C, 0x04, 0x01, 0x07, 0x0A, 0x0B, 0x06, 0x08, 0x05, 0x03, 0x0F, 0x0D, 0x00, 0x0E, 0x09,
0x0E, 0x0B, 0x02, 0x0C, 0x04, 0x07, 0x0D, 0x01, 0x05, 0x00, 0x0F, 0x0A, 0x03, 0x09, 0x08, 0x06,
0x04, 0x02, 0x01, 0x0B, 0x0A, 0x0D, 0x07, 0x08, 0x0F, 0x09, 0x0C, 0x05, 0x06, 0x03, 0x00, 0x0E,
0x0B, 0x08, 0x0C, 0x07, 0x01, 0x0E, 0x02, 0x0D, 0x06, 0x0F, 0x00, 0x09, 0x0A, 0x04, 0x05, 0x03,
0x0C, 0x01, 0x0A, 0x0F, 0x09, 0x02, 0x06, 0x08, 0x00, 0x0D, 0x03, 0x04, 0x0E, 0x07, 0x05, 0x0B,
0x0A, 0x0F, 0x04, 0x02, 0x07, 0x0C, 0x00, 0x05, 0x06, 0x01, 0x0D, 0x0E, 0x00, 0x0B, 0x03, 0x08,
0x09, 0x0E, 0x0F, 0x05, 0x02, 0x08, 0x0C, 0x03, 0x07, 0x00, 0x04, 0x0A, 0x01, 0x0D, 0x0B, 0x06,
0x04, 0x03, 0x02, 0x0C, 0x09, 0x05, 0x0F, 0x0A, 0x0B, 0x0E, 0x01, 0x07, 0x06, 0x00, 0x08, 0x0D,
0x04, 0x0B, 0x02, 0x0E, 0x0F, 0x00, 0x08, 0x0D, 0x03, 0x0C, 0x09, 0x07, 0x05, 0x0A, 0x06, 0x01,
0x0D, 0x00, 0x0B, 0x07, 0x04, 0x00, 0x01, 0x0A, 0x0E, 0x03, 0x05, 0x0C, 0x02, 0x0F, 0x08, 0x06,
0x01, 0x04, 0x0B, 0x0D, 0x0C, 0x03, 0x07, 0x0E, 0x0A, 0x0F, 0x06, 0x08, 0x00, 0x05, 0x09, 0x02,
0x06, 0x0B, 0x0D, 0x08, 0x01, 0x04, 0x0A, 0x07, 0x09, 0x05, 0x00, 0x0F, 0x0E, 0x02, 0x03, 0x0C,
0x0D, 0x02, 0x08, 0x04, 0x06, 0x0F, 0x0B, 0x01, 0x0A, 0x09, 0x03, 0x0E, 0x05, 0x00, 0x0C, 0x07,
0x01, 0x0F, 0x0D, 0x08, 0x0A, 0x03, 0x07, 0x04, 0x0C, 0x05, 0x06, 0x0B, 0x00, 0x0E, 0x09, 0x02,
0x07, 0x0B, 0x04, 0x01, 0x09, 0x0C, 0x0E, 0x02, 0x00, 0x06, 0x0A, 0x0D, 0x0F, 0x03, 0x05, 0x08,
0x02, 0x01, 0x0E, 0x07, 0x04, 0x0A, 0x08, 0x0D, 0x0F, 0x0C, 0x09, 0x00, 0x03, 0x05, 0x06, 0x0B
};
然后跟上面正常的S盒进行循环对比,找到不同的地方
还真的有两处不一样,出题的你良心不会痛吗?
那看来这里是需要自己实现加密代码了。。。。。。
好在以前存了不少代码,小书包翻啊翻,把Java实现的代码翻了出来
不过测试的时候发现各种问题,弄的很尴尬。。。。。。
所以还是老实找C语言实现的代码
接下来就开始扎心了。。。。。。
找了个C实现的DES算法代码,发现结果不对
想了想,如果S盒有问题,那么其它几个Table和盒子可能也有问题,于是开始对比了一波,最后发现PC2_Table有问题
再一次的计算,发现注册码计算还是有问题,当时场面一度很尴尬。。。。。。
突然,我想起了一件事,秘钥开始的时候经过了一次神奇的异或
这尼玛。。。。。。
赶紧的赶紧的,继续改代码
东平西凑,瞎改瞎改
就先这样吧,眼泪掉下来,以后再找个时间分析一个这个样本的保护技术
最后,如果是第一次接触这种动静结合分析的同学,要时刻注意指令集的切换,中间有大量的指令集切换,看指令的地址即可,通常都是三步走,断在调用处,先别跟过去,此时跟过去会断不下来的,直接效果就是和F9一样,这一点应该有体会吧,比如使用的是BL R3
,在这一句下个断点,先断下来,直接在反汇编窗口跟随R3,就可以看到要执行的代码了,但是如果指令集识别有问题,需要先ALT + G
,选择Thumb模式,然后按一下C转为代码模式,再按P识别函数