打美团 CTF 的时候才意识到,有一种坐牢叫AK
[MTCTF 4th]wow
先进行脱壳,因为脱壳后用的是绝对地址,而加载的时候是动态加载,所有没有办法调试,那就直接静态看吧
JUMPOUT
那里看一下汇编,发现有一个天堂之门
push 0x33
call $+5
add [esp+84h+var_84], 5
retf
直接把后面的数据全 dump 出来,然后用 ida64 打开
void __fastcall sub_0(unsigned int *a1)
{
unsigned int v1; // er9
int v2; // ebp
unsigned int v3; // esi
unsigned int *v4; // rdi
unsigned int *v5; // r10
unsigned int v6; // ebx
unsigned int i; // er11
unsigned int v8; // er8
int v9; // edx
unsigned int v10; // eax
unsigned int v11; // er9
unsigned int v12; // er8
int v13; // ebx
int v14; // ebx
int v15; // ebx
int v16; // edx
unsigned int v17; // eax
unsigned int v18; // er9
v1 = a1[8];
v2 = 12;
v3 = 0;
while ( 1 )
{
v3 += 1732584193;
v4 = a1 + 1;
v5 = a1;
v6 = v3 >> 2;
for ( i = 0; i < 8; ++i )
{
v8 = *v4;
if ( (((unsigned __int8)i ^ (unsigned __int8)v6) & 3) != 0 )
{
switch ( ((unsigned __int8)i ^ (unsigned __int8)v6) & 3 )
{
case 1:
v9 = (v1 >> 5) ^ (4 * v8);
v10 = v1;
v11 = v1 ^ 0x10325476;
break;
case 2:
v9 = (v1 >> 5) ^ (4 * v8);
v10 = v1;
v11 = v1 ^ 0x98BADCFE;
break;
case 3:
v9 = (v1 >> 5) ^ (4 * v8);
v10 = v1;
v11 = v1 ^ 0xC3D2E1F0;
break;
default:
goto LABEL_12;
}
}
else
{
v9 = (v1 >> 5) ^ (4 * v8);
v10 = v1;
v11 = v1 ^ 0xEFCDAB89;
}
*v5 += (v11 + (v3 ^ v8)) ^ (((16 * v10) ^ (v8 >> 3)) + v9);
v1 = *v5;
LABEL_12:
++v4;
++v5;
}
v12 = *a1;
v13 = ((unsigned __int8)i ^ (unsigned __int8)v6) & 3;
if ( !v13 )
{
v16 = (v1 >> 5) ^ (4 * v12);
v17 = v1;
v18 = v1 ^ 0xEFCDAB89;
goto LABEL_21;
}
v14 = v13 - 1;
if ( !v14 )
{
v16 = (v1 >> 5) ^ (4 * v12);
v17 = v1;
v18 = v1 ^ 0x10325476;
goto LABEL_21;
}
v15 = v14 - 1;
if ( !v15 )
{
v16 = (v1 >> 5) ^ (4 * v12);
v17 = v1;
v18 = v1 ^ 0x98BADCFE;
LABEL_21:
a1[8] += (v18 + (v3 ^ v12)) ^ (((16 * v17) ^ (v12 >> 3)) + v16);
v1 = a1[8];
goto LABEL_22;
}
if ( v15 == 1 )
{
v16 = (v1 >> 5) ^ (4 * v12);
v17 = v1;
v18 = v1 ^ 0xC3D2E1F0;
goto LABEL_21;
}
LABEL_22:
if ( !--v2 )
__asm { retfq }
}
}
是一个 xxtea,用 switch 来判断使用哪个 key,round 是 12,所以直接用脚本解密就行
#include <stdio.h>
#include <stdint.h>
#define DELTA 0x67452301
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(uint32_t *v, int n, uint32_t const key[4])
{
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
printf("No use");
}
else if (n < -1) /* Decoding Part */
{
n = -n;
// rounds = 6 + 52/n;
rounds = 12;
sum = rounds*DELTA;
y = v[0];
do
{
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--)
{
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
}
while (--rounds);
}
}
int main()
{
unsigned int v[10] = {
3640088821u,
1382566363u,
3805750627u,
1214181292u,
1620003782u,
1482291050u,
2956289443u,
1044419009u,
3554368410u,
0u
};
uint32_t const k[4]= {0xEFCDAB89u, 0x10325476u, 0x98BADCFEu, 0xC3D2E1F0u};
int n= -9; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
btea(v, n, k);
printf("%s\n", v);
return 0;
}
[MTCTF 4th]Superflat
这个题用了 movfuscator,正好前段时间有人在群里提过,一下就想起来了
但要解混淆需要装一个 demov 的工具,配置环境有点复杂
考虑到这个程序中没有任何的动态链接过程,而且能看到两个函数名 getchar
和 putchar
,那大胆猜测是按照每一位来判断的,可以考虑上 pintools
先手动测试了一下,发现对于同样的输入,执行的指令数字完全相同,并且每正确一位会增加 4888 条指令,于是根据 pintools 这个工具自己写个爆破脚本
在爆破前看了一下 output 文件,发现总长度是42位,于是直接猜测 flag 格式为 flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
其中 x 为 [0-9a-f] (小写字母是手动爆破了第一位确定的)
import subprocess
password = "flag{d06f0bcc-xxxxxxxxxxxxxxxxxxxxxxxxxxx}"
cur_pos = 14
last_count = 693225
while cur_pos < len(password) - 1:
for i in "0123456789abcdef-":
command = "echo " + password[:cur_pos] + i + password[cur_pos+1:] + " | ./pin-3.18-98332-gaebd7b1e6-gcc-linux/pin -t ./pin-3.18-98332-gaebd7b1e6-gcc-linux/source/tools/ManualExamples/obj-ia32/inscount0.so -- ./superflat; cat inscount.out"
output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
count = int(output.split(b'\n')[1].split()[1].decode())
if count - last_count > 0:
print (i)
password = password[:cur_pos] + i + password[cur_pos + 1:]
cur_pos += 1
last_count = count
break
else:
print ("\nerror:", cur_pos)
break
# flag{d06f0bcc-93e0-9c5b-161e-e1464176d395}
第一次在比赛中用上 pintool,纪念一下
[SECCONCTF 2021]dis-me
python 逆向,一上线就被叫来做这题了(属于是把 python 题目包场了)
先自己看了眼字节码,然后判断了一下,删了一些东西,再 pycdc 反编译,同时结合着自己的分析,拿到了反编译结果
import marshal
import base64
import types
f = open(__file__, 'rb')
f.seek(12)
s = marshal.loads(f.read()).co_code[4:]
types.FunctionType(marshal.loads(base64.b64decode(bytes([((y - x * 7 - 45) % 256) for x, y in enumerate((s[2 : 2 + s[0] * 256 + s[1]]))]))), globals())()
del s
del f
del marshal
del base64
del types
return None
读取了自身文件,然后偏移改到了 12,就是跳过了文件头,直接从 E3 00 00 00
开始,是 marshal
的正确格式(但自己写的时候有一些问题,不太懂了)
接下来 .co_code[4:]
猜测是取了代码段,并从下标为 4 的地方开始取
s[2 : 2 + s[0] * 256 + s[1]]
:接下来根据取出数据的前两位来判断接下来取出数据的总长度,并且跳过了前两位
于是最终取出的数据为 0x2C
开始的数据,一直取到 64 00 64 01
这个正确程序的开头部分
types 那一行里面的 bytes([((y - x * 7 - 45) % 256) for x, y in enumerate((s[2 : 2 + s[0] * 256 + s[1]]))])
一开始并没有分析出来,主要是位置不确定,于是自己拿数据试了试可能的几个方案,发现这样可以得到 base64的结果,就说明是这个了
将 base64 的结果解码到文件,然后补全文件头后 uncompyle6 反编译,得到正确的流程
f = lambda n: n if n <= 1 else (f(n - 1) + f(n - 2)) % 10
flag = input('Input the flag > ')
s = 1
if flag.startswith('SECCON{'):
if flag.endswith('}'):
if len(flag) == 40:
s = sum(abs(ord(c) - ord(str(f(i)))) for i, c in enumerate(flag[7:-1]))
s or print('Correct! The flag is', flag)
else:
print('Wrong :(')
直接把 str(f(i))
跑一遍就拿到 flag 了(斐波那契)
[SECCONCTF 2021]flag
这道题是个 wasm 逆向,人生第一次做出来,值得纪念
先从网站上把 wasm 文件下载下来
第一步肯定是 wasm2c 一下
./wasm2c 0001d242.wasm -o out.c
接下来进行编译
g++ -c out.c -o out.o
用 ida 打开,发现一个叫 check 的函数,到网站的 js 里面动调到 check,可以获取加载的数据
check(input, "NekoPunch", "6dbf84f73cf6a112268b09525ea550a665e21cb2e3e13af7e3ea0ecb52f5b9cda5b6522b1e978734553f1d7956d4af94bfc3f4d68c8fba9eeecf4035550b9106f70d57d1a6cdaf3211eaaa78d71a9038b71be621241e8b608a43b107f8860f543ab0189aa063800de4bae7d0b11045b8")
于是看一下 check 函数就行
v6 = w2c_g0;
i32_store(&w2c_memory, (unsigned int)w2c_g0 + 40LL, a1);
// 省略
v7 = i32_load(&w2c_memory, v6 + 40LL);
v8 = i32_load(&w2c_memory, v6 + 40LL);
v9 = w2c_f25(v8) - 1;
v52 = w2c_f21(v7, v9);
if ( (char)i32_load8_u(&w2c_memory, v52) == '}'
wasm 是基于堆栈的,这里明显能看到栈的痕迹,上来先把第一个参数(输入字符串的地址)存到了 v6+40
的地方(猜测v6是栈顶)
通过第一条 if 比较,猜测 v52 是输入的最后一位,这样就可以知道 w2c_f25
是计算长度,w2c_f21
相当于 a1[a2]
接下来注意到中间有一个非常整齐的循环
while ( (int)i32_load(&w2c_memory, v6 + 12LL) < 128 )
{
v22 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v22, 0, 4u, 8u, 0xCu);
v23 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v23, 5u, 9u, 0xDu, 1u);
v24 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v24, 0xAu, 0xEu, 2u, 6u);
v25 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v25, 0xFu, 3u, 7u, 0xBu);
v26 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v26, 0, 1u, 2u, 3u);
v27 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v27, 5u, 6u, 7u, 4u);
v28 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v28, 0xAu, 0xBu, 8u, 9u);
v29 = i32_load(&w2c_memory, v6 + 28LL);
w2c_f23(v29, 0xFu, 0xCu, 0xDu, 0xEu);
v57 = i32_load(&w2c_memory, v6 + 12LL) + 1;
i32_store(&w2c_memory, v6 + 12LL, v57);
}
显然 v6+12
的地方存的是循环次数,这个格式和前段时间学的 salsa20
很像
查看 w2c_f23
函数
i32_store(&w2c_memory, (unsigned int)(w2c_g0 - 32) + 28LL, a1);
i32_store(&w2c_memory, v9 + 24LL, a2);
i32_store(&w2c_memory, v9 + 20LL, a3);
i32_store(&w2c_memory, v9 + 16LL, a4);
i32_store(&w2c_memory, v9 + 12LL, a5);
v10 = i32_load(&w2c_memory, v9 + 28LL);
v11 = i32_load(&w2c_memory, v9 + 24LL) + v10;
v12 = i32_load8_u(&w2c_memory, v11);
v13 = i32_load(&w2c_memory, v9 + 28LL);
v14 = i32_load(&w2c_memory, v9 + 12LL) + v13;
v15 = i32_load8_u(&w2c_memory, v14);
v16 = i32_load(&w2c_memory, v9 + 28LL);
v17 = i32_load(&w2c_memory, v9 + 24LL) + v16;
v18 = i32_load8_u(&w2c_memory, v17);
v19 = i32_load(&w2c_memory, v9 + 28LL);
v20 = i32_load(&w2c_memory, v9 + 12LL) + v19;
v21 = i32_load8_u(&w2c_memory, v20);
v22 = i32_load(&w2c_memory, v9 + 28LL);
v23 = i32_load(&w2c_memory, v9 + 20LL) + v22;
v24 = (((int)(unsigned __int8)(v21 + v18) >> 7) | (2 * (unsigned __int8)(v15 + v12))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v23);
i32_store8(&w2c_memory, v23, v24); // x[b] ^= ROL((x[d] + x[a]), 1)
v25 = i32_load(&w2c_memory, v9 + 28LL);
v26 = i32_load(&w2c_memory, v9 + 20LL) + v25;
v27 = i32_load8_u(&w2c_memory, v26);
v28 = i32_load(&w2c_memory, v9 + 28LL);
v29 = i32_load(&w2c_memory, v9 + 24LL) + v28;
v30 = i32_load8_u(&w2c_memory, v29);
v31 = i32_load(&w2c_memory, v9 + 28LL);
v32 = i32_load(&w2c_memory, v9 + 20LL) + v31;
v33 = i32_load8_u(&w2c_memory, v32);
v34 = i32_load(&w2c_memory, v9 + 28LL);
v35 = i32_load(&w2c_memory, v9 + 24LL) + v34;
v36 = i32_load8_u(&w2c_memory, v35);
v37 = i32_load(&w2c_memory, v9 + 28LL);
v38 = i32_load(&w2c_memory, v9 + 16LL) + v37;
v39 = (((int)(unsigned __int8)(v36 + v33) >> 6) | (4 * (unsigned __int8)(v30 + v27))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v38);
i32_store8(&w2c_memory, v38, v39); // x[c] ^= ROL((x[a] + x[b]), 2)
v40 = i32_load(&w2c_memory, v9 + 28LL);
v41 = i32_load(&w2c_memory, v9 + 16LL) + v40;
v42 = i32_load8_u(&w2c_memory, v41);
v43 = i32_load(&w2c_memory, v9 + 28LL);
v44 = i32_load(&w2c_memory, v9 + 20LL) + v43;
v45 = i32_load8_u(&w2c_memory, v44);
v46 = i32_load(&w2c_memory, v9 + 28LL);
v47 = i32_load(&w2c_memory, v9 + 16LL) + v46;
v48 = i32_load8_u(&w2c_memory, v47);
v49 = i32_load(&w2c_memory, v9 + 28LL);
v50 = i32_load(&w2c_memory, v9 + 20LL) + v49;
v51 = i32_load8_u(&w2c_memory, v50);
v52 = i32_load(&w2c_memory, v9 + 28LL);
v53 = i32_load(&w2c_memory, v9 + 12LL) + v52;
v54 = (((int)(unsigned __int8)(v51 + v48) >> 5) | (8 * (unsigned __int8)(v45 + v42))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v53);
i32_store8(&w2c_memory, v53, v54); // x[d] ^= ROL((x[b] + x[c]), 3)
v55 = i32_load(&w2c_memory, v9 + 28LL);
v56 = i32_load(&w2c_memory, v9 + 12LL) + v55;
v57 = i32_load8_u(&w2c_memory, v56);
v58 = i32_load(&w2c_memory, v9 + 28LL);
v59 = i32_load(&w2c_memory, v9 + 16LL) + v58;
v60 = i32_load8_u(&w2c_memory, v59);
v61 = i32_load(&w2c_memory, v9 + 28LL);
v62 = i32_load(&w2c_memory, v9 + 12LL) + v61;
v63 = i32_load8_u(&w2c_memory, v62);
v64 = i32_load(&w2c_memory, v9 + 28LL);
v65 = i32_load(&w2c_memory, v9 + 16LL) + v64;
v66 = i32_load8_u(&w2c_memory, v65);
v67 = i32_load(&w2c_memory, v9 + 28LL);
v68 = i32_load(&w2c_memory, v9 + 24LL) + v67;
v69 = (((int)(unsigned __int8)(v66 + v63) >> 4) | (16 * (unsigned __int8)(v60 + v57))) ^ (unsigned __int8)i32_load8_u(&w2c_memory, v68);
i32_store8(&w2c_memory, v68, v69); // x[a] ^= ROL((x[c] + x[d]), 4)
这个东西套到 salsa20
的模板里就长这个样子
#define QUARTERROUND(x, a, b, c, d) \
x[b] ^= ROL(x[a] + x[d], 1); \
x[c] ^= ROL(x[b] + x[a], 2); \
x[d] ^= ROL(x[c] + x[b], 3); \
x[a] ^= ROL(x[d] + x[c], 4);
于是那个 while 循环就是这个
for(int i = 0; i < 128; i++){
QUARTERROUND(v6+28LL, 0, 4, 8, 12);
QUARTERROUND(v6+28LL, 5, 9, 13, 1);
QUARTERROUND(v6+28LL, 10, 14, 2, 6);
QUARTERROUND(v6+28LL, 15, 3, 7, 11);
QUARTERROUND(v6+28LL, 0, 1, 2, 3);
QUARTERROUND(v6+28LL, 5, 6, 7, 4);
QUARTERROUND(v6+28LL, 10, 11, 8, 9);
QUARTERROUND(v6+28LL, 15, 12, 13, 14);
}
接下来看一下验证部分
i32_store(&w2c_memory, v6 + 8LL, 0LL);
while ( (int)i32_load(&w2c_memory, v6 + 8LL) < 16 )
{
v30 = i32_load(&w2c_memory, v6 + 32LL);
v31 = 4 * i32_load(&w2c_memory, v6 + 20LL);
v32 = 2 * i32_load(&w2c_memory, v6 + 8LL) + v31;
v33 = w2c_f21(v30, v32);
v34 = i32_load8_u(&w2c_memory, v33);
v35 = i32_load(&w2c_memory, v6 + 28LL);
v36 = i32_load(&w2c_memory, v6 + 8LL) + v35;
v55 = (unsigned __int8)i32_load8_u(&w2c_memory, v36);
v37 = i32_load8_u(&w2c_memory, v55 / 16 + 3504LL);
v38 = (v34 != v37) | (unsigned int)i32_load(&w2c_memory, v6 + 24LL);
i32_store(&w2c_memory, v6 + 24LL, v38);
v39 = i32_load(&w2c_memory, v6 + 32LL);
v40 = 4 * i32_load(&w2c_memory, v6 + 20LL);
v41 = 2 * i32_load(&w2c_memory, v6 + 8LL) + v40;
v42 = w2c_f21(v39, v41 + 1);
v43 = i32_load8_u(&w2c_memory, v42);
v44 = i32_load(&w2c_memory, v6 + 28LL);
v45 = i32_load(&w2c_memory, v6 + 8LL) + v44;
v3 = (unsigned __int8)i32_load8_u(&w2c_memory, v45) % 16;
v46 = i32_load8_u(&w2c_memory, v3 + 3504LL);
v47 = (v43 != v46) | (unsigned int)i32_load(&w2c_memory, v6 + 24LL);
i32_store(&w2c_memory, v6 + 24LL, v47);
v58 = i32_load(&w2c_memory, v6 + 8LL) + 1;
i32_store(&w2c_memory, v6 + 8LL, v58);
}
v59 = i32_load(&w2c_memory, v6 + 20LL) + 8;
i32_store(&w2c_memory, v6 + 20LL, v59);
}
v48 = i32_load(&w2c_memory, v6 + 28LL);
w2c_free(v48);
v60 = i32_load(&w2c_memory, v6 + 24LL);
i32_store(&w2c_memory, v6 + 44LL, v60);
}
else
{
LABEL_8:
i32_store(&w2c_memory, v6 + 44LL, 0xFFFFFFFFLL);
}
v49 = i32_load(&w2c_memory, v6 + 44LL);
w2c_g0 = v6 + 48;
--wasm_rt_call_stack_depth;
return v49;
}
很显然 v6+44
和 v6+24
就是判断是否通过验证的,只要每次 (v43 != v46)
和 (v34 != v37)
不成立就行
根据之前的经验,v6+32
是第三个参数(密文字符串地址),v6+20
是最外层循环的索引(每次 +8),v6+8
是这层循环的次数,v6+28
是上一个循环加密后的结果
于是把这个循环简化一下
for (int i = 0; i < len(input); i++){
// ...
for (int j = 0; j < 16; j++) {
v30 = a3;
v31 = 4 * i;
v32 = 2 * j + v31;
v33 = &v30[v32];
v34 = *v33;
v35 = cipher;
v36 = &cipher[i];
v55 = *v36;
v37 = loc_3504[v55 / 16]; // 3504 中存的是 0-9a-f
res |= (v34 != v37);
// 后半部分相似
}
}
再简化一下就是
for (int i = 0; i < strlen(input); i++) {
// ...
for (int j = 0; j < 16; j++) {
v34 = a3[4 * i + 2 * j];
v37 = loc_3504[cipher[i] / 16];
v43 = a3[4 * i + 2 * j + 1];
v46 = loc_3504[cipher[i] % 16];
res |= (v34 != v37) | (v43 != v46);
}
}
所以只需要让加密结果转 hex 后等于密文即可
同样的方法,翻译一下第一个 while 循环
for (int j = 0; j < 8; j++) {
cipher[8 + j] = input[i + j];
}
于是想要找一下前八位是什么,发现
v12 = i32_load(&w2c_memory, v6 + 36LL);
v13 = w2c_f20(v12);
i32_store(&w2c_memory, v6 + 28LL, v13);
于是猜是把第二个参数的前 8 字节存到了 cipher 的前 8 字节
这样就分析完了整个加密流程,每次取 8 字节明文,使用类似 salsa
加密的核心算法,生成的结果与 hexstring 进行比较
#include <stdio.h>
#include <stdint.h>
#define ROL(a,b) (((a) << (b)) | ((a) >> (8 - (b))))
#define REQUARTERROUND(x, a, b, c, d) \
x[a] ^= ROL((x[d] + x[c]) & 0xff, 4); \
x[d] ^= ROL((x[c] + x[b]) & 0xff, 3); \
x[c] ^= ROL((x[b] + x[a]) & 0xff, 2); \
x[b] ^= ROL((x[a] + x[d]) & 0xff, 1);
using namespace std;
void decrypt(unsigned char * in){
for(int i = 0; i < 128; i++){
REQUARTERROUND(in, 15, 12, 13, 14);
REQUARTERROUND(in, 10, 11, 8, 9);
REQUARTERROUND(in, 5, 6, 7, 4);
REQUARTERROUND(in, 0, 1, 2, 3);
REQUARTERROUND(in, 15, 3, 7, 11);
REQUARTERROUND(in, 10, 14, 2, 6);
REQUARTERROUND(in, 5, 9, 13, 1);
REQUARTERROUND(in, 0, 4, 8, 12);
}
}
int main(){
unsigned char enc[] = {
109u, 191u, 132u, 247u, 60u, 246u, 161u, 18u,
38u, 139u, 9u, 82u, 94u, 165u, 80u, 166u,
101u, 226u, 28u, 178u, 227u, 225u, 58u, 247u,
227u, 234u, 14u, 203u, 82u, 245u, 185u, 205u,
165u, 182u, 82u, 43u, 30u, 151u, 135u, 52u,
85u, 63u, 29u, 121u, 86u, 212u, 175u, 148u,
191u, 195u, 244u, 214u, 140u, 143u, 186u, 158u,
238u, 207u, 64u, 53u, 85u, 11u, 145u, 6u,
247u, 13u, 87u, 209u, 166u, 205u, 175u, 50u,
17u, 234u, 170u, 120u, 215u, 26u, 144u, 56u,
183u, 27u, 230u, 33u, 36u, 30u, 139u, 96u,
138u, 67u, 177u, 7u, 248u, 134u, 15u, 84u,
58u, 176u, 24u, 154u, 160u, 99u, 128u, 13u,
228u, 186u, 231u, 208u, 177u, 16u, 69u, 184u
}; // 先用 python 把 hexstring 转换回来
for (int i = 0; i < 7; i++)
decrypt(enc + i * 16);
for(int i = 0; i < 7; i++)
for (int j = 8; j < 16; j++)
printf("%c", enc[16 * i + j]);
printf("\n");
return 0;
}
[SECCONCTF 2021]qchecker
这道题是 ruby 混淆
程序的空格会拼成 SECCON 的字样
测试后发现错误的输入会让程序拼成 WRONG. 的字样
整理一下格式,理解程序
eval$uate=%w(
a=%(eval$uate=%w(#{$uate})*"");
Bftjarzs=b=->a{a.split(?+).map{|b|b.to_i(36)}};
c=b["awyiv4fjfkuu2pkv+awyiv4fvut716g3j+axce5e4pxrogszr3+5i0omfd5dm9xf9q7+axce5e4khrz21ypr+5htqqi9iasvmjri7+axcc76i03zrn7gu7+cbt4m8xybr3cb27+1ge6snjex10w3si9+1k8vdb4fzcys2yo0"];
d,e,f,g,h,i=b["0+0+zeexaxq012eg+k2htkr1olaj6+3cbp5mnkzllt3+2qpvamo605t7j"];
(j=eval(?A<<82<<71<<86)[0])
&& d==0
&& (
e+=1;k=2**64;l=->(a,b){(a-j.ord)*256.pow(b-2,b)%b};
f=l[f,k+13];
g=l[g,k+37];
h=l[h,k+51];
i=l[i,k+81];
j==?}&&(d=e==32&&f+g+h+i==0?2:1);
a.sub!(/"0.*?"/,'"0'+[d,e,f,g,h,i].map{|x|x.to_s(36)}*?+<<34)
);
srand(f);
k=b["7acw+jsjm+46d84"];
l=d==2?7:6;
m=[?#*(l*20)<<10]*11*"";
l.times{
|a|b=d==0&&e!=0?rand(4):0;
9.times{
|e|9.times{
|f|(c[k[d]/10**a%10]>>(e*9+f)&1)!=0
&&(
g=f;
h=e;
b.times{
g,h=h,8-g
};
t=(h*l+l+a)*20+h+g*2+2;
m[t]=m[t+1]=""<<32
)
}
}
};
a.sub!(/B.*?=/,"B=");
n=m.count(?#)-a.length;
a.sub!("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");
o=0;
m.length.times{|b|m[b]==?#&&o<a.length&&(m[b]=a[o];o+=1)};
puts(m)
) * ""
先运行了几份代码做对比,发现区别仅在于 d,e,f,g,h,i=b[];
这一行,而且只有拼成 WRONG 的时候,d 才是 1,e 则不断自增,其余部分的程序完全一样,根据 d==2?7:6;
猜测,当 d 为 2 的时候正确,因为 SECCON 和 WRONG. 都是 6 个字符,完全符合 d 不为 2 的情况
根据这个猜测,srand(f);
以下的代码是负责控制空格样式的,和验证没有关系,不许管
我们需要关注的只有如下函数
d,e,f,g,h,i=b["0+0+zeexaxq012eg+k2htkr1olaj6+3cbp5mnkzllt3+2qpvamo605t7j"];
(j=eval(?A<<82<<71<<86)[0])
&& d==0
&& (
e+=1;k=2**64;l=->(a,b){(a-j.ord)*256.pow(b-2,b)%b};
f=l[f,k+13];
g=l[g,k+37];
h=l[h,k+51];
i=l[i,k+81];
j==?}&&(d=e==32&&f+g+h+i==0?2:1);
a.sub!(/"0.*?"/,'"0'+[d,e,f,g,h,i].map{|x|x.to_s(36)}*?+<<34)
);
根据测试 j=eval(?A<<82<<71<<86)[0]
是获取命令行参数,也就是当前传入的字符
l
函数就是 (a - ord(j)) * invmod(256) % b
把这个程序倒过来就是 (a * 256 + ord(j)) % b
如果不考虑 % b
,最后得到的就是类似 n2s(flag[::-1])
的效果
所以 f, g, h, i
的初值就是 n2s(flag[::-1]) % (k+?)
,标准的中国剩余定理
a = [4659461645708163688, 2641556351334323346, 15837377083725718695, 12993509283917003551]
m = [(2 ** 64 + 13) , (2 ** 64 + 37), (2 ** 64 + 51), (2 ** 64 + 81) ]
def gcd(x, y):
while y != 0:
x, y = y, x % y
return abs(x)
def ex_gcd(x, y):
list_x = [x, 1, 0]
list_y = [y, 0, 1]
while list_y[0] != 0:
r = list_x[0] // list_y[0]
for i in range(3):
list_x[i] -= r * list_y[i]
list_x, list_y = list_y, list_x
if list_x[0] < 0:
list_x[0], list_x[1], list_x[2] = -list_x[0], -list_x[1], -list_x[2]
return list_x
def inverse(a, n):
if n < 2:
raise ValueError("n < 2, error")
g, x, y = ex_gcd(a, n)
if g != 1:
print ("gcd(a, n) != 1, no inverse modular")
return x % n
def ex_crt(a, m, k):
for i in range(1, k):
t = gcd(m[i], m[i - 1])
if (a[i] - a[i - 1]) % t != 0:
return -1
a[i] = (inverse(m[i - 1] // t, m[i] // t) * (a[i] - a[i - 1]) // t) % (m[i] // t) * m[i - 1] + a[i - 1]
m[i] = m[i] // t * m[i - 1]
a[i] = (a[i] % m[i] + m[i]) % m[i]
return a[-1]
from libnum import *
n2s(ex_crt(a, m, 4))[::-1]
# b'SECCON{L3t5_wr1t3_y0ur_Qu1n3!!!}'
[idek]reverseme
源程序是
#include <stdio.h>
#include <string.h>
#include <math.h>
int main(int argc, char** argv) {
if(argc != 2){
printf("Usage: ./reverseme password\n");
return 1;
}
if(strlen(argv[1])!=14){
printf("Incorrect Length\n");
return 1;
}
if(*argv[1] != 112){//Not enough precision
printf("Password Incorrect\n");
return 1;
}
double magic_numbers[7] ={
-68822144.50341525673866271972656250000000000000000000000000,
56777293.39031631499528884887695312500000000000000000000000,
-3274524.75536667229607701301574707031250000000000000000000,
-85761.51255339206545613706111907958984375000000000000000,
8443.33244327564352715853601694107055664062500000000000,
-166.67369627952575683593750000000000000000000000000000,
1.00000000000000000000000000000000000000000000000000,
};
for(int i = 0; i < 6;i++){
double foo=1.0,bar=0.0;
for(int j=0;j<7;j++){
bar += magic_numbers[j] * foo;
foo *= (float)log(*(float*)((unsigned long)argv[1]+2*i));
}
if((int)(100000*bar) != 0){
printf("Password Incorrect\n");
return 0;
}
}
printf("Password Correct\n");
return 0;
}
相同的六次方程,分别带入 flag[0:4], flag[2:6], flag[4:8]...
,知道第一位,于是考虑直接爆破
先修改一下源代码
#include <stdio.h>
#include <string.h>
#include <math.h>
int main(int argc, char** argv) {
if(argc != 2){
printf("Usage: ./reverseme password\n");
return 1;
}
// if(strlen(argv[1])!=14){
// printf("Incorrect Length\n");
// return 1;
// }
// if(*argv[1] != 112){//Not enough precision
// printf("Password Incorrect\n");
// return 1;
// }
double magic_numbers[7] ={
-68822144.50341525673866271972656250000000000000000000000000,
56777293.39031631499528884887695312500000000000000000000000,
-3274524.75536667229607701301574707031250000000000000000000,
-85761.51255339206545613706111907958984375000000000000000,
8443.33244327564352715853601694107055664062500000000000,
-166.67369627952575683593750000000000000000000000000000,
1.00000000000000000000000000000000000000000000000000,
};
// for(int i = 0; i < 6;i++){
int i = 0;
double foo=1.0,bar=0.0;
for(int j=0;j<7;j++){
bar += magic_numbers[j] * foo;
foo *= (float)log(*(float*)((unsigned long)argv[1]+2*i));
}
if((int)(100000*bar) != 0){
printf("Password Incorrect\n");
return 0;
}
// }
printf("Password Correct\n");
return 0;
}
然后 python 脚本进行爆破
import string
import subprocess
charset = string.ascii_letters + string.digits + "!@#$%^_"
# pre = 'p'
# for a in charset:
# for b in charset:
# for c in charset:
# command = "./reverseme_1 p" + a + b + c
# output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
# if b"Correct" in output:
# print ("p" + a + b + c)
pre = "zf" # 每次修改一下 pre 就可以进行爆破了
# pfTw
# pLzf zfTw
# p0%Y %Yn@ n@M1 M1aL aLzf zfTw
# p@M1
for a in charset:
for b in charset:
command = "./reverseme_1 " + pre + a + b
output = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE)
if b"Correct" in output:
print (pre + a + b)
上面用来爆破 p
开头的,共有四种可能,依次爆破就行
最后得到 flag: p0%Yn@M1aLzfTw