banner
lca

lca

真正的不自由,是在自己的心中设下牢笼。

《从零开始学IDA逆向》学习笔记-12(程序注册逆向分析)

image

12.1 通过命令行参数确定 main 函数#

本章程序 TEST_REVERSER.exe,从这个练习中学习静态逆向和调试的新知识。

首先看下程序架构,运行 ide 查看。

image

从上图可知,此程序 32 位架构,通过 vc++ 2015 编译。

第二步,尝试运行程序,提示输入用户名和密码,随意输入用户名密码,显示 bad reverser。

image

第三步 打开 ida,加载目标程序

image

转到关键部位的一个方法就是搜索字符串。搜索一些命令行参数,如 argc、argv 等,由于是 c++ 编写的程序,函数原型如下:

int main(int argc, char *argv[])

从 name 中搜索 arg 等参数,ctrl + F 调用处搜索框。

image

双击上图中的_p_argc_,内容如下:

image

按 X 键搜索引用

image

上图中程序调用了_p_argc__p_argv函数,然后传值给 main 函数。

双击进入 main 函数。

image

main 函数的三个参数

image

main 函数的引用(有符号)

12.2 main 函数栈分析#

在任意函数参数或局部变量上双击,转向静态栈视图。

image

从上图中,我们可以知道,最下方的首先是函数参数,它们总是在返回地址 return address (r) 的下方,因为在调用函数之前参数首先通过 push 指令传入栈,然后再传入返回地址。

再往上是调用 main 函数的上一级函数的 ebp 值。

image

上图中,main 函数执行的第一条指令 push ebp 时会将它保存到栈上,然后将 esp 的值传给 ebp,将 ebp 作为下方的函数参数和上方局部变量引用的基址,最后 sub esp,94h,0x94 移动 esp 为局部变量和缓存区创造空间,在这个程序中移动的距离是 0x94,编译器根据源代码来计算局部变量占用的空间。

esp 的值指向局部变量的上方,ebp 指向基址,基址的上方是局部变量,下面是返回地址和函数参数,如下图所示。

image

所以在以 ebp 为基址的函数中,一旦上一层函数的 ebp 值通过push ebp保存到栈之后,esp 的值被传给 ebp。00000000 作为一个水准线,上方的地址为负(-),下方的地址为正(+)。

上图中,var_4 的相对地址是 - 00000004,如果将 ebp 的值作为基准,var_4 的实际地址是 ebp-4。

在反汇编视图中,右键单击使用 var_4 的任何一处可以验证上述内容。

image

var_4 上方有一片空白区没有变量,有可能是一个缓存区。

image

把视图向上方移动一下。就可以看到空白区上方的第一个变量 Buf,如下图。

image

右键单击选择 ARRAY,弹出如下窗口,可以知道数组由 120 个 1 字节元素组成,因此数组大小为 120。

image

image

函数栈视图

上图中显示了 ebp 基址,当指向 mov ebp,esp 指令后,esp 再减去 0x94,最后指向局部变量区域顶部,如下图所示,执行 sub esp,0x94 之后,esp 的值。

image

上图中,左侧的 00000094 代表的 esp=ebp-0x94,在函数内部调用其他函数时,esp 还会再往上移动,在 main 函数内部知道退出 main 函数,还是对 - 0x94 之前的局部变量进行操作。

12.3 main 函数局部变量#

接下来从静态栈视图对局部变量进行逆向分析,main 函数的参数是已知的。

image

局部变量

上图中,程序读取某个值并且和 ebp 上的值进行异或,运算之后保存到 var_4 中,作用是防止栈溢出。

双击sub_4011B0进入,可以看到sub_401040函数。

image

image

sub_401040 函数内部由 printf 函数,由此判断这个函数用来打印字符的。

image

之后,size 变量赋值为 8,从引用可以看到有两处引用,只是读取了内容,而未修改

image

image

接下来有个 gets_s 函数,gets_s 函数会限制用户输入,上图显示最大输入 8 个字符,通过 push eax 传参,然后 lea 获取变量 buf 也就是缓存区的地址。

如果用户输入少于 8 个字符就直接按回车,函数也会中断输人然后返回。所以在 Buf 缓存区最多有 8 个字符。

然后程序再通过PUSH EDX将缓存区地址传给strlen()这个 API 函数作为参数,strlen()获取 Buf 中的宇符串长度,再把结果保存到 var_90 变量中。

12.4 循环和代码块编组#

image

上图中,蓝色箭头指向的往回跳转可能是一个循环,而且var_84变量开始作为这个循环的计数器。在0x4019f5处有一个条件跳转,满足条件则结束循环。计数器从 0 开始累加,直到大于或等于var_90变量时,循环结束。

image

计数器加 1

计数器变量的值传入 EAX,EAX 加 1 之后再回传给计数器变量。

image

上图中,程序从EBP+EDX+BUF处取出BUFFER的第一个宇节。EBP+BUF和储环计数器相加,计数器开始时是 0,每循环一次加 1,读取下一个宇节。这样循环把BUFFER每一个字节的十六进制数加到var_ 88(初始值0)变量上。

这个循环的内容是字符相加。

image

都标成同一种颜色,拖动最下面的代码块,使它们靠得更近。

可以对这三个区块分组,通过按住 ctrl + 鼠标单击选项卡上方,依次选中每一个区块,上方颜色变成青色。

image

然后右键,组合结点。

image

最终效果

image

想看具体内容,需要右键取消组合结点

image

12.5 注册算法分析#

继续循环内容下面的代码分析

image

上图中,要求用户输入用户名和密码,下面的 sub_4011b0 是 printf 函数,然后调用了 gets_s 函数,用户名和密码使用同一缓存区 Buf 和最大字符限制 Size。

因为程序已经计算出用户名每个字符相加的和,不再使用用户名字符串,所以密码可以使用同一个缓存区。

image

接下来,调用了 atoi 函数将输入的内容转换成 10 进制,并且保存到变量 var_94 中,这个也就是密码变量。

然后程序通脱 push edx 将 var_94 密码变量传入,通过 push eax 传入变量 var_ 88,传入的这两个变量作为 0x401010 函数的参数。

image

进入 0x401010 函数内部,可以发现两个参数,arg_4 应该是密码变量,因为密码变量首先传入了栈中,上面的 arg_0 参数就是 var_88 变量的值。

那么 0x401010 函数是如何使用这两个参数的呢?

在进行 cmp 比较之前,程序将密码变量 arg_4 传给 eax,在执行 shl eax,1。

shl 将 eax 中的 bit 向左偏移,右侧低位用 0 填充,作为特例,shl reg,1 相当于乘以 2。

所以程序将密码变量乘以 2,再和 arg_0 进行比较。

通过 python 的 ord 函数计算 ascii 对应的十进制值。

image

如果将 pepe 作为用户名,那么 pepe 字符的和如下:

image

结果为 0x1aa,输入的密码乘以 2 之后和 0x1aa 比较,所以正确的密码应该是乘以 2 之后等于 0x1aa,那么结果如下:

image

结果是 213。

此时可以打开程序,输入用户名 pepe,密码 213。

image

显示成功信息。

image

通过上图可以知道,当这两个值不相等时转向红色代码块,并返回 0,如果相等则转向绿色代码块,返回值为 1。

返回值的作用是什么呢?

image

通过上图可以知道,返回值传给了 var_7D 变量

image

通过上图可知,如果返回值为 0,则跳转到 bad reverser,如果是 1,则跳转到 good reverser。


这章主要讲了关于如何逆向分析绕过注册,主要内容包括函数栈、局部变量、注册算法分析等,

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.