CrackMe[简单]-第一次做算法逆向

selph
selph
发布于 2021-04-21 / 516 阅读
0
0

CrackMe[简单]-第一次做算法逆向

学习环境:Windows 10 20H2 + Visual Studio 2019 + IDA

参考书籍:《C++反汇编与逆向分析技术揭秘》Chapter 4.5

程序介绍

控制台程序,通过参数输入密码,会打印验证结果

为了减少分析工作量,程序中没有任何错误检查,只有加密与密码检查

目标是还原加密算法

image-20210419174346024

逆向分析

跟着书上的讲解进行分析,书上分为了4个部分,这里也分为4个部分来记录

使用IDA打开,直接就跳转到了main函数:

.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main           proc near               ; CODE XREF: start+AF↓p
.text:00401000
.text:00401000 var_10          = byte ptr -10h
.text:00401000 var_F           = byte ptr -0Fh
.text:00401000 var_E           = byte ptr -0Eh
.text:00401000 var_D           = byte ptr -0Dh
.text:00401000 var_C           = byte ptr -0Ch
.text:00401000 var_B           = byte ptr -0Bh
.text:00401000 var_A           = byte ptr -0Ah
.text:00401000 var_9           = byte ptr -9
.text:00401000 var_8           = byte ptr -8
.text:00401000 var_7           = byte ptr -7
.text:00401000 var_6           = byte ptr -6
.text:00401000 var_5           = byte ptr -5
.text:00401000 var_4           = byte ptr -4
.text:00401000 var_3           = byte ptr -3
.text:00401000 argc            = dword ptr  4
.text:00401000 argv            = dword ptr  8
.text:00401000 envp            = dword ptr  0Ch

刚开始这里是定义的局部变量和参数,在IDA中,值>0是参数,值<0是局部变量

这里var_10~var_3都是1个字节大小的连续局部变量,可以将其看做数组

双击var_10,进入var_10地址里,按*快捷键,将其转换为14字节的数组,并改名位charNumber14:

image-20210419180442348

接下来开始看代码:

text:00401000 charNumber14    = byte ptr -10h
.text:00401000 argc            = dword ptr  4
.text:00401000 argv            = dword ptr  8
.text:00401000 envp            = dword ptr  0Ch
.text:00401000
.text:00401000                 sub     esp, 10h        ; main参数:argc-命令个数, argv-命令行信息, envp-环境变量信息
.text:00401003                 mov     ecx, [esp+10h+argv] ; 取出来argv数据放到ecx
.text:00401007                 mov     al, 1
.text:00401009                 mov     [esp+10h+charNumber14+6], al ; charNumber[6] = 1
.text:0040100D                 mov     [esp+10h+charNumber14+7], al ; charNumber[7] = 1
.text:00401011                 mov     edx, [ecx+4]    ; edx = argv[1]
.text:00401014                 mov     [esp+10h+charNumber14+0Ah], al ; charNumber[10] = 1
.text:00401018                 mov     al, byte ptr [esp+10h+argc] ; al = 参数个数
.text:0040101C                 push    ebx             ; 压入 ebx
.text:0040101D                 mov     bl, al          ; bl = al
.text:0040101F                 push    esi             ; 压入 esi
.text:00401020                 dec     bl              ; bl = bl -1
.text:00401022                 push    edi             ; 压入 edi
.text:00401023                 or      [edx], bl       ; argv[1][0] |= argc - 1
.text:00401025                 mov     edx, [ecx+4]    ; edx = argv[1]
.text:00401028                 mov     [esp+1Ch+charNumber14], 77h ; charNumber[0] = 0x77
.text:0040102D                 mov     [esp+1Ch+charNumber14+1], 76h ; charNumber[1] = 0x76
.text:00401032                 xor     [edx+1], bl     ; argv[1][1] ^= argc - 1
.text:00401035                 mov     dl, 6           ; dl = 6
.text:00401037                 imul    dl              ; eax = eax*6
.text:00401039                 mov     esi, [ecx+4]    ; ecx = argv[1]
.text:0040103C                 sub     al, dl          ; al = al - 6
.text:0040103E                 mov     [esp+1Ch+charNumber14+2], 0CAh ; charNumber[2] = 0xCA
.text:00401043                 mov     [esp+1Ch+charNumber14+3], 0F3h ; charNumber[3] = 0xF3
.text:00401048                 imul    byte ptr [esi+2] ; al = argv[1][2] * (argc-1) * 6
.text:0040104B                 mov     [esi+2], al     ; argv[1][2] = argv[1][2] * (argc-1) * 6
.text:0040104E                 mov     esi, [ecx+4]    ; esi = argv[1]
.text:00401051                 mov     [esp+1Ch+charNumber14+4], 0A8h ; charNumber[4] = 0xA8
.text:00401056                 mov     [esp+1Ch+charNumber14+5], 0Ch ; charNumber[5] = 0xC
.text:0040105B                 movsx   eax, byte ptr [esi+2] ; eax = argv[1][2]
.text:0040105F                 cdq                     ; 扩展edx高位
.text:00401060                 and     edx, 3          ; edx &= 3
.text:00401063                 mov     [esp+1Ch+charNumber14+8], 0FEh ; charNumber[8] = 0xFE
.text:00401068                 add     eax, edx        ; eax = eax + edx
.text:0040106A                 mov     [esp+1Ch+charNumber14+9], 0DBh ; charNumber[9] = 0xDB
.text:0040106F                 sar     eax, 2          ; eax = argv[1][2] / 4
.text:00401072                 mov     [esi+3], al     ; argv[1][3] = al
.text:00401075                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:00401078                 mov     [esp+1Ch+charNumber14+0Bh], 0E0h ; charNumber[B] = 0xE0
.text:0040107D                 mov     [esp+1Ch+charNumber14+0Ch], 0FBh ; charNumber[C] = 0xFB
.text:00401082                 mov     dl, [eax+4]     ; dl = argv[1][4]
.text:00401085                 mov     [esp+1Ch+charNumber14+0Dh], 0 ; charNumber[D] = 0

