我就是一条懒狗,天天白天睡觉。
游戏辅助
本来应该是签到题,但是实际上是脑洞题。
出题人本意可能是考察远程注入,但是题目描述太过于模糊,显得太过于脑洞了。
给出两个程序,一个是游戏辅助(假的,仅含注册码的判断的相关代码),一个是破解这个游戏辅助的补丁。
运行游戏辅助时要求输入注册码,随便输入了一个,不正确。
然后打开补丁,等破解完成后,再次随便输入一个注册码,正确。
补丁的窗口输出:flag{md5(dec(What_you_found))}
游戏辅助的代码很简单,明文比较注册码是否和1_am_n0t_f1ag
相等。
游戏复制窗口中注册码输入1_am_n0t_f1ag
,可以成功注册,没看懂上面的dec()是啥意思,我以为是decrypt,解密的意思,但是比较过程是明文比较的,也没有加密的过程啊。后来题目描述更新了,说是dec是十进制的意思。我无语了。。。
将1_am_n0t_f1ag
取md5提交,并不正确。
正确的What_you_found
不在游戏辅助里面,而是在补丁里面。
看一下游戏补丁的部分代码:
1 | WriteProcessMemory(v4, (char *)&loc_401227 + 1, &Buffer, 2u, 0); |
第2个和第3个WriteProcessMemory分别写入5个数据,拿第3个WriteProcessMemory举个例子:
1 | v7 = 0x0F6E38BB; |
上图来自本题一血的炜昊大佬:
为什么运行游戏补丁后,游戏辅助中随便输入一个注册码就能通过检测了呢?我们再来看一下游戏辅助中的0x401228附近。
上上图中,游戏补丁向游戏复制的内存的0x401228中写入了nop nop,正好件将jnz short loc_40125c覆盖,loc_40125C是注册失败的分支。覆盖后,无论输入的字符串和注册码的验证结果是否相同,都不会跳转至失败的分支,而是继续往下运行,即注册成功的分支。
flag是啥?是md5(20f6e38),20fe38是游戏补丁中向游戏辅助的内存0x40101b处写入的数据。别问我问啥偏偏是这个数据,我也不知道,要是知道的话,我当时就做出来了。
考察远程注入这个知识点倒是挺好的,但是你倒是说明白flag到底是啥呀,What you found
,我found的数据可多了,我怎么知道是哪个。。。
simple
看起来似乎很简单,输入长度为16的字符串,进行32轮tea加密,比较密文。
虚假的解密脚本
v8, v9是明文->密文,v10每轮要减去0x61C8864661C88647或者加上0x9E3779B99E3779B9,注意需要手动维持溢出。v3和v4是中间变量,直接化简去掉即可。
轻松写出解密脚本:
1 | def ull(n): |
解出的输入应该为flag_is_not_here
,按照题目要求取md5提交,不正确。啊哈,怎么回事??
查看交叉引用,发现这个函数(sub_402436)并没有被call,只是mov而已:
先去call的sub_402880,这个函数贼复杂,直接动调吧。后来才知道,这个函数类似于脱壳,将代码解码出来。
动调
本想动调看看哪里有问题,对于这个ELF64,ida7.0不知道为啥动调失败,好在最后发现ida6.8可以动调它。
经过多次动调总结:在4110f0处按f4,输入长度16的字符串
图1是按f4前,图2是按f4并输入长度为16的字符串后。
按3次f8,即可来到解压出的新代码处,是个堆(heap):
其中有些奇奇怪怪的汇编指令:
dec eax频繁出现,而且这个ELF64的程序,汇编指令中没有出现任何一个rax之类的。出大问题。
我的猜测是:
1 | dec eax |
猜测的对不对我不清楚,反正我拿capstone反汇编的结果验证了我的猜测。
capstone 机器码->汇编代码
ida反汇编的结果太影响理解了。
idc脚本提取出这个堆里的代码:
1 | static main() |
使用capstone反汇编:
1 | from capstone import * |
输出的汇编代码为:
1 | push rbp |
capstone反汇编的结果中,跳转分支没有明确地指示,call的函数也和ida中的表示不太一样,不过对照着看也不是不行。
汇编分析
分析结果大体如下:
1 | push rbp |
Your input:
,right
,wrong
是如何输出的呢?
以right为例:
1 | movzx eax, byte ptr [rbp + rax - 0x1f] |
rax是索引,rbp - 0x1f开始的5个数据对应right。
取出数据后与0xee异或,比如第一个数据[rbp - 0x1f] = 0x9c,chr(0x9c ^ 0xee) = 'r'
后面的4个数据也是这样处理,可得字符串right。
加密流程
下面看一下整体代码的流程,配合多次动调分析:
1.首先是Your Input : rightwrong
这些字符所对应的加密数据入栈(数据经过解密处理后才可以得到明文)
2.输出Your Input :
3.将input传值到[rbp - 0x40] ~ [rbp - 0x31]
,我将var_40记为v8,var_38记为v9,以便和ida对应。
4.v28 = 0xbd044a8aad044a8a ^ 0x2333333333333333
5.tea加密,好像是变种,我不清楚。管它是原版还是变种,直接逆它的逻辑就行。共32次循环,每轮循环:
1 | v8 = v8 + ((v9 + ((v9 << 4) ^ (v9 >>5))) ^ v30) |
6.判断v8 == 0xad4bb459940692aa
, v9 == 0x5665fd4ec447c6c9
7.输出right或是wrong
8.exit
真正的解密脚本
1 | def ull(n): |
注意大小端,注意+
和^
的运算优先级。注意v30的改变是在v8和v9中间。