逆向工程核心原理-8

逆向工程核心原理-第八章 abex-crackme-2

运行示例程序

Untitled

是一个标准的序列号破解

使用的是Visual Basic语言编写

在这里就提前将一些VB语言的特征吧

VB语言的特征

VB专用引擎

这个引擎就是同文件夹中的dll文件 , 引擎使用的的不同之处有调用API跟user32的不一样,比如dll中调用的rtcMsgBox()函数, 实在该函数内部通过调用user32.dll里的MessageBoxW()函数来工作

本地代码和伪代码

本地代码就是IA-32代码(就是普通的汇编代码), 伪代码就是要用解释器的代码, 使用VB引擎实现虚拟机可以自解析的指令.

事件处理程序

需要调试的代码存在于各个事件处理程序之中(不是很懂这个是什么意思)

间接调用

调用函数时不是直接CALL,而是, 通过中间的一个跳转实现调用函数

函数之间存在nop指令分隔

如下图所示

Untitled

调试程序

EP

Untitled

蓝色填充的部分就是我们的入口点了, 向下执行第三个语句会跳到第一个语句,这时执行第一个语句

然后我们执行完第一个语句(这个语句跳转的目的地是ds : [004010A0]处的ThunRTMain()函数)(这里体现了上面的VB间接调用特性)

方法一:检索字符串来找到主函数,直接找出答案

不知道为什么用查找文本找不到 , 最后翻到了

注意要用UNICODE来查看,要不然显示的是断掉的字符串(这我也不知道为啥 , 但至少能找到了)

Untitled

点击后来到了主函数

看到了两个判断错的语句 , 基本可以确定了

Untitled

向上翻找(通过判断正确的语句字符串以及跳转的目的地等因素)确定了重要判断语句

Untitled

把注意力放到上面的函数

Untitled

根据函数名”TstEq”可以猜测是”文本是否相等”(也可以联想到C语言的strcmp()函数), 比较(TEST命令)返回值(AX)(为什么可以判断AX就是返回值 , 因为汇编定义 : 调用函数后返回值都是存储在EAX中(来自百度)),最后决定是否跳转

所以可以猜测逻辑是比较两个参数(EDX跟EAX),如果相等的话,就返回true.

还有一个要提的基本知识

1
2
3
4
5
6
7
8
9
10
TEST   EAX, EAX
JE SOMEWHERE

==

if (eax == 0)
{
somewhere.......
}
#注意当JE换成JZ也是同样的效果, 换成JNZ的化刚好相反

TEXT跟5个标志有关 , 太多也太难记了 , 这里就先记住这个TEXT跟JE的搭配就好了

其实书上也有比较简单的理解

  • TEST : 逻辑比较, 与’AND’一样(仅改变EFLAGS寄存器而不改变操作数的值) , 若2个操作数中一个为0, 则AND运算结果被置为0(ZF = 1)
  • JE: 条件转移指令 若ZF = 1, 则跳转

找到我们的要的字符串

既然我们刚刚判断了上面的函数是一个检查字符串对错的函数 , 也就是说这个函数里面藏有我们所需要的参考字符串(也就是正确的字符串)然后看向参数

Untitled

下面传入了两个参数EAX跟EDX , 很容易让人联想到输入字符串跟参考字符串 , 但是寄存器只是临时储存值 , 我们要找到内存中的真正的字符串 。 然后再看上面找到了给两个参数赋值的过程,知道了其实这两个参数的值是两个地址,光知道地址是不够的我们要知道地址上的值才是最重要的,所以我们要根据地址找到我们所需的数据。

这里的地址是以ebp作为参考点的,而且还在栈帧的上方,所以数据存在栈中。

我们看向栈中情况

Untitled

很难看出那个是我们需要的地址

这时右键栈窗口,点击地址中的EBP选项就可以以EBP作为参考点来看地址了

Untitled

找到了[EBP - 4]跟[EBP - 34]两个地址以后,发现这个地址上并没有存储什么东西。

但是在下面却有一个很可疑的数据

Untitled

