首页
社区
课程
招聘
[翻译]Flare-on4第3题解答 - greek_to_me.exe
发表于: 2017-12-21 23:27 5572

[翻译]Flare-on4第3题解答 - greek_to_me.exe

2017-12-21 23:27
5572

题目作者: Matt Williams (@0xmwilliams)
翻译前言: 文章对代码自修改的分析很细致, 使用Unicorn框架来模拟执行代码和Capstone进行反汇编.
ps: 程序和脚本可以从附件下载, 程序可能会报毒但是安全的, 建议在虚拟机下操作, 解压密码: www.pediy.com

greek_to_me.exe是一个Windows x86可执行文件, 如下图所示, 程序中的字符串表露了00401101处要达成的情况, 如下所示.

然而, 在地址00401101前面的汇编代码却包含如下所示的奇怪汇编指令

不过也许你在此时能准确地猜测到, 程序为了能达到地址0x401101, 会修改这些奇怪的指令, 因为这些奇怪的指令运行下去, 我们的程序会极有可能崩溃. 另一种迹象能暗合我们认为这是代码自修改的推测, 那就是在查看程序的文件头时, 我们发现程序入口点所在的.text区段是可写的. 到这里, 我们正常的套路就可以往上查看分析, 看是什么能让程序选择0x401063的正确分支.

当然还有另外一种方法就是确定程序的套接字是在哪里生成的, 话不多说, 我们这就来尝试.

greek_to_me.exe包含有0x401151处的一个简单socket函数调用, 如下图所示

sub_401121里我们可以观察到, 程序用了一系列Windows API函数: socket,bind,listen和accept创建了一个监听本地TCP端口2222(0x8AE)的套接字

程序一直等待着监听端口的连接, 直到从建立连接的客户端那接收到最多4个字节. 接收到的字节会存储在缓冲区中并以参数的形式传递给sub_401121. 一旦有接收到字节, 该函数就能在 不停止现有连接的情况下返回一个socket句柄. 要记住, 当执行到0x4010710x401101时, 程序就会使用到它.

如果sub_401121返回了一个合法的socket句柄, 程序会继续执行, 否则程序退出. 如下代码块为寄存器赋初值, 这几个寄存器将在解码循环中发挥用处

我们看这段代码, 首先, 一个位于.text区段的可执行的代码地址赋值给ECX寄存器, 并且加上了常量值79h, 这也表明了随后将介绍的解码循环里的终止地址. 地址0x40107C赋给EAX寄存器, 代表解码循环的起始地址. 在0x401036, recv缓冲区的第1个字节被赋给了EDX寄存器的低8位

继续向下看代码块, 其中包含一个进行如下操作的循环

EAX中存储的地址则自增1并且跟ECX中存储的最大地址进行比较, 只有当EAX的内容跟最大地址0x4010F5相等时循环才会结束.

继续向下看, 程序随后便将刚刚修改了的代码块首地址(0040107C)和块大小0x79作参数传递给sub_4011E6.

我们可以看到程序返回值的低16位(AX)赋给了EAX寄存器再将EAX跟硬编码值0xFB5E进行比较, 而比较的结果则决定了程序是跳向0x40107C还是执行到显示失败信息的分支

获得这些信息, 我们可以做出正确假设: sub_4011E6是用来计算验证值或者说是之前解码循环所修改的字节的校验值. 并且可以确定从socket接受到的字节值是用作异或修改0x40107C0x4010F4之间代码块的key值. 而程序自修改的代码则通过一个硬编码的校验值进行验证. 因为使用的key只有单字节, 因此我们可以进行简单的暴力穷举来获得期望的key.

如果修改后的代码正常执行并且通过socket返回了Congratulations字符串, 那么就可以确定暴力穷举成功了. 基于这个假设, 我们可以编写一个如下的脚本代码帮助输出正确值:

但如果我们并不想基于解码的字节都正确执行这样一个假设来操作, 而是自己验证解码后的校验值是否匹配, 要怎么办呢? 相比花大量时间逆向校验算法, 这次我们来尝试体验一个有趣的恶意代码分析技术: 代码模拟执行

首先, 我们提取校验函数sub_4011E6的操作码, 我们只关心在0x401265执行完后存储在AX中的返回值, 如下图所示. 并且不需要提取函数的平衡栈的结尾部分.

我们同样也需要从0x40107C处提取0x79长度的待解码字节. 我们提取的字节集合都在如下的用于模拟执行的python简易脚本中可见

如下的代码定义了一个函数, 给定函数一个0x000xFF之间的值, 就能执行原来程序的解码操作.

接下来, 我们定义一个函数, 函数在给定待解码字节后会利用Unicorn框架来模拟执行校验值函数

如上的代码中初始化了一个32位的x86模拟器, 随后创建了一个用于存储校验函数代码, 函数内部栈以及待解码字节的2MB内存. 校验代码和待解码字节可以写入内存范围的任意地址里.

校验值函数从栈上取两个参数: 待解码字节的起始地址(0x40107C)以及长度(0x79), 下图显示了校验值函数调用后的状态.

为了能让校验值函数能在模拟时正确执行, 我们还需要对栈进行设置来匹配上图的栈空间布局, 并适当填充ESP寄存器. 如下所示, 在模拟执行结束后, 我们可以从emulate_checksum返回计算后的校验值

现在到轻松的部分了. 我们暴力穷举异或的key, 解码字节并模拟校验操作, 然后确定哪一个key能获得正确的校验值. 如下所示

运行脚本最后打印出正确的单字节值: 0xA2. 然而我们仍然不明白解码后0x40107C处的指令干了什么. 我们来尝试使用Capstone反汇编器来反汇编这些指令, 如下所示

运行我们的脚本并提供指令, 结果如下所示

我们可以看出两点, 首先栈上正在填充成一个字符串, 其次, 填充到栈上的常量十六进制值在可显字符范围内(0x20~0x7E). 依据它们在栈上的顺序依次提取出这些可显字符, 或者你可以用调试器观察栈上的内容, 得到题目解答: et_tu_brute_force@flare-on.com.

以下附上python脚本

 
 
 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 199
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢
2017-12-23 16:08
0
雪    币: 312
活跃值: (123)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
mark
2017-12-28 12:45
0
雪    币: 223
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
谢谢!  这几种方法都很有帮助!
2018-3-21 16:26
0
游客
登录 | 注册 方可回帖
返回
//