无意中看到了re大佬jojo的帖子https://0xffff.one/d/769,也觉得这学期CSAPP似乎已经看了不少(不是,便买了本加解密朝圣一下。学了半个月,感觉实在得找些东西练下手,有点知识输出,这两天便打算破解一下扫雷玩玩。
分了三步走:
1.先废除扫雷的雷数与时间记录
2.寻找雷区,实现自动标记雷的位置
3.一键通关
0x01报废时钟
![](https://static-img.0xffff.one/Yf9Y8HjGdUqtZk5kuxpYy9AeENxGTQ7RxNlN0LRByho/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4MDU1/LTczMjc3MS05YzI1/NzMyMTE3YzU5ZDk0/MzhhNTdhMzk3MGIz/OWUzLnBuZw.jpg)
扫雷左上角显示所剩雷数,右上角显示已用时间,我们能不能让他们失效呢?
首先上CE:
先找雷数,
![](https://static-img.0xffff.one/RdAbwsbRvCQ7DB6NcLuvsKyELqslwDetKlj_tzqCdHQ/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5MDMw/LTI4MjY3OC1hN2Iz/MzU3ZTgxMGI5MTM2/ZTc4OGQ4NWZlMzk0/NGJkLnBuZw.jpg)
现在雷数为10,通过CE首次扫描找出一部分地址。
为了再次扫描缩小范围,我们标两面旗使雷数减少至8
![](https://static-img.0xffff.one/04XMqMlGu0AqxbYRMxBMhWeNaq3OpsjDZi8bNbGxYu0/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5MTY2/LTYxNTc5LWM3YzNi/YjVmYzg3NWRjZDc0/ZmNhNDk3ZWMxNjY1/ZWYucG5n.jpg)
再次扫描,此时CE只剩下一个结果即为雷数的内存地址
![](https://static-img.0xffff.one/6CrlvgtJjA4pO6QU_Xk2j1d37YSOXiaGsoMIAJUL_So/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5MDYy/LTIxMTk3OC1jN2Mz/YmI1ZmM4NzVkY2Q3/NGZjYTQ5N2VjMTY2/NWVmLnBuZw.jpg)
通过首次扫描与再次扫描确定了存储雷数的内存地址在0x01005194
![](https://static-img.0xffff.one/2E6Th75q-dG9XwI_EMoQhyGSZgWLhBfxZqECUuBraXQ/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4MzUy/LTQxMzY0My05NmZm/MDQ0NjYxZGQzNzc0/ZTQyZmY2ZjdjZjYx/YTIzLnBuZw.jpg)
再找时间,
因为时间在不停的变化,我们需要在OD上挂起线程
![](https://static-img.0xffff.one/_GyUB0C8cIUpnT5wt8T4LUkvfohFxK567G_QA3GcT_8/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4NzU1/LTQ4OTY2Ny0xMmFh/M2U3MDk4YWYxNjQ0/ZTc1YTI2ZDcyNWYz/NDA5LnBuZw.jpg)
现在时间停了下来,此时时间为13,CE便可首次扫描。
第二次再恢复挂起线程,此时时间为26
![](https://static-img.0xffff.one/MoLQhRn8a_4pNRuvOrrw8Xz0jVdHn9JdqKij81UBUGM/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5MzI2/LTgyOTgxMS00ZjI2/NmM0OTI2YTJjYmI0/MTYwNDhlNjdkYTQ1/MGE3LnBuZw.jpg)
我们便再可用CE两次扫描确定存储时间的内存地址0x0100579C (第二次再恢复挂起线程即可)
![](https://static-img.0xffff.one/-44xSugnsKhNUge5AEna_ok71sCc523_7ak2O3M5WOs/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4OTMy/LTEzNjI5NC05NTRl/ZTFkMzcyMjgwNmI4/NjEwOTcwOGFhMTJi/MjMzLnBuZw.jpg)
现在我们转到OD的内存窗口确认上两个内存位置
![](https://static-img.0xffff.one/NPGDAIjDrY4uJMZg2OuyD5fw0WMteQr5OFzFfGxQVPo/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4OTcz/LTEyMDczNS02ODJm/M2Q2YjE3MzU5Yjc2/Y2M2MzJmZGI2NjNk/ZThlLnBuZw.jpg)
![](https://static-img.0xffff.one/pc_xX8_tqJ2ocwXLmeRxxfYYjVKVf5qCyoOkyvFq0TE/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU4OTc2/LTgyMjA0MC00Zjgy/Yzc0Nzc0ZWM0ZGRl/NWQxZjhmMmI0MDQ2/ODEyLnBuZw.jpg)
数值确实与雷数与时间相等,这两个地址看来就是我们要找的。
为了不让这两个数值变化,我们需要找到改变这两个数值的代码并删除,我们便可在这两个内存位置设下断点
![](https://static-img.0xffff.one/2mThaviuqEdi-MgH0QOZhLRVB3wDYM16_aepuvf0CMc/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5NTcy/LTE5Mzc1Ny03NTc3/MmRkNDJhOWU2YjFi/NjVmNDAzYWQxMmU5/MWIwLnBuZw.jpg)
(此为雷数,时间一样)
设完断点后,F9运行程序,我们在扫雷上右键标一面旗,雷数便即将被更改,触发断点
![](https://static-img.0xffff.one/5VRPfw69SlBCImmknFYAHwECakCICaW1Zyvll5S_7hk/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5NzE3/LTI3NDQ3NS1iMzcy/YjUzOWY1OWU3NGIy/ZWU5NWIwZWEzYjRh/OWM1LnBuZw.jpg)
如图所示,第一行代码为将eax中的值加到地址0x01005194的值, eax的值显示为0xFFFFFFFF,即为-1,由此可见,这行代码即为雷数的改写代码。
我们将其删除,用nop覆盖
![](https://static-img.0xffff.one/4fcTRvnQdJeaRUWuAEDyk_Um4j8AYpyBVsqoZSbETAg/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzU5OTk4/LTk2Nzg3Ny1jMWEw/ZDQ3NWY1YjhiOGEw/Njk4NjM5NGJlNzM5/N2UyLnBuZw.jpg)
处理完了雷数,我们现在处理时间
同样在存储时间的地址0x0100579C上下内存断点,找到改写的代码位置
![](https://static-img.0xffff.one/ihtMZkoy1xsyZ7BmuGUQjX6TPXq0iq7CRNY0PS5A_dc/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzYwMTAz/LTgyNTYzLTQ0NzBj/MzVlOGQ0NDBmM2Fh/NGNiNjkzODVmYjJj/ODYucG5n.jpg)
第一行代码将0x100579C的值+1,显然就是改写时间的代码,我们也将其改为nop删除。
现在我们已经将两个功能都废除了,把改写后的代码复制到可执行文件后运行
![](https://static-img.0xffff.one/bwQaBD9HcODLaYRkF0o9wgOfzRNKNtv9PgHy6tdzv5M/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzYwMjUx/LTg2OTYwNi1kNzdk/YWE1MzMxOWM1Njgz/ZjFlZjhhNjc5YTY0/MWNhLnBuZw.jpg)
发现无论标多少雷,雷数依然不会改变,时间也仍然静止,功能成功废除
0x02寻找雷区,实现自动排雷
我们不免想到,这个雷区使怎么生成的呢,雷区又是存储在哪里呢?
我们每次启动游戏,雷区都是随机生成的,那么我们自然会想到程序会调用随机函数rand来生成雷区,那么程序生成雷区的代码段,必然会调用rand函数。换句话说,只要找到了rand,便找到了雷区的位置。
我们用IDA查看导入表确认
![](https://static-img.0xffff.one/hueNGUPFnU-t5UeGS26JV_TYzPEfT0NTBlxmVm-QYv0/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzczNTY2/LTUyNjg2Mi05NDhl/ZjgxYmZjYzhkMGZi/MzE0MjA5ODRhNDU4/Zjg5LnBuZw.jpg)
果然有rand函数
现在找到调用rand函数的代码位置
双击函数进入IDA VIEW-A,右键函数名查找交叉引用
![](https://static-img.0xffff.one/kS-Q_qU3NV1cDemEIfLawIv_a9iA41eHa2S5DgRcREQ/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2MzczOTQy/LTM5MTU3NS1pbWFn/ZS5wbmc.jpg)
![](https://static-img.0xffff.one/CpwfPpG4Yg2x-hgUFAhZixzH50P6FNRwd8bYf9Vgwm4/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2Mzc0MzUz/LTgxMjM5My1pbWFn/ZS5wbmc.jpg)
点ok跳转至引用函数,其位置在0x0100347C,我们把它改名为rand_caller
![](https://static-img.0xffff.one/O0beHoc_WxPUQrsuCehzxBFh-wNSP_H3ultaSryDNSM/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2Mzc0NTMw/LTUxOTY2Mi1pbWFn/ZS5wbmc.jpg)
我们解读一下代码
cdq
:将双字扩展为四字, 此处将EAX中的32位值扩展为64位,并分成两部分储存在EDX:EAX中。
idiv [esp+arg_0]
: 有符号除法操作,被除数储存在EDX:EAX, 除数为参数arg_0, 结果商存储在EAX, 余数存储在EDX
mov eax, edx
: 将存储在EDX中的余数放到EAX中,以便return。
可以看出此段代码的作用是,将rand函数生成的随机数取模运算,使最终返回的数值不大于参数arg_0。
我们继续查询rand_caller的交叉应用,看看是谁调用了这个函数.
![](https://static-img.0xffff.one/af3qIevPOdu0kEslxfCOyo_ObmIvb7HSLiivhrtT0TI/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2Mzc1NzQ5/LTE3NDIwMy1mZjA3/NmM4NGQ1MjMxMDk2/MDUxN2M5ZDFhMDAx/MjVlLnBuZw.jpg)
发现有两处相隔很近的调用,我们跟随查看
![](https://static-img.0xffff.one/12hydQjZOJZE5uuGx8l9MWvWx3tYuDRuTO3H3Kp3pfw/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2Mzc1ODQx/LTg4NTM3NC0xMDhk/MzBlOWUwMmRiY2I3/NGVkN2E4ZGRiNmJk/MWRhLnBuZw.jpg)
发现是位于0x010036C7的代码段调用的,我们同样进行分析 : (我把第二个push的位置调整了一下)
loc_10036C7: //这段代码起始于0x10036C7
push dword_1005334 // 将0x1005334处的数值推入栈中作为rand_caller的参数
call rand_caller // 调用rand_caller
mov esi, eax // 将rand_caller的返回值存入ESI中
inc esi // 将ESI中的返回值+1
push dword_1005338 // 将0x1005338处的数值推入栈中作为rand_caller的参数
call rand_caller // 再次调用rand_caller
inc eax // 将返回值+1
mov ecx, eax // 将返回值存入EAX
shl ecx, 5 // 将ECX中的返回值乘 2^5 = 32
// 此时ESI中存的是第一次rand_caller的返回值加1. ECX中是第二次的返回值加1乘32
test byte_1005340[ecx+esi], 80h // test将80h 与 基址 0x1005340加上偏移[ecx + esi] 的地址上的值做AND比对
jnz short loc_10036C7
//若值的第8位为1,则返回这段代码的头部重新运行,若为0,则继续执行下一块代码。
下一块代码(若值的第八位为0则执行此处):
shl eax, 5 // 应该是汇编产生的冗余代码
lea eax, byte_1005340[eax+esi] // 将上述值的地址存入EAX
or byte ptr [eax], 80h // 将此值的第八位设为1
dec dword_1005330 // 将地址0x1005330上的值减1,此值应该为循环变量
jnz short loc_10036C7 // 若变量不为0,则返回上块代码的头部(0x10036C7)
经过思考,这段代码的作用应该是设置雷区,雷区应该是存储在内存中的一个数组中,将随机生成指定范围的数作为偏移,以0x1005340作为基址置雷。
但这里需要注意一个地方,虽然0x1005340为基址,但偏移的最小值不是为0,即0x1005340并不是第一个地雷所在位置!
1 \le ESI < dword\_1005334 + 1
32 \le ECX < (dword\_1005338 ) * 32 + 1
如上,此为ESI与ECX的取值范围(分析可得),则ESI+ECX \ge 33, 第一个地雷的位置应该是0x1005340 + 33 = 0x1005340 + 0x21 = 0x1005361, 这就是我们要找的雷区位置!!
打开OD,F9运行后,到内存位置0x1005361看看
![](https://static-img.0xffff.one/wGWAayCWP-GMj-eEdLbXkef7gl7U1UjjAm9ySnLllSA/q:90/w:800/rt:fit/aHR0cHM6Ly9zdGF0/aWMuMHhmZmZmLm9u/ZS9hc3NldHMvZmls/ZXMvMjAyMC0xMS0y/Ni8xNjA2Mzc5MzU1/LTM2MDE4My1pbWFn/ZS5wbmc.jpg)
是这样的玩意
为了验证,我们得在上面分析过的建雷区位置0x10036C7下个断点,重新运行游戏,到达后摁F8逐句运行,观察内存的变化(此处略),发现确实是这段代码在这个位置设下雷区。
未完