可以看到[EBP - 4]的下面有一个字符串,而[EBP - 34]下面也有一个我们输入的字符串,两个放在一起的时候就很容易联想到是比较函数中的两个参数了。(这里我们运行程序后的栈情况跟书中的相差挺大的,用书中的方法也可以解出来,但是这个感觉更好一点)

拿去试一下(一旦发现有可能是flag,就马上去试一试,解出来血赚,没有解出来也不亏啊)

Untitled

得出了正确答案。

方法二:解析序列号算法

首先找到主函数开始的地方

方法就是找到主函数开头有的特征

1
2
3
#函数开头的特征
PUSH EBP
MOV EBP, ESP

我们向上面翻找(注意一定要有耐心, 有时翻找的时间可能会长一些, 最好有up按键一个一个找)

于是, 我们找到了函数的开头部分

Untitled

为后面的反复调试做准备, 我们现在函数的开头下一个断点.

解析算法的思路

预测算法进行时的特征

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

所以下面我们就要针对以上两点特征来确定算法的主体(即算法从哪里开始, 到哪里结束, 算法的语句内容)

读取name字符串的代码

我们向下翻找, 多注意CALL语句, 因为程序要想获取字符串的话一般都会调用API, 我们以此为参考有目的的翻找效率会高很多.

Untitled

其实上面的描述还不够准确, 我们的name字符串在函数中会以局部变量的形式存储, 所以读取name一定会使用局部变量, 也就是使用EBP栈帧来定位局部变量

所以总结一下读取字符串的特征

  • 出现[EBP - XXX]这种东西, 然后去查看地址是否是我们所需要的数据
  • 有PUSH指令来进行传参
  • 有一个CALL调用函数

综上所述, 查看EBP - 88地址出的数据

Untitled

在栈中找到了我们的name字符串, 所以确定上面是读取name字符串的代码

补充

后来又重新调试了一下,发现刚刚执行完这个语句后的地址是正确的

Untitled

非常神奇

所以可以判断这个语句是读入字符串

找到加密循环语句

继续向下调试, 并翻找找到了这样的语句(书上的图把代码拼在了一起,比较直观一些)

Untitled

  1. 首先是MOV EBX , 4

     EBX寄存器的一个作用就是循环计数器,  所以当看到给EBX赋值的时候就要猜 
     
      测这是不是循环
    
  2. 判断这些语句是加密循环的另一个因素是两个函数名vbaVarForInit()函数跟vbzVarForNext()函数, 第一个函数For Init 跟第二个函数For Next联系起来可以想到读取单个字符

  3. 同时底下的JMP向上跳回了TEST EAX, EAX也是循环的一个特征: 跳回开头部分, 检查条件来确定是否继续循环, 下面的JE也是判断的关键

总结一下找到加密循环语句的特征

  • 首先是找到循环计数器EBX的赋值语句如: MOV EBX, XXX
  • 找到检查语句跟跳转语句的连用是循环的特征, 如:
1
2
TEST  EAX,EAX
JE somewhere
  • 找到跳回的JMP(标志着循环的结尾部分), 如: JMP upsomewhere(注意这个跳转得是向上的跳转)

还有一点疑问就是为啥给EBX赋值, 后面的检查部分却用EAX啊.我看了一下循环, 结尾部分是调用了一个ForNext函数, 也就是说EAX中的值是那个函数的返回值,这个检查应该是看字符串是读到的是否是空的. 也就是保证这四次循环都能够读到有效的字符. loop count应该是自动减一. (当然只是猜测啊, 希望后面能得到肯定的答案吧……..现在只需要知道循环是执行四次的就行了)

调试的时候发现TEST EAX,EAX加上JE并不是控制整个循环, 而是前面的一部分, 所以我一开始就理解错了, JE应该是检查字符串的长度是否是有4个

分析加密的内容

Untitled

Untitled

第一个语句是从字符串中获得一个字符

  • 为什么可以这样判断呢, 因为这是调试出来的, 执行完这个语句以后可以直接看出, 现在我们输入的name是LameCrow, 可以看到参数是”L”, 一次进行下去第二个参数是”a”, 于是可以猜测第一个语句是从字符串中获得一个字符