这里进行了一些计算操作,然后给数组进行了赋值,为啥这两件事是交叉着执行的呢?因为编译器对目标机器代码进行了流水线优化,不能连着用相同的资源

数组charNumber的数据依次为:0x77,0x76,0xCA,0xF3,0xA8,0xC,0x01,0x01,0xFE,0xDB,0x01,0xE0,0xFB,0x00

通过一些计算给argv[1]参数进行了赋值:

argv[1][0] |= argc - 1;
argv[1][1] ^= argc - 1;
argv[1][2] *= (argc-1) * 6;
argv[1][3] = argv[1][2] / 4;

接着往下看代码:

.text:0040108A                 shl     dl, 3           ; argv[1][4] << 3
.text:0040108D                 mov     [eax+4], dl     ; argv[1][4] = argv[1][4] << 3
.text:00401090                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:00401093                 mov     dl, [eax+5]     ; dl = argv[1][5]
.text:00401096                 sar     dl, 2           ; argv[1][5] >> 2
.text:00401099                 mov     [eax+5], dl     ; argv[1][5] = argv[1][5] >> 2
.text:0040109C                 mov     esi, [ecx+4]    ; esi = argv[1]
.text:0040109F                 mov     al, bl          ; al = argc - 1
.text:004010A1                 mov     dl, [esi+6]     ; dl = argv[1][6]
.text:004010A4                 and     al, 7           ; al = (argc - 1) & 7
.text:004010A6                 and     dl, al          ; dl &= ((argc - 1)&7)
.text:004010A8                 mov     [esi+6], dl     ; argv[1][6] &= ((argc - 1)&7)
.text:004010AB                 mov     esi, [ecx+4]    ; esi = argv[1]
.text:004010AE                 movsx   edx, byte ptr [esi+7] ; edx = argv[1][7]
.text:004010B2                 and     edx, 80000001h  ; edx &= 0x80000001, 保留最高位和最低位
.text:004010B8                 jns     short loc_4010BF ; SF=0时跳转, 不为负数时跳转, 以下是负数的处理
.text:004010BA                 dec     edx             ; dex -= 1, 结果必然是 0x80000000 或 0x7fffffff
.text:004010BB                 or      edx, 0FFFFFFFEh ; 结果是 0xFFFFFFFE 或 0xFFFFFFFF
.text:004010BE                 inc     edx             ; 0xFFFFFFFF 或 0 这套运算等价于edx = edx%2
.text:004010BF
.text:004010BF loc_4010BF:                             ; CODE XREF: _main+B8↑j
.text:004010BF                 mov     [esi+7], dl     ; argv[1][7] = argv[1][7] % 2
.text:004010C2                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:004010C5                 not     bl              ; 取反(argc - 1)
.text:004010C7                 mov     [eax+8], bl     ; argv[1][8] = ~(argc - 1)
.text:004010CA                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:004010CD                 mov     dl, [eax]       ; dl = argv[1][0]
.text:004010CF                 mov     bl, [eax+2]     ; bl = argv[1][2]
.text:004010D2                 lea     esi, [eax+9]    ; esi = &argv[1][9] , 看成指针 char *pArgv9 = argv[1][9]
.text:004010D5                 sub     dl, bl          ; dl = dl - bl = argv[1][0] - argv[1][2]
.text:004010D7                 mov     bl, [esi]       ; bl = *pArgv9
.text:004010D9                 add     bl, dl          ; bl = *pArgv9 + argv[1][0] - argv[1][2]
.text:004010DB                 mov     [esi], bl       ; *pArgv9 = *pArgv9 + argv[1][0] - argv[1][2]
.text:004010DD                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:004010E0                 inc     esi             ; esi = esi + 1 = argv[1][10]的地址
.text:004010E1                 movsx   edi, byte ptr [eax+7] ; edi = argv[1][7]
.text:004010E5                 movsx   eax, byte ptr [eax+6] ; eax = argv[1][6]
.text:004010E9                 cdq                     ; 扩展高位
.text:004010EA                 idiv    edi             ; eax = argv[1][6] / argv[1][7]
.text:004010EC                 inc     esi             ; esi = esi + 1 = argv[1][11]的地址
.text:004010ED                 mov     [esi-1], al     ; *(pArgv9 - 1) = argv[1][10] = argv[1][6] / argv[1][7]
.text:004010F0                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:004010F3                 mov     dl, [eax+3]     ; dl = argv[1][3]
.text:004010F6                 mov     bl, [eax+1]     ; bl = argv[1][1]
.text:004010F9                 mov     al, [esi]       ; al = argv[1][11]
.text:004010FB                 sub     dl, bl          ; dl = argv[1][3] - argv[1][1]
.text:004010FD                 add     al, dl          ; al = argv[1][11] + argv[1][3] - argv[1][1]
.text:004010FF                 mov     [esi], al       ; argv[1][11] = argv[1][11] + argv[1][3] - argv[1][1]
.text:00401101                 mov     eax, [ecx+4]    ; eax = argv[1]
.text:00401104                 movsx   dx, byte ptr [eax+5] ; dx = argv[1][5]
.text:00401109                 movsx   ax, byte ptr [eax+4] ; ax = argv[1][4]
.text:0040110E                 imul    edx, eax        ; edx = argv[1][5] * argv[1][4]
.text:00401111                 mov     [esi], dx       ; *(short)pArgv9 = argv[1][4] * argv[1][5]
.text:00401114                 mov     ecx, [ecx+4]    ; ecx = argv[1]

