patch二进制文件的那些事

本文最后更新于:1 个月前

话说BUU上的这道题,我第一次自己做的时候也无异于网上诸多的解法

进行反编译代码分析,在get_flag()函数中比较直观地能发现,按正常应该是判断条件先为4,将f2进行一个初始赋值(追加)操作,然后判断条件为5,对f2进行一定的逻辑变化,最后判断条件再为1,输出flag

f1已经可以得知,于是重点就放在了f2上面

按照题目逻辑我写下了下述破解f2的小脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s = "7F666F6067756369"
# s =
decoded_s = bytes.fromhex(s)[::-1]

for i in range(0,len(decoded_s)):
# print(decoded_s[i],end='')
if i % 2 == 1:
# decoded_s[i] = decoded_s[i]
print(chr(decoded_s[i]-2),end='')
else:
# decoded_s[i] += 1
print(chr(decoded_s[i]-1),end='')

# print(decoded_s)

但是只有这一种方式吗?

后面尝试过一些简单的小patch后,再回来看这道题,我也打算通过这种方式来完成它

patch方案不难想到,既然我们需要判断条件按照4、5、1的顺序输出,那我们改一下他们的跳表就行了

判断条件switch(rand() % 200)怎么改?这不是有个现成的循环变量i可以用嘛.jpg

看汇编代码,下面这一段正式switch(rand%200)的逻辑,rand()%200的值存放在了[rbp+var_34]中,并加载到了eax寄存器中

这三行代码为switch的关键

rax*8是由于一段地址长8字节,乘以8之后将跳表中对应条件值的地址赋值给rax,最后再jmp到rax

eax是rax的低32位,所以此处rax*8是获取前面存入rax寄存器的值,即rand()%200的返回值

那么这里就非常明显了,将此处的[rbp_var_34]改为i即可

i的地址ida中已经很贴心地帮我们写出来了

所以此处我们如此patch

反编译回去,发现此处条件已经变为了i

下一步就是要对跳表操刀了

我们到跳表jpt_400843的位置,可以看到几个条件对应的地址了

loc_400845对应的case 1

loc_4008A2对应的case 2

loc_4008B6对应的case 3

loc_4008CA对应的case 4

loc_400909对应的case 5

因此这里我们只需要稍微改下前面提到的三个条件的顺序,让他们按照前面提到的顺序出现就行

更直观点,在跳表处按ctrl+tab来到hex视图,每8个字节就是一块地址,我们只需要修改对应的字节即可

我们试着调换一下CA和45的位置

成功更换了二者的位置!

下一步只需要把此时的case4和case5调换下位置即可

然后就出问题了,,没截下图来,是一个JUMPOUT的问题,并且逻辑也并不按照预期来

后面菜菜师傅给我提供了一个很棒的思路

直接按照预期的顺序依次jmp过去就行了

将case4中原本的break的jmp的目标地址改成case5的

成功

但是在将case5的break改成jmp到case1的起始地址就出问题了

此时patch界面爆红,什么意思呢?

实际上因为跳转地址超出short的范围而原本使用的是短跳指令jmp short导致的

原本的语句是:jmp short loc_400975 ; EB 0A,指令占2字节空间

但是jmp loc_400845 ; E9 XX XX XX XX 是 near jump,占5字节,导致下面原本正常的语句会被覆盖,导致出错