Untitled

第二个语句是将字符转换成ASCII编码

  • 为什么可以这样判断呢, 也是使用调试, 执行完这个语句以后看到寄存器窗口返回值EAX = 4C, 恰好就是”L”的编码, 所以可以这样判断, 其次就是函数名Value B str, 大致的意思就是字符串的值, 也可以作为依据之一

Untitled

PUSH EDX是将单个name字符的整数作为参数给下面的函数

  • 判断依据: 也是通过动态调试, 执行玩这个语句以后右侧注释可以直接看到传入参数的值根据下图, 可以看到传入的是一个地址, 再在栈中找到这个地址, 可以看到下方就是我们name字符的整数了(在这个地址上的数据是个00000002, 但是下面就是我们要的数据, 为啥name字符的数据会偏移啊, 主要前面我们所需要的数据基本都有不同程度的偏移, 所以这里我们暂且无视掉这个问题好了,,,,,,)

Untitled

PUSH ECX是目标操作数dest

上面的语句操作完了以后观察栈中情况

Untitled

在观察寄存器窗口

Untitled

栈中从上到下分别对应ECX, EAX, EBX

在dump窗口中ctrl + g 填入地址19F114

可以找到字符”L”

Untitled

Untitled

书中很多内容都找不到………先不管这么多,继续向下调试

Untitled

执行这一条指令

可以看到ECX寄存器所指向的区域作为缓冲区,确实可以看到我们想要的数据

4C + 64 = B0

Untitled

同时EDX里面也有该结果

Untitled

但是有一个疑问是为什么返回值不到EAX里面啊

  • 其实返回值是到EAX里面了, 只不过不是相加后的值, 而是一个地址(因为EAX执行完了以后也变红了, 说明它的值发生了变化), 之所以相加结果在EDX里面, 是因为在函数里面

Untitled

继续调试, 一下代码是将值转化为字符(UNICODE)

Untitled

Untitled

注意这个转换不是将B0转化为对应的UNICODE字符(像是4C ⇒ “L”),而是直接转换(像是B0 ⇒ “B0”)

继续调试, 下列的语句会将字符连接起来

Untitled

Untitled

连接字符串函数 serial(EAX) = old(ECX) + add(EDX)

最后得到了加密流程

  • 从给定的Name字符串前端逐个读取字符(循环进行四次)
  • 如果没有4个字符就会报错(看到循环进行四次的时候可以先拿三个字符去试试)
  • 将字符转化为数字
  • 变换后的数字加上64
  • 再将数字”直接”转换为字符
  • 连接变换后的字符

解出了答案

这是第二次循环的截图(第一次截的图没传上来)

Untitled

第三次

Untitled

Untitled

第四次

Untitled

Untitled

最后得出了答案

提升

书中提到的语句大部分都能够理解了, 但是更加难的是如何从这么多的汇编语句中找到有用的语句.

总结

一些逆向的思路

学会预测(先进行静态分析?)

在调试之前先学会预测程序的走向, 预测对了血赚, 预测错了不亏, 对了就可以节约很多的时间, 但是我有一个坏习惯, 就是一直在静态分析, 而不去结合动态调试, 非要分析出来了采取调试验证. 在逆向中调试的地位跟静态分析的地位一样, 甚至跟重要.

如果有疑问马上想一想可不可以利用调试来验证

比如我在分析的时候发现了有一个寄存器里存有数字8, 刚好是我输入name字符串(LameCrow)的长度, 所以就猜测是不是代表的是字符串长度, 但是调试后发现没有关系, 这就是一个例子

一些汇编的常识

返回值放在EAX

TEST跟JE的组合(TEST跟JZ的组合跟这个一样, JNZ则相反: EAX不为0则跳转)

本题的难点

找到loop count

这点告诉我们调试的时候要注意EBX的值的变化

文章作者: LamのCrow
文章链接: http://example.com/2021/12/18/ReMain-第八章 abex-crackme-2 646ac710963d4a17aab5fdeaad1918ec/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 LamのCrow