Loading... # squ1rrel CTF 2025 wp 这个比赛印象还挺深,因为有一道camelcamel第一次用pintool爆破出来,万幸是单字节加密;re最后被ak了,总共4道题 第一道安卓没做看了下很简单 ## Intermediate Software Design ida反编译分析c++算法即可 ~~~python s = list(b"Yu*4-v)mXzjzso$~0vX_&Z rB~") print(len(s)) c = [0x00000003, 0x00000003, 0x00000001, 0x00000005, 0x00000032, 0x00000008, 0x00000001, 0x00000009, 0x00000005, 0x00000007, 0x00000009, 0x00000003, 0x00000002, 0x00000002, 0x00000006, 0x00000008, 0x00000005, 0x0000001E, 0x00000006, 0x00000003, 0x00000003, 0x00000001, 0x00000005, 0x00000004, 0x00000008, 0x00000001, 0x00000009, 0x00000005, 0x00000007, 0x00000009, 0x00000003, 0x00000002, 0x00000002, 0x00000006, 0x00000008, 0x00000005, 0x00000004, 0x00000006, 0x00000003, 0x00000003, 0x00000001, 0x00000005, 0x00000004, 0x00000008, 0x00000001, 0x00000009, 0x00000005, 0x00000007, 0x00000009, 0x00000003, 0x00000002, 0x00000002, 0x00000006, 0x00000008, 0x00000005, 0x00000004, 0x00000006, 0x00000003, 0x00000003, 0x00000001, 0x00000005, 0x00000004, 0x00000008, 0x00000001, 0x00000009, 0x00000005, 0x00000007, 0x00000009, 0x00000003, 0x00000002, 0x00000002, 0x00000006, 0x00000008, 0x00000005, 0x00000004, 0x00000006] for i in range(len(s)): s[i] -= (c[i+13]-2) s[i] &= 0xff key = [0x2A, 0x51, 0x63, 0x4D, 0x22, 0x5C, 0x37, 0x7E, 0x19] for i in range(0, len(s), 2): s[i] ^= key[i//2%len(key)] print(list(map(hex, s))) print("".join(map(chr, s))) ~~~ ## Russian Games 勾八玩意,写的什么鬼逻辑 题目给了个russian-games.vm (下面的省略了简单函数),里面硬编码的数据经过转字符串发现nand2tetros,查询可知是一种抽象编程语言 ~~~ function Main.MMMMN 0 push argument 0 push argument 1 not and push argument 0 not push argument 1 and or return function Main.MMMMM 1 push argument 1 pop local 0 push argument 1 neg push constant 1 sub push argument 0 and pop local 0 push argument 1 push argument 0 not and push local 0 or return function Main.MMMNM 2 push argument 0 push argument 1 not pop local 0 not push argument 1 and pop local 1 push local 0 push argument 0 and pop local 0 push local 1 push local 0 or return function Main.MMNMM 0 push argument 0 push argument 1 not and push argument 0 not push argument 1 and or push constant 0 return function Main.MMNMN 1 push argument 0 push argument 1 push argument 0 not and push argument 1 not push argument 0 and or pop local 0 return function Main.MMNNM 1 push argument 1 push argument 1 push argument 0 not and push argument 1 not push argument 0 and or pop local 0 return function Main.NMNNM 0 push argument 0 pop pointer 1 push constant 110 pop that 0 push constant 97 pop that 1 push constant 110 pop that 2 push constant 100 pop that 3 push constant 50 pop that 4 push constant 116 pop that 5 push constant 101 pop that 6 push constant 116 pop that 7 push constant 114 pop that 8 push constant 111 pop that 9 push constant 115 pop that 10 push constant 0 pop that 11 push argument 0 return function Main.MMMNN 5 push argument 0 pop pointer 0 push argument 1 pop pointer 1 push argument 2 pop local 1 push constant 3 pop local 2 goto _____ label _____ push this 0 if-goto ____ goto . label ____ push that 0 if-goto ___ goto ______ label ______ push argument 1 pop pointer 1 label ___ push this 0 push that 0 call Main.MMMMN 2 push constant 94 call Main.MMMNM 2 push constant 132 call Main.MMNMN 2 push local 3 if-goto __ goto .__ label _ push local 2 push constant 31 add pop local 2 push local 2 call Main.MMMMM 2 push pointer 1 pop local 0 push local 1 pop pointer 1 pop that 0 goto .__. label .__. push pointer 0 push constant 1 add pop pointer 0 push pointer 1 push constant 1 add pop local 1 push local 0 push constant 1 add pop local 0 push local 0 pop pointer 1 goto _____ label . push argument 2 return label __ push local 3 push constant 1 add pop local 3 push pointer 1 pop local 0 push local 1 pop pointer 1 pop local 4 push local 4 push local 1 call Main.MMMMM 2 push local 1 call Main.MMMMM 2 push constant 431 call Main.MMMNM 2 pop that 0 push pointer 1 push constant 1 add pop local 1 push local 0 pop pointer 1 push local 4 goto _ label .__ push local 3 push constant 1 sub pop local 3 goto _ function Main.NNMNM 3 push argument 0 pop pointer 0 push constant 24576 pop pointer 1 push constant 0 pop local 0 push constant 0 goto ___ label ___ push that 0 pop local 2 push local 2 if-goto ____ pop local 1 push constant 0 goto ___ label ____ push local 2 push constant 128 eq if-goto _____ push local 2 eq not if-goto DIF push local 1 goto ___ label DIF push local 2 pop local 1 push local 1 pop this 0 push pointer 0 push constant 1 add pop pointer 0 push local 0 push constant 100 eq not if-goto ______ goto _____ label ______ push local 0 push constant 1 add pop local 0 push local 1 goto ___ label _____ push constant 0 pop this 0 push argument 0 return function Main.NNMNN 0 push argument 0 pop pointer 0 push constant 97 pop this 0 push constant 481 pop this 1 push constant 15 pop this 2 push constant 37 pop this 3 push constant 420 pop this 4 push constant 116 pop this 5 push constant 128 pop this 6 push constant 503 pop this 7 push constant 229 pop this 8 push constant 130 pop this 9 push constant 489 pop this 10 push constant 189 pop this 11 push constant 333 pop this 12 push constant 496 pop this 13 push constant 358 pop this 14 push constant 321 pop this 15 push constant 497 pop this 16 push constant 297 pop this 17 push constant 461 pop this 18 push constant 429 pop this 19 push constant 439 pop this 20 push constant 410 pop this 21 push constant 496 pop this 22 push constant 428 pop this 23 push constant 588 pop this 24 push constant 486 pop this 25 push constant 632 pop this 26 push constant 587 pop this 27 push constant 496 pop this 28 push constant 560 pop this 29 push constant 736 pop this 30 push constant 468 pop this 31 push constant 726 pop this 32 push constant 689 pop this 33 push constant 463 pop this 34 push constant 651 pop this 35 push constant 779 pop this 36 push constant 486 pop this 37 push constant 864 pop this 38 push constant 891 pop this 39 push constant 482 pop this 40 push constant 810 pop this 41 push constant 975 pop this 42 push constant 445 pop this 43 push constant 951 pop this 44 push constant 969 pop this 45 push constant 483 pop this 46 push constant 943 pop this 47 push argument 0 return function Main.NNNNN 0 push argument 0 pop pointer 0 push argument 1 pop pointer 1 label ___ push this 0 push that 0 eq not if-goto ____ push this 0 push constant 0 eq if-goto _____ push pointer 0 push constant 1 add pop pointer 0 push pointer 1 push constant 1 add pop pointer 1 goto ___ label ____ push constant 0 return label _____ push constant 0 push constant 0 eq return function Sys.init 0 push constant 2048 call Main.NNMNM 1 # 输入 push constant 4096 call Main.NMNNM 1 # key push constant 10240 call Main.MMMNN 3 # 加密 push constant 8192 call Main.NNMNN 1 # 返回要比较的数组 call Main.NNNNN 2 # 比较 pop pointer 0 label WHILE goto WHILE // loops infinitely ~~~ 代码给ds分析更清楚些(gpt分析有点拉),可以知道前面几个简短函数的功能,均是异或以及数组返回 ~~~python def MMMMN(a, b): return (a & (~b)) | ((~a) & b) def MMMMM(a, b): local0 = (-b - 1) & a return (b & ~a) | local0 def MMMNM(a, b): local0 = ~b local1 = ~a & b local0 = local0 & a return local1 | local0 def MMNMN(a, b): # 自己手动分析下,ds给的不对,local0相当于没用 local0 = (b&(~a))|((~b)&a) return a def MMNNM(a, b): # 自己手动分析下,ds给的不对,local0相当于没用 local0 = (b&(~a))|((~b)&a) return b def NMNNM(): s = ''.join(chr(c) for c in [110, 97, 110, 100, 50, 116, 101, 116, 114, 111, 115]) return s def NNMNM(): return input() def NNMNN(): data = [ 97, 481, 15, 37, 420, 116, 128, 503, 229, 130, 489, 189, 333, 496, 358, 321, 497, 297, 461, 429, 439, 410, 496, 428, 588, 486, 632, 587, 496, 560, 736, 468, 726, 689, 463, 651, 779, 486, 864, 891, 482, 810, 975, 445, 951, 969, 483, 943 ] return data def NNNNN(a, b): return a == b ~~~ Sys.init重点分析,可以知道输入、加密的位置,最关键的代码是MMMNN函数,里面很多跳转,同时调用了很多异或操作,下面是我一点点扣代码将逻辑转为了python,并使用了flag头作为测试 ~~~python def func(arg0, arg1, arg2): pointer0 = arg0 # this指针基地址 pointer1 = arg1 # that指针基地址 local_vars = [0, arg2, 3, 0, 0, 0] p0 = 0 p1 = 0 while True: # 标签_____ this0 = pointer0[p0] if this0 == 0: print(pointer0) return arg2 # 标签____ that0 = pointer1[p1] if that0 == 0: # 标签______: 重置pointer1到arg1 pointer1 = arg1 that0 = pointer1[0] p1 = 0 # 标签___: 计算异或结果 xor1 = this0 ^ that0 xor2 = xor1 ^ 94 res = xor2 # MMNMN返回第一个参数,忽略第二个参数132 # 检查local3 if local_vars[3] != 0: # 标签__: 处理local3非零的情况 local_vars[3] += 1 local_vars[0] = p1 p1 = local_vars[1] local_vars[4] = res xor3 = local_vars[4] ^ local_vars[1] xor4 = xor3 ^ local_vars[1] xor5 = xor4 ^ 431 pointer0[p0] = xor5 local_vars[1] = p1 + 1 p1 = local_vars[0] else: # 标签.__: local3减1 local_vars[3] -= 1 # 标签_: 处理local2和存储结果 local_vars[2] += 31 # 计算异或并存储到that0 xor_local = res ^ local_vars[2] # 调整指针和局部变量 local_vars[0] = p1 p1 = local_vars[1] if local_vars[3] != 0: # 函数里两个pop that经过测试发现只能取一个,所以这道题目有一个奇偶不同处理 pointer0[p0] = xor_local # 判断点在于local_vars[3],初始为0,然后-1然后0然后-1做不同操作 p0 += 1 local_vars[1] = p1 + 1 local_vars[0] += 1 p1 = local_vars[0] func(list(b"squ1rrel{\x00"), list(b"nand2tetros\x00"), 10240) ~~~ 输出终于让我看到了一点眉目 ~~~ [97, 481, 37, 420, 128, 503, 130, 489, 333, 0] [97, 481, 15, 37, 420, 116, 128, 503, 229, 130, 489, 189, 333, 496, 358, 321, 497, 297, 461, 429, 439, 410, 496, 428, 588, 486, 632, 587, 496, 560, 736, 468, 726, 689, 463, 651, 779, 486, 864, 891, 482, 810, 975, 445, 951, 969, 483, 943] ~~~ 和data对比发现每三个一组前两个对应flag加密完的结果,勾八的那第三个是干啥用的 解密就很简单了 ~~~python key = b"nand2tetros" data = [ 97, 481, 15, 37, 420, 116, 128, 503, 229, 130, 489, 189, 333, 496, 358, 321, 497, 297, 461, 429, 439, 410, 496, 428, 588, 486, 632, 587, 496, 560, 736, 468, 726, 689, 463, 651, 779, 486, 864, 891, 482, 810, 975, 445, 951, 969, 483, 943 ] new_data = [] for i in range(0, len(data), 3): new_data += data[i:i+2] data = new_data for i in range(len(data)): if i % 2 == 0: data[i] ^= (3+31*(i+1)) else: data[i] ^= 431 data[i] ^= 94 data[i] ^= key[i%len(key)] print("".join(map(chr, data))) ~~~ `squ1rrel{n4nd2t3tr1s_VM_1s_gr8!}` 做题思路:这题算是一种vm,见多了就知道,必须一步步分析,碰到控制流这种就得多测试数据,一点点调看输入输出怎么变化的 ## camelcamelcamel 读ida反编译代码读了一晚上,ocaml编译得,和haskell貌似一样的抽象语言 最后才想到可以单字节爆破,使用了pintool,魔改了下原来的inscount0.cpp脚本如下,使得其在运行程序完直接输出跑的指令数,需要编译成so文件,具体可以参考https://www.cnblogs.com/level5uiharu/p/16963907.html ~~~c++ /* * Copyright (C) 2004-2021 Intel Corporation. * SPDX-License-Identifier: MIT */ #include <iostream> #include <fstream> #include "pin.H" using std::cerr; using std::endl; using std::ios; using std::ofstream; using std::string; using namespace std; ofstream OutFile; static UINT64 icount = 0; VOID docount() { icount++; } VOID Instruction(INS ins, VOID* v) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END); } KNOB< string > KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "inscount.out", "specify output file name"); VOID Fini(INT32 code, VOID* v) { // 修改这里:删除"Count "字符串,直接输出icount cout << icount << endl; OutFile.close(); } INT32 Usage() { cerr << "This tool counts the number of dynamic instructions executed" << endl; cerr << endl << KNOB_BASE::StringKnobSummary() << endl; return -1; } int main(int argc, char* argv[]) { if (PIN_Init(argc, argv)) return Usage(); OutFile.open(KnobOutputFile.Value().c_str()); INS_AddInstrumentFunction(Instruction, 0); PIN_AddFiniFunction(Fini, 0); PIN_StartProgram(); return 0; } ~~~ 然后就要借助pwntool,这题其实比较幸运,只能输入40位否则报错,同时每次检查单字节走过的指令数相同 ~~~python import string from pwn import * import time context.log_level = 'error' # 减少调试输出 total_length = 40 # 优先使用常见字符提高效率,经测试主要是小写+数字+下划线+大写字母 charset = "_{}" + string.ascii_lowercase + string.digits + string.ascii_uppercase + "!?@#*+-" # prefix = "squ1rrel{0caml_1s_c00l_4nd_we1rd_nU8X3N" prefix = "s" s = "s"+"!"+"A"*38 p = process(["./pin -t inscount0.so -- ./camelcamelcamel"], shell=True) p.sendline(s.encode()) out = p.recvall().decode() print(s) print(out, end="") count = int(out.split("\n")[1]) max_count = count time_start = time.time() while len(prefix) < total_length: for c in charset: # 构造当前payload current = prefix + c current = current.ljust(total_length, 'A')[:total_length] # 执行Pin命令 p = process(["./pin -t inscount0.so -- ./camelcamelcamel"], shell=True) # 发送输入(不带换行符) p.sendline(current.encode()) out = p.recvall().decode() print(current) print(out, end="") count = int(out.split("\n")[1]) if len(prefix) != 39: if count > max_count + 30: # 测试发现每正确一个字符多执行39个指令,因此在这里多设置些防止pin有时候不准确 max_count = count prefix += c break else: if count > max_count: # 最后一个测试发现每正确一个字符多执行14个指令 max_count = count prefix += c break print(f"Bruteforce took {time.time()-time_start}s") ~~~ 挺优雅的解题思路hhh 这里还有一篇[wp](https://github.com/danlliu/squ1rrelctf-2025-writeups/blob/main/rev/camel/WRITEUP.md)直接分析ocamel代码的 最后修改:2025 年 06 月 23 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