0x00 序言
本学习系列(二)将分析并总结160个CM之中难度中等的题目.
本次分析的程序难度中偏下,好理解好掌握!
0x01 程序分析正文
拿到程序后先查壳:
可以看到,没有壳子,所以直接开始分析,本次我选择的是全程使用od动态调试.
然后我们先运行下程序看看是大概怎样的程序.
可以看到就是一个name/serial型的程序,不过成功的提示应该会在Status编辑框里显示,并不会有MessageBox提示,这一点应该也是为了防止下断
程序载入OD后先搜索一下字符串:
可以看到成功提示的字,跳过去看一下
可以发现非常明显的成功失败判断的跳转,在该关键跳转处下断后,可以发现程序是一直被断下的,说明他的验证函数是被循环执行的,可能是创建了一个时钟进行循环验证.
此外还能发现,serial输入纯数字才能触发验证判断,否则是不会在关键跳处断下,这说明很可能是验证函数中serial被作为纯数字来进行运算.
图中可看出,最终被判断的是eax是否为0x10,而eax的值来源于byte ptr [0x403166],这个地址很可能是一个global var.因此接下来的思路就是寻找是什么地方在修改这个变量
那么就在0x403166处下个内存断点看看:
以下是断下的三个位置:
由此我们可以看出程序中每次都是给这个变量+4,那么需要加到0x10还差一次,那来试试在程序中搜索(Ctrl+F)这条汇编命令,按Ctrl+L可以跳到下一个搜索结果
啊哈!果不其然,的确有第四条指令,而且这条上面有一个jnz的跳转,如果跳成功就是执行下面把0x403166置0的操作,否则就是执行+4操作
而jnz跳转上面的test eax,eax 其实就是判断eax是否为0,若为0则不跳转jnz,再往上看,是把另一个疑似global var的[0x403188]赋值给eax,然后eax+0x9112478
后面判断eax是否为零.
因此要想这里的第四条+4指令执行,则[0x403188]里的值必须是-0x9112478,在内存中,则是(0xFFFFFFFF-0x9112478+1)=0xF6EEDB88(好像是补码?大概)
因此现在我们要再次使用刚才的套路,内存下断看看[0x403188]在哪里被written,这里特别说一点就是这里的下断是下4个字节,因为上图中运算的长度是4字节.
最终断下了三个地址:
并且根据对该指针的监视我们可以知道,在程序走到0x401488时,[0x403188]的内容是一开始在serial输入的"1234"的hex格式0x000004D2,对上图代码下断走了一遍发现
00401361 . 8D3D 8C314000 lea edi, dword ptr [0x40318C]
[0x40318C]存的就是输入的name的四个字节,并且发现这部分的代码会被循环4次,每次[0x40318C]的内容会后移4字节.
也就是说这里可能就是核心算法,计算了name和serial得出结果存放到[0x403188],若结果是0xF6EEDB88,验证就会成功!
接下来分析下这里算法的具体步骤:
如上文所述:这部分的代码会被循环4次,每次[0x40318C]的内容会后移1字节
经过多次的单步运行,查看指针指向的内容后可知,总共会把name分成16部分,因此需循环16次,每部分4字节,name最大长度是16,超过的字符应该是不影响
如果name不足16字节,则不足的部分由0x00来代替.
接下来直接从jmp跳下面开始分析:
00401361 . 8D3D 8C314000 lea edi, dword ptr [0x40318C] ; 此处是把name的指针赋值给edi
00401367 . 0FBE05 683140>movsx eax, byte ptr [0x403168] ; 将[0x403168]内容赋值给eax,这里的内容是计次的变量
0040136E . 03F8 add edi, eax ; 根据计次变量移动edi指针,实现每次循环四个字节向右移一位
00401370 . FE05 68314000 inc byte ptr [0x403168] ; 计次变量+=1
00401376 . A1 88314000 mov eax, dword ptr [0x403188] ; 将[0x403188]的内容赋值给eax,这里初始值是输入的serial的int格式,例如输入"1234"则是0x000004D2 (HEX:D2 04 00 00)
0040137B . 8B25 A0314000 mov esp, dword ptr [0x4031A0] ; esp指向变化,这里应该是和他循环有关但是和算法内容无关,所以可以忽略
00401381 . 40 inc eax ; eax 中的内容自增1
00401382 . FF05 88314000 inc dword ptr [0x403188] ; [0x403188]的内容自增1,此时eax和[0x403188]内容相等
00401388 . 3307 xor eax, dword ptr [edi] ; 四个字节转成int后与eax异或,结果保存在eax
0040138A . A3 88314000 mov dword ptr [0x403188], eax ; 把eax的结果赋值给[0x403188]
0040138F . 803D 68314000>cmp byte ptr [0x403168], 0x10 ; 判断计次变量是否循环到16,若是则跳出循环
00401396 . 75 07 jnz short 0040139F ; 跳转,跳出循环
00401398 . 8005 66314000>add byte ptr [0x403166], 0x4 ; 3
经过以上循环后,如果[0x403188]的内容是0xF6EEDB88则验证成功,因此我们只要写出逆向的循环解密即可!
易语言代码如下:
serial_int = 4142848904' 此处是0xF6EEDB88
.计次循环首 (16, i)
name_i = 到字节集 (取文本中间 (name, 17 - i, 4)) '这里是取出4字节
name_i = name_i + 取空白字节集 (4 - 取字节集长度 (name_i))'这里是name不足则补齐0x00
name_site = 取字节集数据 (到字节集 (name_i), #长整数型, )'将四字节转成长整数,以便下面异或运算
serial_int = xor64 (serial_int, name_site)'这里用的异或非易语言原生异或,原生异或不支持4字节长整数
serial_int = serial_int - 1
.计次循环尾 ()
调试输出 (serial_int)
成功计算出正确Serial