因为作者本人是在学校的希冀平台上做的,so以下的准备部分的内容适合学校的希冀平台~
准备部分
objdump -d ctarget > c.txt
Part I : ctarget
level 1
ctarget的流程大概就是执行test函数,然后输入字符串。这里level 1的要求是调用touch1函数即可,因此我们需要找到ctarget里getbuf函数的反汇编。
打开刚刚生成的c.txt文件,输入
/getbuf
来查找getbuf方法的汇编代码
这里借用其他博主的图来表示的更清楚
getbuf函数开辟了24字节的栈空间,函数里有用户自定义的8字节的数组,用户在8字节内可以随便输入,但是输入超过23字节,就会覆盖到调用者栈帧最下面的返回地址。这里我们的是开辟了40个字节的空间,只要我们输入的字符串将调用者的返回地址覆盖成我们想要它返回的地址即可。我们来看touch1的反汇编
发现touch1的首地址是 0x4016d1,注意我们这里要用小端法,即输入的字符串要写成d1 16这样,开辟的24字节空间里面填什么无所谓。最后我们可以填成这样:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
d1 16 40
将该文件命名为1.1,之后执行代码
./hex2raw<1.1>1.1_raw
再运行
./ctarget<1.1_raw>
然后就🆗啦~
level 2
我们先打开c.txt,查看touch2的反汇编
/ touch2
大意还是一样的,调用touch2即成功。我们看见touch2传了一个参数,这个参数值如果等于cookie的话就成功。cookie的值在给定的文件cookie.txt里有,我的cookie是0x30bad0de。
回忆一下,函数的第一个参数放在 %rdi 寄存器里面,这里我们显然不能直接输入字符串,需要借助汇编语言来实现。具体解题思路如下:
- 将正常的返回地址设置成为注入代码的地址,这次注入直接在栈顶注入,即设置为%rsp
- cookie的值写在%rdi里
- 获取touch2的首地址,这个已经有了
- 要调用touch2,却不能用call jmp等命令,所以只能用ret弹出,在弹出之前要先把touch2地址压栈。
这里需要补充的是ret指令到底在做什么:
在CPU中有一个“PC”即程序寄存器,在 x86-64 中用%rip
表示,它时刻指向将要执行的下一条指令在内存中的地址。而ret
指令就相当于:
pop %rip
即把栈中存放的地址弹出作为下一条指令的地址。
因此我们的汇编代码为:
命令行里编译再查看其反汇编:
这三条指令地址我们就有了,就是每行反汇编最前面的一长串十六进制数字,接着我们去找%rsp在哪,借助gdb。
得到%rsp地址,然后写注入字符串就好了,字符串为 注入代码地址——无用字符——返回地址。
然后就可以啦~
level 3
本题与上题类似,不同点在于传的参数是一个字符串。先给出touch3
的C语言代码
void touch3(char *sval)
{
vlevel = 3; /* Part of validation protocol */
if (hexmatch(cookie, sval)) {
printf("Touch3!: You called touch3(\"%s\")\n", sval);
validate(3);
} else {
printf("Misfire: You called touch3(\"%s\")\n", sval);
fail(3);
}
exit(0);
}
touch3
中调用了hexmatch
,它的C语言代码为:
/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, char *sval)
{
char cbuf[110];
/* Make position of check string unpredictable */
char *s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
也就是说,要把cookie
转换成对应的字符串传进去
注意第6行,s
的位置是随机的,我们写在getbuf
栈中的字符串很有可能被覆盖,一旦被覆盖就无法正常比较。
因此,考虑把cookie
的字符串数据存在test
的栈上,其它部分与上题相同,这里不再重复思路。
注入代码为:
我们期望的栈帧为:
逻辑如下:
getbuf
执行ret
,从栈中弹出返回地址,跳转到我们注入的代码- 代码执行,先将存在
caller
的栈中的字符串传给参数寄存器%rdi
,再将touch3
的地址压入栈中 - 代码执行
ret
,从栈中弹出touch3
指令,成功跳转
因此最终solution为:
攻击成功~
Part II : rtarget
在第二部分中,我们要攻击的是rtarget
,它的代码内容与第一部分基本相同,但是攻击它却比第一部分要难得多,主要是因为它采用了两种策略来对抗缓冲区溢出攻击
- 栈随机化。这段程序分配的栈的位置在每次运行时都是随机的,这就使我们无法确定在哪里插入代码
- 限制可执行代码区域。它限制栈上存放的代码是不可执行的。
因此我们在这里需要使用的攻击策略是ROP:面向返回的程序设计,就是在已经存在的程序中找到特定的以ret
结尾的指令序列为我们所用,称这样的代码段为gadget
,把要用到部分的地址压入栈中,每次ret
后又会取出一个新的gadget
,于是这样就能形成一个程序链,实现我们的目的。
同时,有如下指令编码表:
level 1
本题的任务与Phase 2
相同,都是要求返回到touch2
函数,phase 2
中用到的注入代码为:
我们根本不可能找到这种带特定立即数的gadget
,只能思考其他办法。
首先,要做的是把 cookie 赋值给参数寄存器%rdi
,考虑将 cookie 放在栈中,再用指令:
pop %rdi
ret
就能实现参数的赋值了,当ret
后,从栈中取出来的程序地址再设置为touch2
的地址就能成功解决本题
但是后来发现在farm
中找不到这条指令的gadget
,因此我们考虑在其他寄存器进行中转,用两个gadget:
popq %rax
ret
###############
movq %rax, %rdi
ret
因此栈帧情况大致如下:
逻辑如下:
getbuf
执行ret
,从栈中弹出返回地址,跳转到我们的gadget01
gadget01
执行,将cookie
弹出,赋值给%rax
,然后执行ret
,继续弹出返回地址,跳转到gadget2
gadget2
执行,将cookie
值成功赋值给参数寄存器%rdi
,然后执行ret
,继续弹出返回地址,跳转到touch2
首要问题是找到我们需要的gadget
先用如下指令得到target
的汇编代码及字节级表示
objdump -d rtarget > rtarget.s
查表知,pop %rax
用58
表示,于是查找58:(
其中 90 表示“空”,可以忽略)
得到指令地址为0x401860
movq %rax, %rdi
表示为48 89 c7
,刚好能找到!
得到指令地址为0x401874
根据上图的栈帧,就能写出输入序列:
攻击成功~
level 2
本题的任务与Phase 3
相同,都是要求返回到touch3
函数
level 3
中用到的注入代码为:
其中0x556589c8
是栈中cookie
存放的地址。
而在本题中,栈的位置是随机的,把cookie
存放在栈中似乎不太现实,但是我们又不得不这样做,那么有什么办法呢?只能在代码中获取%rsp
的地址,然后根据偏移量来确定cookie
的地址。想到这,思路就明晰了。
而farm中可以用来相加赋值的汇编只有一个:
因此我们要给rdi和rsi赋值基址(rsp)和偏移
查表,movq %rsp, xxx
表示为48 89 xx
,查找一下发现有一个可用的gadget
还真找到了,48 89 e0
对应的汇编代码为
movq %rsp, %rax
而 movq %rax, xxx也只有一个 movq %rsp, %dri:
剩下部分流程与Phase 3
一致,大体思路如下:
- 先取得栈顶指针的位置
- 取出存在栈中得偏移量的值
- 通过
lea (%rdi,%rsi,1),%rax
得到 cookie 的地址 - 将 cookie 的地址传给
%rdi
- 调用
touch 3
由于gadget
的限制,中间的细节需要根据自己的情况尝试,就不再一一列举了,直接给出我的查找结果:
farm里面查找:
以上farm里的内容找到对应的rtarget里的地址:
因此我的序列是:
Pass( •̀ >< •́ )y