AVALON Reverse Engineering 一条汇编指令引发的蝴蝶效应 后续

一条汇编指令引发的蝴蝶效应 后续

环境配置

系统 : winxp 32bit
程序 : crackme1
要求 : 输入口令
使用工具 :ida pro & od

开始分析

静态分析

通过参考之前得文章,可知程序关键流程的地址,直接在ida中生成伪代码:

  memset(&v6, 0xCCu, 0xA4u);
  hWnd = GetDlgItem(hDlg, 1002);
  GetWindowTextA(hWnd, &String, 20);
  hWnd = GetDlgItem(hDlg, 1003);
  GetWindowTextA(hWnd, &v8, 20);
  if ( sub_40100A() )
  {
    v1 = sub_401028(&String);
    v2 = _itoa(v1, &v10, 10);
    result = strcmp(&v8, v2);
    if ( !result )
      result = MessageBoxA(hDlg, "Make a keygen ;-)", "lol", 0);
  }
  else
  {
    v4 = sub_40101E(&String);
    v5 = _itoa(v4, &v10, 10);
    result = strcmp(&v8, v5);
    if ( !result )
      result = SetWindowTextA(hWnd, "Now make a keygen!");
  }

这里有两种处理用户名字符的流程,根据sub_40100A的调用结果来决定。函数中有这样一段汇编代码:

00401628  |.  64:A1 1800000>mov     eax, dword ptr fs:[18]                      ;  找到TEB地址
0040162E  |.  3E:8B40 30    mov     eax, dword ptr [eax+30]                     ;  在TEB偏移30H处获得PEB地址
00401632  |.  3E:0FB640 02  movzx   eax, byte ptr [eax+2]                       ;  BeingDebugged标志

所以,这里实现了一段反调试功能,在调试中需要注意规避;我们再查看真正关键的代码逻辑:

int __cdecl sub_4011C0(char *str)
{
  int v1; // esi@6
  char v3; // [sp+Ch] [bp-50h]@1
  int len; // [sp+4Ch] [bp-10h]@1
  int tmp; // [sp+50h] [bp-Ch]@1
  int sum_value; // [sp+54h] [bp-8h]@1
  int index; // [sp+58h] [bp-4h]@1

  memset(&v3, 0xCCu, 0x50u);
  index = 0;
  sum_value = 0;
  tmp = 0;
  len = strlen(str);
  _strupr(str);
  for ( index = 0; index < len; ++index )
  {
    if ( str[index] != ' ' )
    {
      tmp = str[index];
      tmp *= 0x157A;
      sum_value += --tmp;
    }
  }
  v1 = 10 * sum_value;
  return v1 + sub_401005(sum_value);
}


程序遍历用户名字符串并且获得一个累加值,然后再处理累加值:

int __cdecl sub_4010E0(signed int input)
{
  double div_value; // st7@3
  char v3; // [sp+14h] [bp-58h]@1
  double INPUT; // [sp+54h] [bp-18h]@3
  int v5; // [sp+5Ch] [bp-10h]@3
  int res; // [sp+60h] [bp-Ch]@1
  int randoom; // [sp+64h] [bp-8h]@1
  int index; // [sp+68h] [bp-4h]@1

  memset(&v3, 0xCCu, 0x58u);
  randoom = 1;
  res = 0;
  for ( index = 10; index >= 0; --index )       // range(10,-1,-1)
  {
    INPUT = input;
    div_value = pow(10.0, index);
    v5 = (INPUT / div_value);
    if ( v5 > 0 )
      randoom = fake_random_value_401037(randoom);
    res += randoom * v5;
  }
  return res % 10;

fake_random_value_401037的内容很简单:

int __cdecl sub_401080(int a1)
{
  char v2; // [sp+Ch] [bp-44h]@1
  int v3; // [sp+4Ch] [bp-4h]@1

  memset(&v2, 0xCCu, 0x44u);
  v3 = 7;
  if ( a1 == 7 )
    v3 = 3;
  if ( a1 == 3 )
    v3 = 1;
  return v3;
}

至此,程序逻辑已经很清晰了,接下来用自带反调试的od在动态调试中确认一些信息。

动态调试

首先,直接查看关键反调试跳转:

00401506  |.  E8 FFFAFFFF   call crackme1.0040100A
0040150B  |.  85C0          test eax,eax
0040150D  |.  74 4D         je Xcrackme1.0040155C


这里可以借助插件或者手动绕开反调试,接下来需要查看关键函数的内容:

004010E0  /> \55            push ebp
004010E1  |.  8BEC          mov ebp,esp
004010E3  |.  83EC 58       sub esp,0x58
004010E6  |.  53            push ebx
004010E7  |.  56            push esi
004010E8  |.  57            push edi
004010E9  |.  8D7D A8       lea edi,[local.22]
004010EC  |.  B9 16000000   mov ecx,0x16
004010F1  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC
004010F6  |.  F3:AB         rep stos dword ptr es:[edi]
004010F8  |.  C745 FC 00000>mov [local.1],0x0
004010FF  |.  C745 F8 01000>mov [local.2],0x1
00401106  |.  C745 F4 00000>mov [local.3],0x0
0040110D  |.  C745 FC 0A000>mov [local.1],0xA
00401114  |.  EB 09         jmp Xcrackme1.0040111F
00401116  |>  8B45 FC       /mov eax,[local.1]
00401119  |.  83E8 01       |sub eax,0x1
0040111C  |.  8945 FC       |mov [local.1],eax
0040111F  |>  837D FC 00     cmp [local.1],0x0
00401123  |.  7C 4F         |jl Xcrackme1.00401174
00401125  |.  DB45 08       |fild [arg.1]
00401128  |.  DD5D E8       |fstp qword ptr ss:[ebp-0x18]
0040112B  |.  DB45 FC       |fild [local.1]
0040112E  |.  83EC 08       |sub esp,0x8
00401131  |.  DD1C24        |fstp qword ptr ss:[esp]
00401134  |.  68 00002440   |push 0x40240000
00401139  |.  6A 00         |push 0x0
0040113B  |.  E8 C91E0000   |call crackme1.00403009
00401140  |.  83C4 10       |add esp,0x10
00401143  |.  DC7D E8       |fdivr qword ptr ss:[ebp-0x18]
00401146  |.  E8 AD210000   |call crackme1.004032F8
0040114B  |.  8945 F0       |mov [local.4],eax
0040114E  |.  837D F0 00    |cmp [local.4],0x0
00401152  |.  7E 0F         |jle Xcrackme1.00401163
00401154  |.  8B4D F8       |mov ecx,[local.2]
00401157  |.  51            |push ecx
00401158  |.  E8 DAFEFFFF   |call crackme1.00401037
0040115D  |.  83C4 04       |add esp,0x4
00401160  |.  8945 F8       |mov [local.2],eax
00401163  |>  8B55 F0       |mov edx,[local.4]
00401166  |.  0FAF55 F8     |imul edx,[local.2]
0040116A  |.  8B45 F4       |mov eax,[local.3]
0040116D  |.  03C2          |add eax,edx
0040116F  |.  8945 F4       |mov [local.3],eax
00401172  |.^ EB A2         \jmp Xcrackme1.00401116
00401174  |>  8B45 F4       mov eax,[local.3]
00401177  |.  99            cdq
00401178  |.  B9 0A000000   mov ecx,0xA
0040117D  |.  F7F9          idiv ecx
0040117F  |.  8BC2          mov eax,edx
00401181  |.  5F            pop edi
00401182  |.  5E            pop esi
00401183  |.  5B            pop ebx
00401184  |.  83C4 58       add esp,0x58
00401187  |.  3BEC          cmp ebp,esp
00401189  |.  E8 32210000   call crackme1.004032C0
0040118E  |.  8BE5          mov esp,ebp
00401190  |.  5D            pop ebp
00401191  \.  C3            retn

虽然内容很多,但是ida中查看该函数之后,大致架构已经理清楚了。只需要查看最关键的代码段如何运行:

    div_value = pow(10.0, index);
    v5 = (INPUT / div_value);

只需要部署如下断点,不断观察:

Breakpoints
地址       模块       激活                       反汇编                                注释
00401140   crackme1   始终                         add esp,0x10
00401166   crackme1   始终                         imul edx,dword ptr ss:[ebp-0x8]

就不难理清程序的计算逻辑了。

编写代码

只需要照着程序抄一遍代码即可写出注册机:

def fake_random_value(a1):
  v3 = 7
  if ( a1 == 7 ):
    v3 = 3
  if ( a1 == 3 ):
    v3 = 1
  return v3

def get_samll_value(sum_value):
    random = 1
    res = 0

    for index in range(10,-1,-1):
        div_value = pow(10,index)
        v5 = sum_value / div_value
        if v5 > 0:
            random = fake_random_value(random)

        res = res + (random*v5)

    return res % 10

def fun_4011C0(str):
    my_str = str.upper()
    sum_value = 0
    for ch in my_str:
        #print ord(ch)
        tmp = ord(ch) * 0x157A - 1
        sum_value = sum_value + tmp

    #print sum_value
    return sum_value * 10 + get_samll_value(sum_value)

password = fun_4011C0("dreamcracker")
assert 47722525 == password
print password

参考资料

  1. https://www.cnblogs.com/ZRBYYXDM/p/5209786.html 一条汇编指令引发的 蝴蝶效应
  2. https://blog.csdn.net/liujiayu2/article/details/77711838 汇编浮点指令fld、fstp