通过一些计算给argv[1]参数进行了计算赋值:

argv[1][4] = argv[1][4] << 3;
argv[1][5] = argv[1][5] >> 2;
argv[1][6] = argv[1][6] & ((argc - 1) & 7);
argv[1][7] = argv[1][7] % 2;
argv[1][8] = ~(argc - 1);
char* pArgv9 = &argv[1][9];
*pArgv9 = *pArgv9 + argv[1][0] - argv[1][2];
pArgv9 += 1;
pArgv9 += 1;
*(pArgv9 - 1) = argv[1][6] / argv[1][7];
*pArgv9 += argv[1][3] - argv[1][1];
*(short*)pArgv9 = (short)argv[1][4] * (short)argv[1][5];

最后的判断代码:

.text:00401117                 lea     esi, [esp+1Ch+charNumber14] ; esi = charNumber14的首地址
.text:0040111B
.text:0040111B loc_40111B:                             ; CODE XREF: _main+13D↓j
.text:0040111B                 mov     dl, [ecx]       ; dl = argv[1][0]
.text:0040111D                 mov     bl, [esi]       ; bl = charNumber[0]
.text:0040111F                 mov     al, dl          ; al = argv[1][0]
.text:00401121                 cmp     dl, bl          ; dl和bl相比, 相同时ZF=1
.text:00401123                 jnz     short loc_401143 ; ZF=0时跳转,也就是不相同时跳转
.text:00401125                 test    al, al          ; 判断当前密码是否对比完成, 没完成就ZF=0
.text:00401127                 jz      short loc_40113F ; ZF = 1时跳转, 密码对比完成后跳转
.text:00401129                 mov     dl, [ecx+1]     ; dl = argv[1][1]
.text:0040112C                 mov     bl, [esi+1]     ; bl = charNumber[1]
.text:0040112F                 mov     al, dl          ; al = argv[1][1]
.text:00401131                 cmp     dl, bl          ; dl和bl相比, 相同时ZF=1
.text:00401133                 jnz     short loc_401143 ; ZF=0时跳转,也就是不相同时跳转
.text:00401135                 add     ecx, 2          ; 对比接下来2位
.text:00401138                 add     esi, 2
.text:0040113B                 test    al, al          ; 判断一下是否比对完密码
.text:0040113D                 jnz     short loc_40111B ; 没比对完就跳转回去继续比对
.text:0040113F
.text:0040113F loc_40113F:                             ; CODE XREF: _main+127↑j
.text:0040113F                 xor     eax, eax        ; eax清零
.text:00401141                 jmp     short loc_401148
.text:00401143 ; ---------------------------------------------------------------------------
.text:00401143
.text:00401143 loc_401143:                             ; CODE XREF: _main+123↑j
.text:00401143                                         ; _main+133↑j
.text:00401143                 sbb     eax, eax        ; eax = eax - eax - CF
.text:00401145                 sbb     eax, 0FFFFFFFFh ; eax = eax + 1 - CF
.text:00401148
.text:00401148 loc_401148:                             ; CODE XREF: _main+141↑j
.text:00401148                 pop     edi
.text:00401149                 pop     esi
.text:0040114A                 test    eax, eax        ; eax为0时, ZF=1
.text:0040114C                 pop     ebx
.text:0040114D                 jnz     short loc_401167 ; ZF = 0 时跳转
.text:0040114F                 push    offset asc_406044 ; "密码正确!"
.text:00401154                 push    offset aS       ; "%s \r\n"
.text:00401159                 call    _printf
.text:0040115E                 add     esp, 8
.text:00401161                 xor     eax, eax
.text:00401163                 add     esp, 10h
.text:00401166                 retn
.text:00401167 ; ---------------------------------------------------------------------------
.text:00401167
.text:00401167 loc_401167:                             ; CODE XREF: _main+14D↑j
.text:00401167                 push    offset asc_406030 ; "密码错误!"
.text:0040116C                 push    offset aS       ; "%s \r\n"
.text:00401171                 call    _printf
.text:00401176                 add     esp, 8
.text:00401179                 xor     eax, eax
.text:0040117B                 add     esp, 10h
.text:0040117E                 retn
.text:0040117E _main           endp

