文章之前先来个贱卖广告:
文章开始之前,先感谢15PB的老师们之前的辛苦教育。
键盘钩子程序逆向.zip
一、IDA静态逆向分析
(一)、可执行文件流程图
(1)函数1 地址: main 函数功能: 主函数功能,载入模块,调用模块中的导出功能(挂钩、脱钩)
1)int __cdecl main(int argc, const char **argv, const char **envp)
得出分析结果:
ⓐ此函数的返回类型为 int 类型。
ⓑ此函数的调用方式为__cdecl 调用方式,此种调用方式最显著的特点是谁调用此函数谁清栈,所以也可以根据清栈方式判定目标函数是否属于__cdecl调用方式。
push offset LibFileName ; "Keyhook.dll"
call ds:LoadLibraryA
mov ebx, eax call ds:LoadLibraryA
mov ebx, eax
test ebx, ebx
jnz short loc_401030
得出分析结果:
ⓐ调用了LoadLibraryA API函数,且传入的参数是Keyhook.dll。也就是说程序载入Keyhook.dll模块,并判断是否正确载入模块。
反汇编成C:
2)如果1)不执行跳转( ebx = eax = 0)。
push 1 ; uType
push offset Caption ; "f婮T"
push offset Text ; "}廵Qd"
push eax ; hWnd
call ds:MessageBoxW
得出分析结果:
ⓐ调用MessageBoxW API函数。
ⓑ窗口句柄:hWnd = eax =0;Text,Caption 参数为乱码,uType = 1。
可以根据上一个判断跳转推断,因为是 ebx = eax = 0 也就是说LoadLibraryA API("Keyhook.dll")载入模块失败,执行的MessageBoxW ,因此这很可能是一个提示载入模块失败的对话框。
1)、2)反汇编成C:
if(LoadLibraryA API("Keyhook.dll"))
{}
MessageBoxW(NULL,L”XXX”,L”YYY”,1);
retuen 0;
3)如果1)的判断执行跳转。
mov esi, ds:GetProcAddress
push edi
push offset ProcName ; "HookStart"
push ebx ; hModule
call esi ; GetProcAddress
push offset aHookstop ; "HookStop"
push ebx ; hModule
mov edi, eax ;HookStart
call esi ; GetProcAddress
mov [ebp+var_4], eax ;HookStop
call edi ;HookStart
push offset Format ; "脱钩请输入 'q'\n"
call ds:printf
mov esi, ds:getchar
得出分析结果:
ⓐ调用两次GetProcAddress API函数。 hModule句柄参数是1)LoadLibraryA API("Keyhook.dll")得到的句柄;第二个参数分别是 获取名为: "HookStart","HookStop",也就是获取 Keyhook.dll 模块中名为:"HookStart","HookStop"的两个函数地址,且两个函数都没有使用到返回值,所以可以初步判定两个函数的返回值类型为 void类型。
ⓑ调用printf ,输出"脱钩请输入 'q'\n"
反汇编成C:
因为 GetProcAddress 获取的是一个函数地址,所以最好定义2个函数地址类型变量。
typedef void (*LPFUNCTION_HOOKSTART) ();
typedef void (*LPFUNCTION_HOOKSTOP) ();
LPFUNCTION_HOOKSTART HookStart = 0 ;
LPFUNCTION_HOOKSTOP HookStop = 0;
GetProcAddress(LoadLibraryA ("Keyhook.dll"),”HookStart” );
GetProcAddress(LoadLibraryA ("Keyhook.dll"),”HookStop” );
HookStart();
printf("脱钩请输入 'q'\n");
4)继续往下执行。
call esi ; getchar
cmp eax, 71h
jnz short loc_401063 ;call esi ; getchar
分析得出结果:
ⓐ调用getchar ,判断返回值 eax == 71h。getchar 调用的过后面一般会伴随着(do while类型)循环,这里也不例外。
反汇编成C:
while(getchar == 71h )
{};
5)如果getchar == 71h 执行为真。
call [ebp+var_4] ;HookStop
push ebx ; hLibModule
call ds:FreeLibrary
pop edi
pop esi
xor eax, eax
pop ebx
mov esp, ebp
pop ebp
retn
分析得出结果:
ⓐ调用 HookStop 函数。
ⓑ调用 FreeLibrary 函数,参数为ebx = LoadLibraryA ("Keyhook.dll")。
4)、5)反汇编成C:
while(getchar == 71h )
{
HookStop();
FreeLibrary( LoadLibraryA ("Keyhook.dll"));
};
return 0;
总结:
ⓐ载入Keyhook.dll模块。
ⓑ获取Keyhook.dll模块中的HookStart 函数地址。
ⓒ获取Keyhook.dll模块中的HookStop 函数地址。
ⓓ执行Keyhook.dll模块中的HookStart函数。
ⓔ调用printf("脱钩请输入 'q'\n")。
ⓕ使用while循环,getchar == 71h为判断依据。
ⓖ释放Keyhook.dll模块。
翻译成C++:
typedef void (*LPFUNCTION_HOOKSTART) ();
typedef void (*LPFUNCTION_HOOKSTOP) ();
int __cdecl main(int argc, const char argv[],)
{
if(LoadLibraryA API("Keyhook.dll"))
{
LPFUNCTION_HOOKSTART HookStart = 0 ;
LPFUNCTION_HOOKSTOP HookStop = 0;
GetProcAddress(LoadLibraryA ("Keyhook.dll"),”HookStart” );
GetProcAddress(LoadLibraryA ("Keyhook.dll"),”HookStop” );
HookStart();
printf("脱钩请输入 'q'\n");
while(getchar == 71h )
{
HookStop();
FreeLibrary( LoadLibraryA ("Keyhook.dll"));
};
return 0;
}
MessageBoxW(NULL,L”XXX”,L”YYY”,1);
retuen 0;
}
现在需要分析Keyhook.dll模块的HookStart(),HookStop() 两个导出函数的具体功能。
(2)函数2地址: Keyhook.dll.HookStart(), 函数功能: 实现对notepad.exe进程挂钩
因为Keyhook.dll的DllEntryPoint_0没有任何有意义的算法。所以就不分析了,直接分析Keyhook.dll.HookStart()。
从函数头的注释; Exported entry 1. HookStart,可以侧面映证该函数为导出函数。
汇编代码:
lea edi, [ebp+var_C0]
mov ecx, 30h
mov eax, 0CCCCCCCCh
rep stosd
mov esi, esp
push 0 ; dwThreadId
mov eax, hmod
push eax ; hmod
push offset fn ; lpfn
push 2 ; idHook
call ds:SetWindowsHookExW
cmp esi, esp
call sub_10011145
mov esp, ebp
得出分析结果:
ⓐ调用 SetWindowsHookExW 函数。参数1(idHook = 2);参数2( lpfn = fn,函数地址,也就是说该值是一个函数的起始地址,稍后再分析);参数3(eax = hmod,hmod的值存放在.data数据段中且hmod = 0,因此可以判断此函数是全局变量或者static 变量);参数4(dwThreadId = 0)。
ⓑsub_10011145的调用只是用于堆栈检查,不去要做详细的分析。但由此函数不需要清栈可以推断,这是stdcall/thiscall中的一个,但HookStart()函数里没有使用到this值,所以可以判断这是stdcall调用方式。
反汇编成C:
__std(export) void HookStart()
{
DWORD idHook = 2;
HMODULE hmod = 0;
DWORD dwThreadId = 0;
SetWindowHookExW(idHook ,lpfn , hmod,dwThreadId);
return ;
}
(3)函数3地址: fn 重命名: lpfn 函数功能: 对目标进程进行判断(过滤); int __stdcall lpfn(int nCode, WPARAM wParam, LPARAM lParam)
1)汇编代码:
mov eax, ___security_cookie ; eax = ebx
xor eax, ebp
mov [ebp+var_4], eax
mov [ebp+szStr], 0
push 103h ; Size
push 0 ; Val
lea eax, [ebp+szDst]
push eax ; Dst
call j_memset
add esp, 0Ch
mov [ebp+Dword_1], 0
cmp [ebp+nCode], 0
jl short loc_1001145F
得出分析结果:
ⓐ调用j_memset 函数初始化一个字符串,目标数据(参数1:szDst),初始值(参数2:Val = 0),长度为(参数3:Size = 103h)。
ⓑ局部变量 Dword_1 =0。
ⓒ判断参数 nCode 是否等于0。
2)、汇编代码:
mov eax, [ebp+lParam]
and eax, 80000000h
jnz short loc_1001145F
得出分析结果:
ⓐ参数lParam 与 80000000h 相与,然后进行判断,不等于则跳转。
1)、2)反汇编成C:
int __stdcall lpfn(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szDst[103h] ={0,};
DWORD Dword_1 = 0;
if( nCode >= 0 )
{
if(lParam & 80000000h)
{
}
}
}
3)汇编代码:
mov esi, esp
push 104h ; nSize
lea eax, [ebp+szStr]
push eax ; lpFilename
push 0 ; hModule
call ds:GetModuleFileNameA
cmp esi, esp
call sub_10011145
push 5Ch ; int
lea eax, [ebp+szStr]
push eax ; Str
call sub_100110A5
add esp, 8
得出分析结果:
ⓐ调用GetModuleFileNameA ,参数1(hModule = 0 ,默认本模块自身),参数2(lpFilename = szStr =szDst),参数3(nSize = 104h)。
ⓑsub_10011145 函数只是对栈检查而已,无需多虑。
ⓒ调用 sub_100110A5 函数,因为传入了两个参数(Str,5Ch),且不像是系统自调函数,所以需要跟入,稍后跟进分析。
4)汇编代码:
mov [ebp+Dword_1], eax
mov esi, esp
push offset Str2 ; "notepad.exe"
mov eax, [ebp+Dword_1]
add eax, 1
push eax ; Str1
call ds:_stricmp
add esp, 8
cmp esi, esp
call sub_10011145
test eax, eax
jnz short loc_1001145F
得出分析结果:
ⓐ获取调用sub_100110A5 函数的返回值,传给变量Dword_1 = eax。
ⓑ调用_stricmp 函数,参数1(Dword_1 + 1 == eax+1 ),参数2(Str2 是一个全局变量,Str2 == )。由char Str2[]
.rdata:10015658 6E 6F 74 65 70 61 64 2E+Str2 db 'notepad.exe',0 可以判定这是一个CHAR 数组全局/静态变量,但如果是静态变量会在某个函数中出现定义,但却没有,所以可以确定这是一个全局变量。
ⓒsub_10011145没有对eax进行操作,而_stricmp 有返回值(使用到eax),所以test eax, eax,判定的是_stricmp 的返回值是否为假。
3)、4)反汇编成C:
CHAR g_szProcName[] = "notepad.exe"; //全局变量
HMODULE hModule = NULL;
GetModuleFileNameA(hModule,szDst,104h );
Dword_1 = sub_100110A5(szStr,5Ch);
if(_stricmp(Dword_1 + 1 ,g_szProcName))
{
}
5)在4)test eax, eax 结果为0的情况下,执行的汇编代码:
mov eax, 1
jmp short loc_10011480
ⓐeax +1
ⓑ跳转至loc_10011480 继续执行(后面的是系统自动调用的函数,主要是做栈安全检测),返回eax结果,结束函数调用。
反汇编成C:
return 1;
6)汇编代码:
mov esi, esp
mov eax, [ebp+lParam]
push eax ; lParam
mov ecx, [ebp+wParam]
push ecx ; wParam
mov edx, [ebp+nCode]
push edx ; nCode
mov eax, hhk
push eax ; hhk
call ds:CallNextHookEx
cmp esi, esp
得出分析结果:
ⓐ调用CallNextHookEx 函数。参数1(eax = hhk = 0,hhk是一个全局变量),参数2(edx = nCode),参数3(ecx = wParam),参数4(eax = lParam)。
ⓑ再往下是占清理代码和返回CallNextHookEx 的返回值。
反汇编成C:
HHOOK g_hhk =0;
return CallNextHookEx(g_hhk, nCode,wParam,lParam);
总结:
此函数主要是用于判定目标程序是不是"notepad.exe",如果是就返回CallNextHookEx的返回值,即挂钩,如果不是就返回 1。总而言至就是判断当前扑捉到的是不是"notepad.exe"进程,然后对"notepad.exe"进程挂钩。
翻译成C++:
HHOOK g_hhk =0; //全局变量
CHAR g_szProcName[] = "notepad.exe"; //全局变量
int __stdcall fn_0(int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szDst[103h] ={0,};
DWORD Dword_1 = 0;
if( nCode >= 0 )
{
if(lParam & 80000000h)
{
HMODULE hModule = NULL;
GetModuleFileNameA(hModule,szDst,104h );
Dword_1 = sub_100110A5(szStr,5Ch);
if(_stricmp(Dword_1 + 1 ,g_szProcName == 0))
{
return 1;
}
return CallNextHookEx(g_hhk, nCode,wParam,lParam);
}
}
}
(4)函数4地址: Keyhook.dll.HookStop() 函数功能: 此函数功能只是对以挂钩的进程进行脱钩。
mov esi, esp
mov eax, hhk
push eax ; hhk
call ds:UnhookWindowsHookEx
cmp esi, esp
call sub_10011145
mov hhk, 0
得出分析结果:
ⓐ调用UnhookWindowsHookEx进行脱钩,参数(hhk:全局变量)
反汇编成C:
ⓑ将全局变量置为 0(hhk= 0).
UnhookWindowsHookEx(hhk);
hhk= 0;
总结:
此函数主要是进行脱钩。
翻译成C:
void __std(export) HookStop()
{
UnhookWindowsHookEx(hhk);
g_hhk= 0;
return 0;
}
整合翻译成C:
HHOOK g_hhk =0; //全局变量
CHAR g_szProcName[] = "notepad.exe"; //全局变量
BOOL __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
return TRUE;
}
__std(export) void HookStart()
{
DWORD idHook = 2;
HMODULE hmod = 0;
DWORD dwThreadId = 0;
SetWindowHookExW(idHook ,lpfn , hmod,dwThreadId);
return ;
}
int __stdcall lpfn (int nCode, WPARAM wParam, LPARAM lParam)
{
CHAR szDst[0x103] ={0,};
DWORD Dword_1 = 0;
if( nCode >= 0 )
{
if(lParam & 80000000h)
{
HMODULE hModule = NULL;
GetModuleFileNameA(hModule,szDst,0x104 );
Dword_1 = sub_100110A5(szStr,0x5C);
if(_stricmp(Dword_1 + 1 ,g_szProcName == 0))
{
return 1;
}
return CallNextHookEx(g_hhk, nCode,wParam,lParam);
}
}
}
void __std(export) HookStop()
{
UnhookWindowsHookEx(g_hhk);
g_hhk= 0;
return 0;
}
文章结束!应该会有勘误,望指出。 谢谢大神的赏脸!
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课