无扩散关联加密算法, python运行程序单字节爆破, patch汇编
运行
D:\TRY\holiday\recurrent\5_analgo\3>analgo
111111111111111111111111111111111111111111
wrong!
分析
为 go 语言程序, 显著的特征就是字符串由指针跟长度组成
// main.main
void __cdecl main_main()
{
__int64 v0; // r14
__int128 v1; // xmm15
unsigned __int64 *v2; // rax
__int64 v3; // rbx
__int64 v4; // rdi
_QWORD *v5; // rsi
unsigned __int64 *_input; // rdx
bool v7; // cl
__int64 v8; // rax
unsigned __int64 p_input; // rbx
__int64 v10; // rax
__int64 __input; // rax
unsigned __int64 v12; // rcx
__int64 v14; // [rsp+10h] [rbp-168h]
__int64 v15; // [rsp+10h] [rbp-168h]
__int64 v16; // [rsp+18h] [rbp-160h]
__int64 v17; // [rsp+18h] [rbp-160h]
__int128 v18; // [rsp+20h] [rbp-158h]
char v19; // [rsp+90h] [rbp-E8h] BYREF
__int64 cmpdata0; // [rsp+A5h] [rbp-D3h]
__int64 cmpdata1; // [rsp+ADh] [rbp-CBh]
__int64 cmpdata2; // [rsp+B5h] [rbp-C3h]
__int64 cmpdata3; // [rsp+BDh] [rbp-BBh]
__int64 cmpdata4; // [rsp+C5h] [rbp-B3h]
__int16 cmpdata5; // [rsp+CDh] [rbp-ABh]
_BYTE v26[105]; // [rsp+CFh] [rbp-A9h] BYREF
unsigned __int64 *input; // [rsp+138h] [rbp-40h]
RTYPE *v28; // [rsp+140h] [rbp-38h]
char **v29; // [rsp+148h] [rbp-30h]
RTYPE *v30; // [rsp+150h] [rbp-28h]
char **v31; // [rsp+158h] [rbp-20h]
_QWORD v32[2]; // [rsp+160h] [rbp-18h] BYREF
while ( (unsigned __int64)&v19 <= *(_QWORD *)(v0 + 16) )
runtime_morestack_noctxt();
v14 = runtime_newobject();
input = v2;
*v2 = 0LL;
v32[0] = &RTYPE__ptr_string;
v32[1] = v2;
v3 = qword_368E60;
v4 = 2LL;
v5 = v32;
*(_QWORD *)&v18 = fmt_Fscanf(v14, v16);
if ( v3 )
{
_input = input;
v7 = 0;
}
else
{
_input = input;
if ( input[1] == 42 )
{
regexp_MustCompile(v15, v17);
v17 = *((_QWORD *)&v1 + 1);
v18 = v1;
v4 = 0LL;
v5 = 0LL;
v15 = regexp__ptr_Regexp_doExecute();
v7 = v8 != 0;
_input = input;
}
else
{
v7 = 0;
}
}
if ( !v7 )
goto LABEL_12;
p_input = *_input;
runtime_stringtoslicebyte(v4, (__int64)v5, (__int64)_input, _input[1]);// 切片函数
*(_QWORD *)&v26[97] = v10;
*(_QWORD *)v26 = 0LL;
__input = ((__int64 (*)(void))loc_23DF6B)();
qmemcpy(v26, ">>>>><<>>>><[[[.]+]<[-.+.].+.]].][.][[.+.]>[.<-.]<.][.][[.-,.<>>>>>>><<<<.].][[.[.]<.][.].-]].[.]", 97);// 将这个字符串的内容赋值到v26变量中, 长度为0x61, 根据main_anal中的操作来看这个类似于虚拟机字节码
main_anal(__input, p_input, 0x2E5B2E5D5D2D2E5DLL, 97LL, v12);
cmpdata0 = 0x9F061721E8B3DD38LL;
cmpdata1 = 0x120D94C88B56A209LL;
cmpdata2 = 0x8C0C2D3877E67EF3LL;
cmpdata3 = 0x2507BAF4CF113DELL;
cmpdata4 = 0x1B66F9F5A0238BCALL;
cmpdata5 = 0x9727;
if ( p_input == 42 ? runtime_memequal() : 0 ) // 关键判断
{
v30 = &RTYPE_string;
v31 = &right;
fmt_Fprintln(v15, v17, v18, *((__int64 *)&v18 + 1));
}
else
{
LABEL_12:
v28 = &RTYPE_string;
v29 = &wrong;
fmt_Fprintln(v15, v17, v18, *((__int64 *)&v18 + 1));
}
}
首先由 *(_QWORD *)&v18 = fmt_Fscanf(v14, v16);
进行输入, 随后检查长度, 并进行切片.
进入加密函数, 需要关注前面的 memcpy 操作的字符串, 跟加密函数高度相关
qmemcpy(v26, ">>>>><<>>>><[[[.]+]<[-.+.].+.]].][.][[.+.]>[.<-.]<.][.][[.-,.<>>>>>>><<<<.].][[.[.]<.][.].-]].[.]", 97);// 将这个字符串的内容赋值到v26变量中, 长度为0x61, 根据main_anal中的操作来看这个类似于虚拟机字节码
main_anal(__input, p_input, 0x2E5B2E5D5D2D2E5DLL, 97LL, v12);
main_anal
// main.anal
__int64 __fastcall main_anal(__int64 __input, unsigned __int64 _len, __int64 num, __int64 len, unsigned __int64 a5)
{
__int64 result; // rax
__int64 v6; // rbx
__int64 v7; // r14
unsigned __int64 len_sub_1; // r9
unsigned __int64 v9; // rcx
char v10; // dl
int v11; // r10d
unsigned __int8 _IP; // r11
int v13; // r12d
unsigned __int64 _len_sub_1; // r11
unsigned __int64 __len; // r9
__int64 v16; // rax
__int64 v17; // rsi
__int64 v18; // r8
__int64 v19; // rdx
__int64 v20; // rax
__int64 v21; // rcx
unsigned __int64 v22; // rsi
unsigned __int64 v23; // r15
__int64 v24; // r8
__int64 v25; // rax
unsigned __int64 v26; // rcx
unsigned __int64 v27; // rcx
int v28; // edx
__int64 v29; // rax
unsigned __int64 v30; // rcx
__int64 v31; // rsi
unsigned __int64 v32; // r15
__int64 v33; // r8
__int64 v34; // rax
unsigned __int64 v35; // rcx
unsigned __int64 v36; // r9
unsigned __int64 v37; // rsi
unsigned __int64 v38; // r13
__int64 v39; // r8
__int64 v40; // rax
unsigned __int64 v41; // rcx
__int64 v42; // [rsp-42h] [rbp-D8h]
char v43; // [rsp+1h] [rbp-95h]
unsigned __int8 v44; // [rsp+2h] [rbp-94h]
unsigned __int8 v45; // [rsp+2h] [rbp-94h]
char v46; // [rsp+3h] [rbp-93h]
unsigned __int8 v47; // [rsp+4h] [rbp-92h]
char v48; // [rsp+5h] [rbp-91h]
unsigned __int8 v49; // [rsp+5h] [rbp-91h]
unsigned __int64 v50; // [rsp+Eh] [rbp-88h]
unsigned __int64 v51; // [rsp+16h] [rbp-80h]
unsigned __int64 v52; // [rsp+1Eh] [rbp-78h]
unsigned __int64 v53; // [rsp+26h] [rbp-70h]
unsigned __int64 v54; // [rsp+2Eh] [rbp-68h]
unsigned __int64 v55; // [rsp+3Eh] [rbp-58h] BYREF
unsigned __int64 v56; // [rsp+46h] [rbp-50h]
unsigned __int64 v57; // [rsp+4Eh] [rbp-48h]
unsigned __int64 v58; // [rsp+56h] [rbp-40h]
unsigned __int64 v59; // [rsp+5Eh] [rbp-38h]
__int64 v60; // [rsp+66h] [rbp-30h]
__int64 v61; // [rsp+6Eh] [rbp-28h]
__int64 v62; // [rsp+76h] [rbp-20h]
__int64 v63; // [rsp+7Eh] [rbp-18h]
__int64 v64; // [rsp+86h] [rbp-10h]
__int64 v65; // [rsp+9Eh] [rbp+8h]
__int64 v66; // [rsp+9Eh] [rbp+8h]
__int64 v67; // [rsp+A6h] [rbp+10h]
__int64 v68; // [rsp+AEh] [rbp+18h]
unsigned __int64 v69; // [rsp+C6h] [rbp+30h]
while ( (unsigned __int64)&v55 <= *(_QWORD *)(v7 + 16) )
{
v66 = result;
v68 = len;
v69 = a5;
runtime_morestack_noctxt();
result = v66;
len = v68;
a5 = v69;
}
v67 = v6;
v65 = result;
v46 = 0;
v57 = 0LL;
len_sub_1 = _len - 1;
LODWORD(v9) = 0;
v10 = 1;
v11 = 0;
while ( v6 > (unsigned __int8)v9 )
{
_IP = *(_BYTE *)(result + (unsigned __int8)v9);// 更新IP
if ( v46 == _IP )
JUMPOUT(0x292E1ALL);
v46 = *(_BYTE *)(result + (unsigned __int8)v9);
v57 = 0LL;
v44 = v11;
v43 = v10;
v50 = len_sub_1;
v13 = v9 + 1;
v47 = v9 + 1;
if ( _IP > '.' )
{
if ( _IP > '>' )
{
if ( _IP == '[' )
{
if ( v10 )
{
_len_sub_1 = len_sub_1 + 1;
v59 = len_sub_1 + 1;
if ( (__int64)_len > (__int64)(len_sub_1 + 1) )
{
if ( a5 < len_sub_1 + 2 )
runtime_panicSliceAcap();
if ( _len < _len_sub_1 )
runtime_panicSliceB();
v31 = _len - len_sub_1;
v54 = len_sub_1 + v31 + 1;
v32 = a5;
v33 = __input + (_len_sub_1 & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
if ( v32 < v54 )
{
v64 = v33;
v58 = v31;
runtime_growslice(v42);
__input = v34;
v32 = v35;
}
v58 = v32;
v62 = __input;
runtime_memmove();
if ( v54 <= v59 )
runtime_panicIndex();
__input = v62;
v11 = v44;
*(_BYTE *)(v50 + v62 + 1) = v44;
result = v65;
v10 = v43;
v6 = v67;
_len_sub_1 = v59;
v13 = v47;
__len = v54;
a5 = v58;
}
else
{
__len = _len + 1;
if ( a5 < _len + 1 )
{
runtime_growslice(v42);
__len = __input + 1;
v10 = v43;
v6 = v67;
v11 = v44;
_len_sub_1 = v59;
v13 = v47;
__input = v29;
a5 = v30;
result = v65;
}
*(_BYTE *)(__input + _len) = v11;
}
}
else
{
_len_sub_1 = len_sub_1 + ((1LL << v57) & -(__int64)(v57 < 0x40));
__len = _len;
}
}
else
{
if ( _IP != ']' )
goto LABEL_67;
if ( v10 )
{
if ( len_sub_1 >= _len )
runtime_panicIndex();
v45 = *(_BYTE *)(len_sub_1 + __input);
v37 = _len - len_sub_1;
v53 = len_sub_1 + v37 - 1;
v38 = a5;
v39 = __input + ((len_sub_1 + 1) & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
if ( v38 < v53 )
{
v64 = v39;
v59 = v37;
runtime_growslice(v42);
__input = v40;
v38 = v41;
}
v61 = __input;
runtime_memmove();
v36 = v50 - 1;
result = v65;
v10 = v43;
v6 = v67;
v13 = v47;
_len = v53;
a5 = v38;
__input = v61;
v11 = v45;
}
else
{
v36 = len_sub_1 - (-(__int64)(v57 < 0x40) & (1LL << v57));
}
_len_sub_1 = v36;
__len = _len;
}
}
else
{
if ( _IP != '<' )
{
if ( _IP == '>' )
JUMPOUT(0x29328FLL);
LABEL_67:
__len = _len;
_len_sub_1 = v50;
goto LABEL_3;
}
if ( v10 )
{
v11 -= v57 < 0x20 ? 1 << v57 : 0;
}
else
{
if ( len_sub_1 >= _len )
runtime_panicIndex();
*(_BYTE *)(__input + len_sub_1) = *(_BYTE *)(len_sub_1 + __input) - (v57 < 0x20 ? 1 << v57 : 0);
}
__len = _len;
_len_sub_1 = v50;
}
}
else
{
if ( _IP > ',' )
{
if ( _IP != '-' )
{
v10 ^= 1u;
__len = _len;
_len_sub_1 = v50;
goto LABEL_3;
}
if ( len_sub_1 >= _len )
runtime_panicIndex();
v49 = *(_BYTE *)(len_sub_1 + __input);
v22 = _len - len_sub_1;
v51 = len_sub_1 + v22 - 1;
v23 = a5;
v24 = __input + ((len_sub_1 + 1) & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
if ( v23 < v51 )
{
v64 = v24;
v59 = v22;
runtime_growslice(v42);
__input = v25;
v23 = v26;
}
v60 = __input;
runtime_memmove();
if ( v43 )
{
if ( v44 )
{
v27 = v51;
goto LABEL_34;
}
v27 = v51;
LABEL_35:
v28 = v47;
}
else
{
v27 = v51;
if ( v51 <= v50 - 1 )
runtime_panicIndex();
if ( !*(_BYTE *)(v50 + v60 - 1) )
goto LABEL_35;
LABEL_34:
v28 = v49;
}
v6 = v67;
v13 = v28;
a5 = v23;
__len = v27;
__input = v60;
_len_sub_1 = v50 - 1;
v10 = v43;
v11 = v44;
result = v65;
goto LABEL_3;
}
if ( _IP == '+' )
{
v48 = v9;
_len_sub_1 = len_sub_1 + 1;
v59 = len_sub_1 + 1;
if ( (__int64)_len > (__int64)(len_sub_1 + 1) )
{
if ( a5 < len_sub_1 + 2 )
runtime_panicSliceAcap();
if ( _len < _len_sub_1 )
runtime_panicSliceB();
v55 = a5;
v17 = _len - len_sub_1;
v52 = len_sub_1 + v17 + 1;
v18 = __input + (_len_sub_1 & ((__int64)(1 - (a5 - len_sub_1)) >> 63));
v19 = v55;
if ( v55 < v52 )
{
v64 = v18;
v58 = v17;
runtime_growslice(v42);
__input = v20;
v19 = v21;
}
v56 = v19;
v63 = __input;
runtime_memmove();
if ( v52 <= v59 )
runtime_panicIndex();
__input = v63;
*(_BYTE *)(v50 + v63 + 1) = v48;
result = v65;
v10 = v43;
v6 = v67;
v11 = v44;
_len_sub_1 = v59;
v13 = v47;
__len = v52;
a5 = v56;
}
else
{
__len = _len + 1;
if ( a5 < _len + 1 )
{
runtime_growslice(v42);
__len = __input + 1;
v10 = v43;
v6 = v67;
v11 = v44;
_len_sub_1 = v59;
v13 = v47;
__input = v16;
a5 = v9;
result = v65;
LOBYTE(v9) = v48;
}
*(_BYTE *)(__input + _len) = v9;
}
}
else
{
if ( _IP != ',' )
goto LABEL_67;
if ( len_sub_1 >= _len )
runtime_panicIndex();
if ( _len <= len_sub_1 + 2 )
runtime_panicIndex();
*(_BYTE *)(__input + len_sub_1) = (-63 * *(_BYTE *)(len_sub_1 + __input + 2)) ^ *(_BYTE *)(len_sub_1 + __input);
__len = _len;
_len_sub_1 = v50;
}
}
LABEL_3:
LODWORD(v9) = v13; // 结合上面的v13 = v9 + 1, 可以看成v9++
_len = __len;
len_sub_1 = _len_sub_1;
}
return result;
}
/* Orphan comments:
这里是针对">"的语句>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
v57初始化为0, 如果v57 < 32, 则返回1<<v57, 否则返回0
*/
总体看就是一个 while 循环中包含了 switch, 跟暑假复现题 gocode 非常相似, 前面 memcpy 的">>..."字符串就是字节码, 用来操作加密行为. 看了小组的 WP 是叫做brainfuck 代码.
根据不同的输入可以发现是无扩散关联的加密方式, 即各个字节加密互相不受影响, 这种加密可以采用爆破破解.
但是程序本身是无法直接爆破加密的, 因为它会在关键判断出验证长度
.text:0000000000293A42 48 83 F9 2A cmp rcx, 2Ah ; '*' ; len
.text:0000000000293A46 74 04 jz short loc_293A4C
直接修改指令 cmp rcx, 2Ah
会导致判断一直为错误, 因为 rcx 是中的值是字符串长度无法改变, 而后面的验证函数又是通过 rcx 来确定检验长度的, 所以需要修改 rcx ,而不是后面的直接数.
修改指令为 mov ecx, 1
, 修改后续直接数相当于修改 ecx .
另一个问题: cmp 指令会影响下一个 jz 结果, 修改后 jz 会一直跳转到"wrong", 需要修改为 jmp 绝对跳转.
两处修改会覆盖掉后面的一点内容, 但都是无用指令, 所以覆盖掉也没有关系.
使用 Python 爆破
import subprocess
import string
# fla111111111111111111111111111111111111111
def patcher(size):
data = bytearray(open("analgo.exe", "rb").read())
data[0xB3043] = size
open("analgo.exe", "wb").write(data)
def try_one(t):
pipe = subprocess.Popen("./analgo.exe", bufsize=2, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
pipe.stdin.write(t)
return pipe.stdout.read()
data = list('f11111111111111111111111111111111111111111\n')
for i in range(1, 0x2A):
patcher(i)
for j in string.printable:
data[i] = j
if b'right!\n' == try_one(bytes("".join(data).encode("ascii"))):
break
print("".join(data))
flag
flag{568a3cdd-77e1-4c42-9fee-127e27a5744e}