这里无意间分析了一波strcmp函数的内部实现....

判断代码如下

if(!strcmp(argv[1],charNumber14)){
    printf("密码正确");
}else{
    printf("密码错误");
}

代码还原

#include <stdio.h>
#include <string.h>
char charNumber14[14] = { 0x77,0x76,0xCA,0xF3,0xA8,0xC,0x01,0x01,0xFE,0xDB,0x01,0xE0,0xFB,0x0 };
int main(int argc, char* argv[], char* envp) {
	argv[1][0] |= argc - 1;
	argv[1][1] ^= argc - 1;
	argv[1][2] = argv[1][2] *(argc - 1) * 6;
	argv[1][3] = argv[1][2] / 4;
	argv[1][4] = argv[1][4] << 3;
	argv[1][5] = argv[1][5] >> 2;
	argv[1][6] = argv[1][6] & ((argc - 1) & 7);
	argv[1][7] = argv[1][7] % 2;
	argv[1][8] = ~(argc - 1);
	char* pArgv9 = &argv[1][9];
	*pArgv9 = *pArgv9 + argv[1][0] - argv[1][2];
	pArgv9 += 1;
	pArgv9 += 1;
	*(pArgv9 - 1) = argv[1][6] / argv[1][7];
	*pArgv9 += argv[1][3] - argv[1][1];
	*(short*)pArgv9 = (short)argv[1][4] * (short)argv[1][5];

	if (!strcmp(argv[1], charNumber14)) {
		printf("密码正确");
	}
	else {
		printf("密码错误");
	}
	return 0;
}

测试

大量运用位运算的算法不可逆,无法回推正确密码,这里的正确密码是题中已给出的:www.51asm.com,将我们的还原代码编译出来,输入这个正确密码来验证一下:

image-20210420182308720

上面那个是我们还原出来的代码编译出来的程序,运行结果为正确

下面那个是原Crackme程序,密码也是正确

总结

做这个大概花了我一下午的时间,这是我第一次逆向程序,这个Crackme虽然很简单,但因为不熟悉吧,做的时候还是有点费劲的

这个简单的CM里,有数组和指针的使用,以及库函数的调用,数组的赋值和计算都还好,到指针这里就有点不好弄了,首先得先判断出来是指针才行,不然做到后面2字节的赋值就搞不来了

emmm

不得不说,还挺好玩,想说的就这么多,下次再整一个哈哈哈哈


评论