分析 abex-crackme #2

1、分析环境

  • 操作系统:Win10 1809 x64
  • 调试工具:Ollydbg

2、运行程序,了解大致的运行过程

1552905013756

  • 根据上图发现,该程序需要输入适合的NameSerial并点击Check进行验证

1552905214554

1552982693551

  • 通过输入Name发现,该程序Name至少需要4个字符
  • 该程序是通过获取用户输入的Name字符串,再通过加密的方式加密Name字符生成相应的Serial值。这里可以明确一下目标,即逆向分析该加密算法具体是如何实现的。

3、开始调试

  • 运行OllyDbg调试程序,查看反汇编代码

1552906396475

  EP代码前面的代码为IAT区域,是一些调用VB引擎的函数,如ThunRTMain()。接着PUSH指令将RT_MainStruct结构体的地址作为ThunRTMain()函数的参数压入栈,再执行CALL指令即调用ThunRTMain()函数。这里用到的是间接调用的方法调用ThunRTMain()函数,是VC++和VB编译器中常用的间接调用方法。

  • 查看一下RT_MainStruct结构体

1552906714435

  RT_MainStruct结构体的成员是其他结构体成员的地址,即VB引擎通过参数传递过来的RT_MainStruct结构体获取程序运行需要的所有信息。

  • 查看ThunRTMain()函数

1552906870459

  由于这里是MSVBVM60.dll模块的地址区域,是VB引擎代码而不是程序代码,因而不用对其进行分析。

4、分析 crackme

4.1、检索字符串

  • 根据程序运行时有弹窗提示,此时可以使用Ollydbg的字符串检索来定位到实际的代码处

1552908307973

  • 通过观察,我们发现该程序的限制Name长度的弹窗字符串、错误弹窗字符串、还有一处应该是正确弹窗的字符串,这里我们点击其中一个正确弹窗字符串进入其地址处,同样可以通过错误的字符串去查看,两处相距不远
  • 进入到正确字符串地址后向上滚动,发现条件转移语句的代码

1552908740116

  • 调用__vbaVarTstEq()函数比较字符串后,执行TEST命令(TEST命令为逻辑比较,仅改变EFLAGS寄存器而不改变操作数的值)检测AX的值是否为0,再由JE指令决定执行那部分代码。

4.2、查找字符串地址

  • 403329地址处的__vbaVarTstEq()函数为字符串比较的参数,上方2个PUSH是该函数的参数,即需要比较的两个字符串。
  • 此时,我们可以在403329处设置断点,然后运行代码让其在该处中断下来,并查看栈

1552910023225

  • 查看存储在栈中的内存地址

1552983007877

  • 由于Dump窗口显示的是16进制数据,此时我们可以将其转换成long型地址,就可以观察到实际的字符串了

1552982960541

  • 这里我们可以观察到VB使用的字符串是可变长度的字符串类型(Unicode)

  • 通过观察,EDX为实际的Serial值,EAX是用户输入的Serial值

  • 运行一个新的Crackme程序,输入Name=“ABex1234”,Serial=“A5A6C9DC”,会弹出成功的消息框

1552983065666

  • 由此可知,当输入Name=“ABex1234”,Serial=“A5A6C9DC”时即可显示正确的信息。也就是说,程序是根据输入的Name来生成特定的Serial值。由此便需要查看生成Serial的算法。

4.3、生成Serial的算法(找到函数开始部分)

  由上面分析发现,该函数可能是Check按钮的事件处理程序。此时我们找到该函数头(栈帧代码,该处上方有NOP指令填充)

1552980570798

4.4、预测代码

由上面猜测该程序是根据Name的输入得出Serial的,推测出如下特点:

  • 读取Name字符串(使用GetWindowText、GetDlgItemText等API)
  • 启动循环,对字符加密(XOR、ADD、SUB等)

4.5、读取Name字符串的代码

1552981077720

  • 经过调试发现,[EBP - 0x88]处为保存Name字符串的字符串对象,402F98为获取Name字符串的函数

  • 我们可以通过Dump窗口和栈窗口(修改相对于EBP)观察到该字符串

1552983148412

4.6、加密循环

继续调试,找到加密循环的关键位置,这里为__vbaVarForInit()__vbaVarForNext()函数:

1552983672105

4.7、加密方法

输入的Name字符串为“ABex1234”

在上述循环中寻找如__vbaStrVarVal()等的字符串相关处理函数,在该函数处设置断点,运行完该指令后,查看那EAX寄存器保存的内容

1552985865758

可以看到,其为输入的Name字符串的第一个字符“A”。

接着rtcAnsiValueBstr()函数将“A”字符进行Unicode>ASCII的变换。已知“A”=53。

1552985955574

继续向下调试,我们观察到__vbaVarAdd()函数,应该是加法运算,并且该函数上方有3个PUSH,我们在PUSH下方设置断点运行到此处

1552986532295

执行完操作后,查看这3出寄存器所指的内存区域

1552991103792

1552990497051

继续运行代码到__vbaVarAdd()函数处,并观察3个寄存器的内存区域,发现其中,EAX的值发生改变,为0x64

1552990775571

此时我们得到如下推测:

  • EAX = 64 (用于加法运算)
  • ECX = 计算结果目标缓冲区(推测:该处未有数值初始化)
  • EDX = 41(“A”)

1552990867729

执行完CALL后观察ECX寄存器的内存数据是否发生变化,41 + 64 = A5

1552991224061

接着下面的代码将ASCII数值转换为Unicode字符:

1552991660556

rtcHexVarFromVar()函数将相应ASCII数值转换为Unicode字符。上述代码将数字A5转换为字符“A5”(Unicode)。执行完该CALL指令后,查看EAX寄存器所指的缓冲区:

1552991803617

我们到该字符串实际的地址查看:

1552991892987

接下来是拼接字符串

1552995191155

最终循环4次,生成如下序列号:

Serial = old("A5A6C9") + add("DC") = "A5A6C9DC"

4.8、加密方法整理

  1. 从给定的Name字符串前端逐一读取字符(共4次)
  2. 将字符转换成数字(ASCII代码)
  3. 将变换后的数组加64
  4. 再次将数字转换为字符
  5. 连接变换后的字符

5、Serial生成代码

  • 编程工具:VS2017

  • 编程语言:C

#include <stdio.h>
#include <conio.h>

int main()
{
    char str[ 20 ];
    printf( "Name:" );
    gets_s( str, 20 );
    int Serial[ 4 ] = { 0 };
    for (int i = 0;i<4;i++)
    {
        Serial[i] = str[ i ] + 0x64;
    }
    printf( "Serial:" );
    printf( "%X%X%X%X\n", Serial[0], Serial[ 1 ], Serial[ 2 ], Serial[ 3 ] );
    _getch();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/PhantomW/p/10669874.html
今日推荐