首页
论坛
专栏
课程

[原创]CTF2018追凶Writeup

2018-12-11 23:20 314

[原创]CTF2018追凶Writeup

2018-12-11 23:20
314
        拿到程序后先执行一下,32位窗口程序,有输入框和确定按钮,任意输入一个字符,得到MessageBox弹出的错误提示,记录下提示信息。
        stud pe 去掉程序的动态地址并保存,ida7打开修改后的程序,Import中尝试搜索GetDlgItemText函数:找到,x一下找交叉引用位置,来到  sub_401040。 咋一看,程序逻辑极其简单: 从输入框中取最多20个字符->逐个累加求和得sumX->判断和sumX是否处于0到0x1024之间;如果是,分配sumX个字节的内存->调用 sub_401020, 然后释放内存->弹出提示。而MessageBox的参数是在取输入之前初始化的全局变量,想当然地认为应该是在sub_401020做了某种手脚,把输入修改成了正确的提示。
      进入 sub_401020 来到汇编状态,该函数除了把 loc_40D180 放入栈中,并没有什么其它的操作,点进  loc_40D180 把mov byte ptr *** 后面的都改成按字符显示,一路看下去,好像就是弹出正确提示的地方样,没细看输入的验证码如何用,直接在IDA 里面运行调试,任意输入一个,在 00401035  retn 设置断点,运行到此时手动把 esp 的值修改成 0x40d180,再执行,弹出了成功的对话框。看起来该题可能是个溢出题。继续回到 sub_401040,既然是溢出,获取到验证码的缓冲区是关键,然后,再三确认发现 GetDlgItemTextA 的向栈中临时变量拷贝并不会导致任何问题。对MessageBoxA的参数查找引用,除了 sub_401040中有使用外,只有一个00414030处对错误框内容和00414034对错误框标题有引用,该位置前后扫一下,里面又有变量又有函数,看起来真像一个全局的类/结构体啊..  继续对 off_414030 和 off_414034分别按 X 查找交叉引用,都指向了 sub_401a10。对 sub_401a10 一路引用找过去,看到一个名为 StartAddress 的函数,里面有全局变量的判断、线程同步。到这里,大至猜测:真正的验证码判断可能是在另一个线程中?然后并没有看到从输入框中获取的验证码丢进线程,猜测并不成立,但不影响后面的调试
      调试解决
       程序看起来并没有采用反调试,直接在 sub_401A10 处下断点,输入几个字符点击确定,程序马上断到了 sub_401a10,F8单步几次,观察了几个变量,发现很重要的几个信息如下图0:

得到结论1: 最长输入是19个字符,和sub_401040里面一致
       结论2: 4147F0 里面保存了输入的验证码,参与sub_401290, sub_401870, sub_4017B0 的运算
       结论3:  sub_401290不能返回0, sub_4017B0 返回的值必须等于0x5634D252(调试时手动修改这两个函数返回后的eax值,可出现成功提示)
       现在先假设这就是真正的判断逻辑,继续分析  sub_401290,  sub_401870, sub_4017B0这三个函数。sub_401870是一个明显的求字符串长的函数。先大概理一下整体的流程:
sub_401290:
      函数的输入是验证码和验证码的长度,并且初始化了一个全局的数组 4147D0 里面,再调用 sub_4015B0, sub_4015B0必须返回非0
sub_4015B0:
     对输入的验证码两两一组,依次传入 sub_401380,并且 sub_401380 不能返回0, 最后再判断全局数组 4147D0 的值是否依次是 1-8, 0
sub_401380: 
    根据验证码两两一组,对全局数组  4147D0 里面的值进行了一些顺序调换,
     当所有的检测、调换等都正常完成时,sub_401290得以满足。看来关键点在 sub_4015B0和sub_401380里面。再看 sub_4015B0,极具提示的信息如下图1:

根据 w, d, s, a这个游戏里面的上下左右移动键和 sub_401380 里面对   4147D0 有位置交换和交换的方式的重要提示,把 4147D0 转成 3 * 3 的矩阵,刷新下F5自动出来的代码,可以明显地看出,在 sub_401290 里面初始化矩阵为
4 1 3
7 2 5
8 6 0
经过输入的验证码在 sub_4015B0 和 sub_401380 里面的处理后,矩阵需要被变换成:
1 2 3
4 5 6
7 8 0
    结合 sub_401380 里面对矩阵的移动可知,函数的第一个参数分别为 0, 1, 2, 3对应的是上移,右移,下移,左移,和 w, d, s, a 的正常操作完全对应。第二个参数需要对应矩阵里面的值,如下图2所示:

    现在,题目变成了一个很简单的数字拼图: 只能移动0附近的字符。先手动拼一下,马上就可以得出移动顺序为 d6d8s7s4a1w2a5w6, 而且正好满足不超过20个字符的限制,直接把它作为验证码尝试,提示成功。sub_4017B0 里面有对输入的一个求和判断,感觉因为有移动次数不超过9的限制,这个就不是很有必要了。

   题目解完了,再看看它实现的原理是什么呢
最开始以为是在线程中验证,但并没有发现输入到线程,并且调试时发现真正的验证函数仍然由主线程调用,猜测不合理。在 sub_401a10设断点,执行到此时看调用堆栈,仍然没有有用的信息。再点中函数 sub_401a10,按x查找交叉引用,发现一个很明显的地方,如下图3所示:

点进 user32的749c6bee, 是 GetDlgItemTextA函数,当调用完成输入获取后,跳转到 sub_401a10, 如下图4 所示:

现在确定,是 GetDlgItemText的可执行代码被修改,从 sub_401c10中调用的 sub_4019b0和sub)4019e0里面对VirtualProtect的调用,就可以判断出了。
现在它的实现原理已经理清,大概应该就是这样的:
1. 利用TLSCallback:在程序执行时抢先修改了 GetDlgItemTextA 函数
2. 当sub_401040调用到GetDlgItemTextA函数后会跳转到sub_401a10中进行验证
3. sub_401a10调用验证通过后,修改作为MessageBoxA参数的全局变量,为正确的提示信息,未通过则不改。
4. sub_401a10在验证结束(不管通过与否),都把GetDlgItemTextA被修改的部分还回去..
5. 回到调用GetDlgItemTextA的sub_401040,弹出提示信息..


[推荐]看雪企服平台,提供安全分析、定制项目开发、APP等级保护、渗透测试等安全服务!

最后于 2018-12-12 11:33 被leavesth编辑 ,原因:
上一主题 下一主题
最新回复 (0)
游客
登录 | 注册 方可回帖
返回