GFSJ0078-【EASYHOOK】
hook
主函数代码如下
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE FileA;
DWORD NumberOfBytesWritten;
char Buffer[32];
sub_401370(aPleaseInputFla);
scanf("%31s", Buffer);
if ( strlen(Buffer) == 19 )
{
sub_401220();
FileA = CreateFileA(FileName, 0x40000000u, 0, nullptr, 2u, 0x80u, nullptr);
WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, nullptr);
sub_401240(Buffer, &NumberOfBytesWritten);
if ( NumberOfBytesWritten == 1 )
sub_401370(aRightFlagIsYou);
else
sub_401370(aWrong);
system(Command);
return 0;
}
else
{
sub_401370(aWrong);
system(Command);
return 0;
}
}
从主函数看起来 关键逻辑像是在 WriteFile 和 sub\401240 里面 程序先调用 sub\401220 然后创建一个文件 接着调用 WriteFile 把输入写入文件 最后调用 sub\401240 表面上看 sub\401240 好像是最终校验函数 但是实际真正的校验发生在 WriteFile 被 hook 之后的函数里面
先看 sub\401220
int sub_401220()
{
HMODULE LibraryA;
DWORD CurrentProcessId;
CurrentProcessId = GetCurrentProcessId();
hProcess = OpenProcess(0x1F0FFFu, 0, CurrentProcessId);
LibraryA = LoadLibraryA(LibFileName);
WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName);
lpAddress = WriteFile_0;
if ( !WriteFile_0 )
return sub_401370((int)&unk_40A044);
unk_40C9B4 = *(_DWORD *)lpAddress;
unk_40C9B8 = *((_BYTE *)lpAddress + 4);
byte_40C9BC = -23;
dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;
return sub_4010D0();
}
GetCurrentProcessId 用来获取当前进程 ID
OpenProcess 用 0x1F0FFF 权限打开当前进程
LoadLibraryA 加载 DLL
这里的 LibFileName 大概率是 kernel32 dll
GetProcAddress 根据 ProcName 获取函数地址
这里 ProcName 大概率是 WriteFile
获取到 WriteFile 地址之后 程序把地址保存到 lpAddress
接着保存 WriteFile 入口的前 5 个字节
unk_40C9B4 = *(_DWORD *)lpAddress;
unk_40C9B8 = *((_BYTE *)lpAddress + 4);
这两句一共保存了 5 个字节 然后程序构造 hook 补丁
byte_40C9BC = -23;
dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5;
这里 byte\40C9BC 是 signed char 所以 -23 对应的无符号字节是 0xE9 0xE9 是 x86 里面的 near jmp 指令 dword\40C9BD 是 jmp 的相对偏移
也就是
目标地址 - 当前地址 - 5
所以这里构造出来的 5 字节补丁是
E9 xx xx xx xx
含义是
jmp sub_401080
也就是说 程序准备把 WriteFile 的入口改成跳转到 sub\401080
然后进入 sub\4010D0
BOOL sub_4010D0()
{
DWORD v1;
DWORD flOldProtect;
v1 = 0;
VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);
WriteProcessMemory(hProcess, lpAddress, &byte_40C9BC, 5u, nullptr);
return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);
}
sub\4010D0 负责真正写入 hook
VirtualProtectEx 把 WriteFile 入口所在内存页改成可读写执行
这里的 4 对应 PAGE\EXECUTE\READWRITE
然后 WriteProcessMemory 把前面构造好的 5 字节 jmp 写到 WriteFile 入口
最后再次调用 VirtualProtectEx 恢复原来的内存权限
所以 sub\401220 执行完成以后
WriteFile 的入口已经被改成
jmp sub_401080
因此主函数里面这句
WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, nullptr);
实际不会直接进入系统的 WriteFile
而是会先跳到 sub\401080
sub\401080 代码如下
int __stdcall sub_401080(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped)
{
int v5;
v5 = sub_401000(lpBuffer, nNumberOfBytesToWrite);
sub_401140();
WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
if ( v5 )
*lpNumberOfBytesWritten = 1;
return 0;
}
这里才是真正的校验入口 它先调用 sub\401000 参数 lpBuffer 就是输入的 Buffer 参数 nNumberOfBytesToWrite 是 0x13 也就是 19 所以 sub\401000 会处理输入的 19 个字节
然后 sub\401080 调用 sub\401140 sub\401140 的作用是恢复 WriteFile 原始入口
代码如下
BOOL sub_401140()
{
DWORD v1;
DWORD flOldProtect;
v1 = 0;
VirtualProtectEx(hProcess, lpAddress, 5u, 4u, &flOldProtect);
WriteProcessMemory(hProcess, lpAddress, &unk_40C9B4, 5u, nullptr);
return VirtualProtectEx(hProcess, lpAddress, 5u, flOldProtect, &v1);
}
它同样先修改内存页权限 然后把之前保存的 WriteFile 前 5 个字节写回去 这样后面再调用 WriteFile 的时候就不会再次跳到 sub\401080 也就避免了无限递归 所以 sub\401080 的执行顺序是 先用 sub\401000 校验输入 然后恢复 WriteFile 原始字节 然后真正写文件 如果 sub\401000 返回 1 就把 lpNumberOfBytesWritten 改成 1
主函数后面判断的是
if ( NumberOfBytesWritten == 1 )
所以最终能不能输出 right 关键就看 sub\401000 是否返回 1 继续看 sub\401000
int __cdecl sub_401000(int a1, int a2)
{
char i;
bool v3;
char v4;
char v5;
int v6;
for ( i = 0; i < a2; ++i )
{
if ( i == 18 )
{
*(_BYTE *)(a1 + 18) ^= 0x13u;
}
else
{
v3 = (i & 0x80000001) == 0;
if ( i < 0 )
v3 = (((i & 1) - 1) | 0xFFFFFFFE) == -1;
if ( v3 )
v4 = *(_BYTE *)(i + a1 + 2);
else
v4 = *(_BYTE *)(i + a1) - i;
*(_BYTE *)(i + a1) = i ^ v4;
}
}
v5 = 0;
if ( a2 <= 0 )
return 1;
v6 = 0;
while ( byte_40A030[v6] == *(_BYTE *)(v6 + a1) )
{
v6 = ++v5;
if ( v5 >= a2 )
return 1;
}
return 0;
}
exp
target = [
0x61, 0x6a, 0x79, 0x67, 0x6b,
0x46, 0x6d, 0x2e, 0x7f, 0x5f,
0x7e, 0x2d, 0x53, 0x56, 0x7b,
0x38, 0x6d, 0x4c, 0x6e
]
flag = [0] * 19
for i in range(19):
if i == 18:
flag[18] = target[18] ^ 0x13
elif i & 1:
flag[i] = ((target[i] ^ i) + i) & 0xff
else:
flag[i + 2] = target[i] ^ i
flag[0] = ord('f')
print(bytes(flag).decode())
flag
flag{Ho0k_w1th_Fun}
评论