2014 NAGA&PIOWIND APP应用攻防竞赛 Crackme01
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_53E4sub_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[i] = 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[i] = 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, #1sub_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[i]
LOAD:000054C8 1B 78 LDRB R3, [R3] ; R3 = PassDecoded[i]
LOAD:000054CA 9A 42 CMP R2, R3 ; 对比用户名和解码后的数据
LOAD:000054CC 09 D0 BEQ loc_54E2 ;
LOAD:000054CC ; 相等继续对比,i++不相等则异常退出

所以整个校验逻辑就是,输入用户名以及用户名的Base64编码作为密码即可,编码后的数据需要每3位插入一个-
长度也需要注意范围的校验,所以简单写个Java程序来计算即可,代码写的挫,不贴了
大概就是这样

Last updated