运行
D:\TRY\holiday\recurrent\2_puzzle\copy>puzzle
Input: aaaa
Wrong!
查壳 && 脱壳
查壳发现是UPX壳, 但是使用脱壳工具会报错, 原因是修改了UPX壳的特征, 比如节区名, 我们需要改回来才能使用工具脱壳, 或者直接手脱也行
分析
通过跟进可以找到关键函数
void important_function()
{
__int64 v0; // xmm0_8
__int64 *_input; // rdx
__int64 j; // rax
__int64 index; // rcx
__int64 i; // rbx
__int64 v5; // rdi
__int64 len; // rsi
unsigned __int8 one_char; // si
char v8; // [rsp+28h] [rbp-1E8h]
__int128 v9; // [rsp+28h] [rbp-1E8h]
__int64 v10; // [rsp+30h] [rbp-1E0h]
__int64 v11; // [rsp+38h] [rbp-1D8h]
__int64 v12; // [rsp+50h] [rbp-1C0h]
char v13[9]; // [rsp+67h] [rbp-1A9h] BYREF
char v14[9]; // [rsp+70h] [rbp-1A0h] BYREF
char v15; // [rsp+79h] [rbp-197h] BYREF
__int64 v16; // [rsp+7Ah] [rbp-196h]
char v17[9]; // [rsp+82h] [rbp-18Eh] BYREF
char v18[9]; // [rsp+8Bh] [rbp-185h] BYREF
char v19[9]; // [rsp+94h] [rbp-17Ch] BYREF
char v20[9]; // [rsp+9Dh] [rbp-173h] BYREF
char v21; // [rsp+A6h] [rbp-16Ah] BYREF
__int64 v22; // [rsp+A7h] [rbp-169h]
char v23; // [rsp+AFh] [rbp-161h] BYREF
__int64 v24; // [rsp+B0h] [rbp-160h]
__int128 v25; // [rsp+B8h] [rbp-158h]
__int64 v26[2]; // [rsp+C8h] [rbp-148h] BYREF
__int64 v27[4]; // [rsp+D8h] [rbp-138h] BYREF
__int64 *inputt; // [rsp+F8h] [rbp-118h]
__int64 v29[2]; // [rsp+100h] [rbp-110h] BYREF
__int64 v30[2]; // [rsp+110h] [rbp-100h] BYREF
__int64 input[2]; // [rsp+120h] [rbp-F0h] BYREF
__int64 p_puzzle; // [rsp+130h] [rbp-E0h] BYREF
__int64 box[26]; // [rsp+138h] [rbp-D8h]
while ( (unsigned __int64)&v18[5] <= *(_QWORD *)(*(_QWORD *)NtCurrentTeb()->NtTib.ArbitraryUserPointer + 16LL) )
runtime_morestack_noctxt();
v25 = 0LL;
inputt = (__int64 *)runtime_newobject((__int64 *)&RTYPE_string);
p_puzzle = 0LL;
*(double *)&v0 = ((double (*)(void))loc_5E698F)();
v23 = 4;
v24 = 0x800000900000000LL;
box[0] = 9LL;
box[1] = 9LL;
p_puzzle = (__int64)&v23;
v21 = 0;
v22 = 0x600000002000100LL;
box[3] = 9LL;
box[4] = 9LL;
box[2] = (__int64)&v21;
v20[0] = 0;
*(_QWORD *)&v20[1] = 0x3000000000502LL;
box[6] = 9LL;
box[7] = 9LL;
box[5] = (__int64)v20;
v19[0] = 0;
*(_QWORD *)&v19[1] = 0x8000001000000LL;
box[9] = 9LL;
box[10] = 9LL;
box[8] = (__int64)v19;
v18[0] = 0;
*(_QWORD *)&v18[1] = 0x10600070005LL;
box[12] = 9LL;
box[13] = 9LL;
box[11] = (__int64)v18;
v17[0] = 2;
*(_QWORD *)&v17[1] = 150994944LL;
box[15] = 9LL;
box[16] = 9LL;
box[14] = (__int64)v17;
v15 = 0;
v16 = 0x700020008000000LL;
box[18] = 9LL;
box[19] = 9LL;
box[17] = (__int64)&v15;
v14[0] = 0;
*(_QWORD *)&v14[1] = 394240LL;
box[21] = 9LL;
box[22] = 9LL;
box[20] = (__int64)v14;
*(_QWORD *)&v13[1] = 0x80000000000LL;
*(_WORD *)v13 = 1543;
box[24] = 9LL;
box[25] = 9LL;
box[23] = (__int64)v13;
fmt_Fprintf((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_708528, (__int64)"Input: ", 7LL, v0, 0LL, 0LL);
input[0] = (__int64)&RTYPE__ptr_string;
input[1] = (__int64)inputt;
fmt_Fscanf(
(__int64)&go_itab__ptr_os_File_comma__ptr_io_Reader,
qword_708520,
(__int64)"%s",
2LL,
(__int64)input,
1LL,
1LL);
if ( !v12 )
{
_input = inputt;
j = 0LL;
index = 0LL;
while ( j < 9 ) // 循环9次, 对应有9行
{
for ( i = 0LL; i < 9; ++i ) // 循环9次, 对应有9列
{
v5 = box[3 * j - 1]; // 排列的顺序是: <数独一行的指针>, <9>, <9>
// -1 0 1
// 2 3 4
// ...
// 所以v5的值为指向数独一行数据的指针
if ( box[3 * j] <= (unsigned __int64)i )
((void (__noreturn *)(void))runtime_panicIndex)();
if ( !*(_BYTE *)(i + v5) ) // 如果该字节为0的话
{
len = _input[1]; // go字符串格式是input[0]为字符串指针, input[1]为字符串长度
if ( len <= index )
goto LABEL_13;
if ( len <= (unsigned __int64)index )
((void (__noreturn *)(void))runtime_panicIndex)();
one_char = *(_BYTE *)(index + *_input);// 取出输入的一个字符
if ( one_char < '0' || one_char > '9' )// 检查该字符是否符合要求
goto LABEL_13;
*(_BYTE *)(v5 + i) = one_char - '0'; // 将字符转换成对应的BYTE型, 并写入数独中
++index;
}
}
++j;
}
if ( _input[1] == index && (check((__int64)&p_puzzle, 9uLL), v8) )
{
*(_QWORD *)&v9 = runtime_stringtoslicebyte((__int64)v27, *inputt, inputt[1]);
*((_QWORD *)&v9 + 1) = crypto_md5_Sum(v9, v10, v11);
v25 = v9;
*(_OWORD *)v26 = v9;
v30[0] = runtime_convT2Enoptr((__int64)&RTYPE__16_uint8, (__int64)v26);
v30[1] = v9;
fmt_Fprintf(
(__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer,
qword_708528,
(__int64)"Your flag is flag{%x}async stack too l",
21LL,
(__int64)v30,
1LL,
1LL);
}
else
{
LABEL_13:
v29[0] = (__int64)&RTYPE_string;
v29[1] = (__int64)&off_684828;
fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_708528, (__int64)v29, 1LL, 1LL);
}
}
}
在清楚了后面的两个循环检查的逻辑的情况下, 以及题目puzzle, 我们可以判断应该是数独.
而前面的一大块赋值语句就是初始化数独.
在使用工具脱完壳后, 程序不能直接调试, 我们可以使用setIP的方法进行局部的调试(请教了闻铃哥)
我们一直交叉引用到程序的入口点, 并此处打上一个硬件断点
同时记录数独初始化的起始地址0x638C9C
现在开始调试, 并修改RIP的值为0x638C9C跳转到数独初始化的位置.
F8, 运行到初始化完成的位置我们查看当前程序的内存, 得到了我们需要的数据
这里有点小坑就是最终输入填入的数据顺序, 和我们在内存中dump下来的数据在行上是相反的. 上面的数组是指针数组(中间隔了两个9), 而其中指针指向的地址是从大到小的. 后面进行填入操作的时候, 也是从地址最高的一行开始填入的.
//这是我们从内存中dump下来的样子
7 6 0 0 0 0 8 0 0
0 0 4 6 0 0 0 0 0
0 0 0 0 8 0 2 0 7
2 0 0 0 9 0 0 0 0
0 5 0 7 0 6 1 0 0
0 0 0 0 1 0 0 8 0
0 2 5 0 0 0 0 3 0
0 0 1 0 2 0 0 0 6
4 0 0 0 0 9 0 0 8 //但是我们的填入数据是从这一行开始的
使用在线工具解数独, 然后将所有0的答案填入即可.
得到flag
D:\TRY\holiday\recurrent\2_puzzle\copy>puzzle
Input: 76135283549798674164925733849217386455934161872359295314
Your flag is flag{23c3cb3aedbbfdd009d1bf52e530676a}
flag{23c3cb3aedbbfdd009d1bf52e530676a}
关键判断中判断了哪些东西
判断了是否越界
91 ~ 92
判断了单个字节的内容是否为0
95 ~ 96