-
-
[原创] 看雪 2023 KCTF 年度赛 第六题 至暗时刻
-
发表于: 2023-9-15 03:50 9579
-
main函数通过beginthreadex启动的sub_140001630(重命名为mainfunction)是真正的主逻辑所在。
IDA F5反编译,主要部分如下:
定义出std::string的结构体和相关函数,然后排除掉析构函数的干扰,主体逻辑并不复杂(常量字符串大多用sub_1400013B0(str_xor)函数异或加密了,聊胜于无):获取输入(userinput),依次拼接"kctf"、global_string1、userinput、global_string2四部分为一个大字符串(global_string1和global_string2是两个全局string,分别在sub_140001000和sub_140001028初始化。其中global_string1长63字节,包含0-9和A-F,global_string2长120字节,只包含0-9)。
对于拼接后的大字符串,分别经过了sub_140002BBA(maybe_virtualallocex)和sub_140002DA9(maybe_writeprocessmemory)两个函数处理。这两个函数内部都是直接调用syscall指令执行系统调用,且调用号(eax)也是经过复杂的运算得出的。
(题目不支持win11的原因来源如此:windows的系统调用在用户态的接口封装在ntdll.dll中,而内核的系统调用号码和参数结构等并不属于公开的稳定api,随着系统版本更新也会有变化,这一点与linux完全不同)
此时最简单的方法是动态调试看这两个函数的具体行为是什么。在此之前,先留意程序是否存在反调试。sub_140001320和sub140001338是两个tlscallback,里面做了很明显的IsDebuggerPresent和NtQueryInformationProcess反调试检测,且如果检测到调试器,会调用sub_140001298对以"kctf"开头的内存页做修改。此外,在sub_140001450(has_antidebug2_,被sub_140001630(mainfunction)调用)函数也有反调试(CheckRemoteDebuggerPresent)。将这些地方全部patch掉。
开一台win10虚拟机调试(在win11上运行则程序不会有最后ok!或no!的输出),对于和sub_140002DA9(maybe_writeprocessmemory)函数,观察到第二个参数Source的值通常为0x1F0000,是一块分配出来的内存区域,且调用这个函数之后后会把拼接了常量和用户输入的大字符串写入开头(此时,偏移量为0的地方是"kctf",偏移量为4的地方是global_string1,然后紧跟的是userinput和global_string2)。
接下来调用了sub_140001450(has_antidebug2_)函数,这里除了反调试,还有一处调用i = (unsigned __int8 *)&unk_140008050; !(unsigned int)sub_140002E4E((__int64)hObject, RtlFillMemory_, a2 + v5, 1i64, *i, (__int64)ThrdAddr, v11, v12, v13, v14, v15);
。其中a2是Source+0x1F4(Source是0x1F0000,即写入了大字符串的内存段)。unk_140008050是.data段的地址,在调用sub_140001450之后观察0x1F0000段的内存,发现unk_140008050处的0x92B个字节被写入了0x1F01F4的位置。
观察发现unk_140008050开始的字节很像x64指令,IDA里按c发现确实如此。随后,mainfunction以0x1F01F4为第二个参数调用了sub_140002E4E,可以猜测sub_140002E4E是启动新的进程,从0x1F01F4开始执行指令。在此之后mainfunction只剩下判断和输出。
直接分析,发现这些指令开头是对后面的自修改,所以继续动态调试到这部分执行完毕(到达0x1F0217)再dump出来。
看到了mainfunction中检测的"120"常量字符串和分支。动态调试发现v14为0x1F0000,所以这个函数前半部分只是找到此块内存区域,sub_1F0AA3是真正的输入校验逻辑。(其参数v14+4,恰好是global_string1+userinput+global_string2三部分的拼接)
sub_1F0AA3里,先不管调用的三个函数,这里外层的while循环是0-9的遍历,内层的while循环还有3*3的分块,看起来很像数独。
调用的三个函数,逻辑高度类似,都是循环检查sub_1F063B的返回值范围在1-9且无重复,所以它们依次是数独的行检测、列检测、九宫检测。
共同调用到的函数是sub_1F063B,从调用方式看,它的后两个参数应该是坐标,返回值是坐标位置的值。所以下一步需要搞清楚拼接后的大字符串的编码逻辑,以及如何映射到数独的格子。
从调用情况看,sub_1F063B不应返回大于9的值。而sub_1F063B最开始就先获取了长度,但长度的检测却是在循环中完成的(should_be_81和should_be_243)。
这里的参数a1是0x1F0004,指向的内容是global_string1+userinput+global_string2三部分的拼接。
回顾一下,global_string1的长度是63,包含数字和字母,疑似hexencoded,global_string2的长度是120,只包含数字。此处检查strlen(a1)等于363,减去两个常量字符串的63和120,合计用户输入的userinput长度应为180。此时global_string2位于a1的63+180=243=0xF3的偏移处。
大循环的上半部分从a1偏移量0处开始遍历,每3个字符按十六进制解析为3位十进制整数,从百位到个位分别是数值和横纵坐标。另外,当循环次数达到21后,会开启一个新的循环,从a1偏移量0xF3的位置开始,每2个字符按十进制解析为整数,检查是否为横纵坐标所指示的位置。
结合global_string1长度63=21*3,userinput长度180=60*3,且21+60=81=9*9,所以global_string1是数独的21个初始值,userinput是60个待填值。a1的0xF3偏移开始是120个字符的global_string2,而120=60*2,所以global_string2指示的是60个待填值的排列顺序。
提取数独,找一个求解器解出答案,反向编码,即可得到正确的输入:
最终的正确答案:
(p.s. 看到有人发现了多解,按照上面的分析似乎只有大小写可能导致多解(毕竟,限制了输入长度,其实就已经排除了大部分意料之外的多解)?但是尝试把答案全部小写却不能通过程序检查。所以可能还有其他的问题(数独多解?)?不深究了)
(p.s. 赛程过半,今年的KCTF可比往年卷多了。。。)
struct
string
{
char
*s;
__declspec
(align(16))
__int64
size;
__int64
capacity;
};
// sub_140001630
__int64
mainfunction()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
std_string_assign(&userinput, byte_140006428);
std_string_assign(&Format, aV);
std_string_assign(&lpModuleName, aJzlOm);
std_string_assign(&lpProcName, aM);
std_string_assign(&str_kctf, aA);
std_string_append(&lpProcName, aD_0);
std_string_assign(&Block,
"7PK:"
);
p_Format = (
char
*)&Format;
if
( Format.capacity >= 0x10ui64 )
p_Format = Format.s;
str_xor(p_Format, Format.size,
"&x+^x"
, 5);
// Please enter your key:
s = (
char
*)&Format;
if
( Format.capacity >= 0x10ui64 )
s = Format.s;
printf
(s);
v3 = userinput.size;
if
( userinput.size < 0x1F4ui64 )
{
v5 = 500 - userinput.size;
if
( (unsigned
__int64
)(500 - userinput.size) > userinput.capacity - userinput.size )
{
sub_140002398(&userinput, 500 - userinput.size, v2, 500 - userinput.size);
}
else
{
userinput.size = 500i64;
p_userinput = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
p_userinput = userinput.s;
v7 = &p_userinput[v3];
memset
(v7, 0, 500 - v3);
v7[v5] = 0;
}
}
else
{
userinput.size = 500i64;
v4 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v4 = userinput.s;
v4[500] = 0;
}
v8 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v8 = userinput.s;
scanf
(
"%s"
, v8);
v9 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v9 = userinput.s;
v10 = &global_string1;
std_string_append(&global_string1, v9);
v12 = &global_string2;
if
( global_string2.capacity >= 0x10ui64 )
v12 = global_string2.s;
v13 = global_string2.size;
v14 = global_string1.size;
if
( global_string2.size > (unsigned
__int64
)(global_string1.capacity - global_string1.size) )
{
std_string_realloc_append((
void
**)&global_string1.s, global_string2.size, v11, v12, global_string2.size);
}
else
{
global_string1.size += global_string2.size;
v15 = (
char
*)&global_string1;
if
( global_string1.capacity >= 0x10ui64 )
v15 = global_string1.s;
v16 = &v15[v14];
memmove
(v16, v12, global_string2.size);
v16[v13] = 0;
}
p_strkctf = &str_kctf;
if
( str_kctf.capacity >= 0x10ui64 )
p_strkctf = str_kctf.s;
str_xor(p_strkctf, str_kctf.size,
"*s>0?"
, 5);
// kctf
if
( (unsigned
__int64
)(0x7FFFFFFFFFFFFFFFi64 - str_kctf.size) < global_string1.size )
sub_140001284();
v19 = &str_kctf;
if
( str_kctf.capacity >= 0x10ui64 )
v19 = str_kctf.s;
v20 = &global_string1;
if
( global_string1.capacity >= 0x10ui64 )
v20 = global_string1.s;
std_string_concat((
struct
string *)Src, str_kctf.size, v18, v19, str_kctf.size, v20, global_string1.size);
if
( global_string1.capacity >= 0x10ui64 )
{
v21 = global_string1.s;
if
( (unsigned
__int64
)(global_string1.capacity + 1) >= 0x1000 )
{
if
( (unsigned
__int64
)&global_string1.s[-*((_QWORD *)global_string1.s - 1) - 8] > 0x1F )
invalid_parameter_noinfo_noreturn();
v21 = (
char
*)*((_QWORD *)global_string1.s - 1);
}
j_j_free(v21);
}
global_string1.size = 0i64;
global_string1.capacity = 15i64;
LOBYTE(global_string1.s) = 0;
memcpy
(&global_string1, Src,
sizeof
(global_string1));
CurrentProcess = GetCurrentProcess();
v59 = 5500i64;
LODWORD(Size) = 0x3000;
maybe_virtualallocex((
__int64
)CurrentProcess, (
__int64
)&Source, 0i64, (
__int64
)&v59, Size);
if
( global_string1.capacity >= 0x10ui64 )
v10 = global_string1.s;
maybe_writeprocessmemory((
__int64
)CurrentProcess, (
__int64
)Source, (
__int64
)v10, global_string1.size, (
__int64
)v60);
p_lpModuleName = (
char
*)&lpModuleName;
if
( lpModuleName.capacity >= 0x10ui64 )
p_lpModuleName = lpModuleName.s;
str_xor(p_lpModuleName, lpModuleName.size,
"!?>d*"
, 5);
// kernel32.dll
p_lpProcName = (
char
*)&lpProcName;
if
( lpProcName.capacity >= 0x10ui64 )
p_lpProcName = lpProcName.s;
str_xor(p_lpProcName, lpProcName.size,
"?x)da"
, v24);
// RtlFillMemory
v26 = (
char
*)&lpProcName;
if
( lpProcName.capacity >= 0x10ui64 )
v26 = lpProcName.s;
v27 = (
char
*)&lpModuleName;
if
( lpModuleName.capacity >= 0x10ui64 )
v27 = lpModuleName.s;
ModuleHandleA = GetModuleHandleA(v27);
RtlFillMemory_ = (
__int64
)GetProcAddress(ModuleHandleA, v26);
if
( !RtlFillMemory_
|| (v36 = Source, (unsigned
int
)has_antidebug2_((
__int64
)CurrentProcess, (
__int64
)(Source + 0x1F4))) )
{
// COLLAPSED destructors
// ...
}
sub_140002E4E((
__int64
)hThread, (
__int64
)(v36 + 0x1F4), 0i64, 0i64, 0i64, 0i64, 1, 0i64, 0i64, 0i64, 0i64);
ResumeThread(hThread);
sub_140002A8E((
__int64
)hThread, 0i64, 0i64, v43, Sizea);
CloseHandle(hThread);
*(_DWORD *)Str1 = 0;
*(_DWORD *)Destination = 0;
strncpy
(Destination, Source, 3ui64);
strncpy
(Str1, Source + 67, 3ui64);
if
( !
strcmp
(Str1,
"110"
) )
{
v44 = &unk_140006590;
}
else
{
if
(
strcmp
(Str1,
"120"
) )
ExitProcess(0);
v44 = &unk_140006598;
}
str_xor(Destination, 3, v44, 3);
printf
(
"\n%s"
, Destination);
// COLLAPSED destructors
// ...
return
0i64;
}
struct
string
{
char
*s;
__declspec
(align(16))
__int64
size;
__int64
capacity;
};
// sub_140001630
__int64
mainfunction()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
std_string_assign(&userinput, byte_140006428);
std_string_assign(&Format, aV);
std_string_assign(&lpModuleName, aJzlOm);
std_string_assign(&lpProcName, aM);
std_string_assign(&str_kctf, aA);
std_string_append(&lpProcName, aD_0);
std_string_assign(&Block,
"7PK:"
);
p_Format = (
char
*)&Format;
if
( Format.capacity >= 0x10ui64 )
p_Format = Format.s;
str_xor(p_Format, Format.size,
"&x+^x"
, 5);
// Please enter your key:
s = (
char
*)&Format;
if
( Format.capacity >= 0x10ui64 )
s = Format.s;
printf
(s);
v3 = userinput.size;
if
( userinput.size < 0x1F4ui64 )
{
v5 = 500 - userinput.size;
if
( (unsigned
__int64
)(500 - userinput.size) > userinput.capacity - userinput.size )
{
sub_140002398(&userinput, 500 - userinput.size, v2, 500 - userinput.size);
}
else
{
userinput.size = 500i64;
p_userinput = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
p_userinput = userinput.s;
v7 = &p_userinput[v3];
memset
(v7, 0, 500 - v3);
v7[v5] = 0;
}
}
else
{
userinput.size = 500i64;
v4 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v4 = userinput.s;
v4[500] = 0;
}
v8 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v8 = userinput.s;
scanf
(
"%s"
, v8);
v9 = (
char
*)&userinput;
if
( userinput.capacity >= 0x10ui64 )
v9 = userinput.s;
v10 = &global_string1;
std_string_append(&global_string1, v9);
v12 = &global_string2;
if
( global_string2.capacity >= 0x10ui64 )
v12 = global_string2.s;
v13 = global_string2.size;
v14 = global_string1.size;
if
( global_string2.size > (unsigned
__int64
)(global_string1.capacity - global_string1.size) )
{
std_string_realloc_append((
void
**)&global_string1.s, global_string2.size, v11, v12, global_string2.size);
}
else
{
global_string1.size += global_string2.size;
v15 = (
char
*)&global_string1;
if
( global_string1.capacity >= 0x10ui64 )
v15 = global_string1.s;
v16 = &v15[v14];
memmove
(v16, v12, global_string2.size);
v16[v13] = 0;
}
p_strkctf = &str_kctf;
if
( str_kctf.capacity >= 0x10ui64 )
p_strkctf = str_kctf.s;
str_xor(p_strkctf, str_kctf.size,
"*s>0?"
, 5);
// kctf
if
( (unsigned
__int64
)(0x7FFFFFFFFFFFFFFFi64 - str_kctf.size) < global_string1.size )
sub_140001284();
v19 = &str_kctf;
if
( str_kctf.capacity >= 0x10ui64 )
v19 = str_kctf.s;
v20 = &global_string1;
if
( global_string1.capacity >= 0x10ui64 )
v20 = global_string1.s;
std_string_concat((
struct
string *)Src, str_kctf.size, v18, v19, str_kctf.size, v20, global_string1.size);
if
( global_string1.capacity >= 0x10ui64 )
{
v21 = global_string1.s;
if
( (unsigned
__int64
)(global_string1.capacity + 1) >= 0x1000 )
{
if
( (unsigned
__int64
)&global_string1.s[-*((_QWORD *)global_string1.s - 1) - 8] > 0x1F )
invalid_parameter_noinfo_noreturn();
v21 = (
char
*)*((_QWORD *)global_string1.s - 1);
}
j_j_free(v21);
}
global_string1.size = 0i64;
global_string1.capacity = 15i64;
LOBYTE(global_string1.s) = 0;
memcpy
(&global_string1, Src,
sizeof
(global_string1));
CurrentProcess = GetCurrentProcess();
v59 = 5500i64;
LODWORD(Size) = 0x3000;
maybe_virtualallocex((
__int64
)CurrentProcess, (
__int64
)&Source, 0i64, (
__int64
)&v59, Size);
if
( global_string1.capacity >= 0x10ui64 )
v10 = global_string1.s;
maybe_writeprocessmemory((
__int64
)CurrentProcess, (
__int64
)Source, (
__int64
)v10, global_string1.size, (
__int64
)v60);
p_lpModuleName = (
char
*)&lpModuleName;
if
( lpModuleName.capacity >= 0x10ui64 )
p_lpModuleName = lpModuleName.s;
str_xor(p_lpModuleName, lpModuleName.size,
"!?>d*"
, 5);
// kernel32.dll
p_lpProcName = (
char
*)&lpProcName;
if
( lpProcName.capacity >= 0x10ui64 )
p_lpProcName = lpProcName.s;
str_xor(p_lpProcName, lpProcName.size,
"?x)da"
, v24);
// RtlFillMemory
v26 = (
char
*)&lpProcName;
if
( lpProcName.capacity >= 0x10ui64 )
v26 = lpProcName.s;
v27 = (
char
*)&lpModuleName;
if
( lpModuleName.capacity >= 0x10ui64 )
v27 = lpModuleName.s;
ModuleHandleA = GetModuleHandleA(v27);
RtlFillMemory_ = (
__int64
)GetProcAddress(ModuleHandleA, v26);
if
( !RtlFillMemory_
|| (v36 = Source, (unsigned
int
)has_antidebug2_((
__int64
)CurrentProcess, (
__int64
)(Source + 0x1F4))) )
{
// COLLAPSED destructors
// ...
}
sub_140002E4E((
__int64
)hThread, (
__int64
)(v36 + 0x1F4), 0i64, 0i64, 0i64, 0i64, 1, 0i64, 0i64, 0i64, 0i64);
ResumeThread(hThread);
sub_140002A8E((
__int64
)hThread, 0i64, 0i64, v43, Sizea);
CloseHandle(hThread);
*(_DWORD *)Str1 = 0;
*(_DWORD *)Destination = 0;
strncpy
(Destination, Source, 3ui64);
strncpy
(Str1, Source + 67, 3ui64);
if
( !
strcmp
(Str1,
"110"
) )
{
v44 = &unk_140006590;
}
else
{
if
(
strcmp
(Str1,
"120"
) )
ExitProcess(0);
v44 = &unk_140006598;
}
str_xor(Destination, 3, v44, 3);
printf
(
"\n%s"
, Destination);
// COLLAPSED destructors
// ...
return
0i64;
}
__int64
__fastcall has_antidebug2_(
__int64
a1,
__int64
a2)
{
HANDLE
CurrentProcess;
// rax
__int64
(**v4)();
// rax
unsigned
int
v5;
// edi
unsigned
__int8
*i;
// rsi
__int64
v7;
// r9
__int64
v9;
// [rsp+20h] [rbp-60h]
unsigned
int
*ThrdAddr;
// [rsp+28h] [rbp-58h]
int
v11;
// [rsp+30h] [rbp-50h]
__int64
v12;
// [rsp+38h] [rbp-48h]
__int64
v13;
// [rsp+40h] [rbp-40h]
__int64
v14;
// [rsp+48h] [rbp-38h]
__int64
v15;
// [rsp+50h] [rbp-30h]
unsigned
int
v16[4];
// [rsp+60h] [rbp-20h] BYREF
_Thrd_t v17;
// [rsp+70h] [rbp-10h] BYREF
BOOL
pbDebuggerPresent;
// [rsp+B8h] [rbp+38h] BYREF
pbDebuggerPresent = 0;
CurrentProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if
( pbDebuggerPresent )
{
v4 = (
__int64
(**)())operator
new
(8ui64);
*v4 = antidebug1_;
*(_QWORD *)v16 = beginthreadex(0i64, 0, (_beginthreadex_proc_type)StartAddress, v4, 0, &v16[2]);
if
( !*(_QWORD *)v16 )
{
v16[2] = 0;
std::_Throw_Cpp_error(6);
LABEL_15:
TerminateThread(hObject, 0);
CloseHandle(hObject);
return
1i64;
}
if
( !v16[2] )
{
std::_Throw_Cpp_error(1);
__debugbreak();
}
if
( v16[2] == Thrd_id() )
{
std::_Throw_Cpp_error(5);
__debugbreak();
}
v17 = *(_Thrd_t *)v16;
if
( Thrd_join(&v17, 0i64) )
std::_Throw_Cpp_error(2);
}
v15 = 0i64;
v14 = 0i64;
v13 = 0i64;
v12 = 0i64;
v11 = 1;
ThrdAddr = 0i64;
if
( !(unsigned
int
)sub_140003529() )
{
v5 = 0;
for
( i = (unsigned
__int8
*)&unk_140008050;
!(unsigned
int
)sub_140002E4E(
(
__int64
)hObject,
RtlFillMemory_,
a2 + v5,
1i64,
*i,
(
__int64
)ThrdAddr,
v11,
v12,
v13,
v14,
v15);
++i )
{
if
( ++v5 >= 0x92B )
{
ResumeThread(hObject);
sub_140002A8E((
__int64
)hObject, 0i64, 0i64, v7, v9);
CloseHandle(hObject);
return
0i64;
}
}
goto
LABEL_15;
}
return
1i64;
}
__int64
__fastcall has_antidebug2_(
__int64
a1,
__int64
a2)
{
HANDLE
CurrentProcess;
// rax
__int64
(**v4)();
// rax
unsigned
int
v5;
// edi
unsigned
__int8
*i;
// rsi
__int64
v7;
// r9
__int64
v9;
// [rsp+20h] [rbp-60h]
unsigned
int
*ThrdAddr;
// [rsp+28h] [rbp-58h]
int
v11;
// [rsp+30h] [rbp-50h]
__int64
v12;
// [rsp+38h] [rbp-48h]
__int64
v13;
// [rsp+40h] [rbp-40h]
__int64
v14;
// [rsp+48h] [rbp-38h]
__int64
v15;
// [rsp+50h] [rbp-30h]
unsigned
int
v16[4];
// [rsp+60h] [rbp-20h] BYREF
_Thrd_t v17;
// [rsp+70h] [rbp-10h] BYREF
BOOL
pbDebuggerPresent;
// [rsp+B8h] [rbp+38h] BYREF
pbDebuggerPresent = 0;
CurrentProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if
( pbDebuggerPresent )
{
v4 = (
__int64
(**)())operator
new
(8ui64);
*v4 = antidebug1_;
*(_QWORD *)v16 = beginthreadex(0i64, 0, (_beginthreadex_proc_type)StartAddress, v4, 0, &v16[2]);
if
( !*(_QWORD *)v16 )
{
v16[2] = 0;
std::_Throw_Cpp_error(6);
LABEL_15:
TerminateThread(hObject, 0);
CloseHandle(hObject);
return
1i64;
}
if
( !v16[2] )
{
std::_Throw_Cpp_error(1);
__debugbreak();
}
if
( v16[2] == Thrd_id() )
{
std::_Throw_Cpp_error(5);
__debugbreak();
}
v17 = *(_Thrd_t *)v16;
if
( Thrd_join(&v17, 0i64) )
std::_Throw_Cpp_error(2);
}
v15 = 0i64;
v14 = 0i64;
v13 = 0i64;
v12 = 0i64;
v11 = 1;
ThrdAddr = 0i64;
if
( !(unsigned
int
)sub_140003529() )
{
v5 = 0;
for
( i = (unsigned
__int8
*)&unk_140008050;
!(unsigned
int
)sub_140002E4E(
(
__int64
)hObject,
RtlFillMemory_,
a2 + v5,
1i64,
*i,
(
__int64
)ThrdAddr,
v11,
v12,
v13,
v14,
v15);
++i )
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课