squ1rrel CTF 2025 wp
这个比赛印象还挺深,因为有一道camelcamel第一次用pintool爆破出来,万幸是单字节加密;re最后被ak了,总共4道题
第一道安卓没做看了下很简单
Intermediate Software Design
ida反编译分析c++算法即可
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分析有点拉),可以知道前面几个简短函数的功能,均是异或以及数组返回
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头作为测试
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加密完的结果,勾八的那第三个是干啥用的
解密就很简单了
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
/*
* 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位否则报错,同时每次检查单字节走过的指令数相同
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直接分析ocamel代码的