首页
社区
课程
招聘
[原创]IDA对一个简单的键盘钩子逆向分析
发表于: 2015-10-28 16:35 9233

[原创]IDA对一个简单的键盘钩子逆向分析

2015-10-28 16:35
9233

文章之前先来个贱卖广告:
文章开始之前,先感谢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;
}

            文章结束!应该会有勘误,望指出。  谢谢大神的赏脸!


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 3
支持
分享
最新回复 (11)
雪    币: 33
活跃值: (244)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
附件传一下
2015-10-28 17:24
0
雪    币: 111
活跃值: (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
楼主的附件在文章上面
2015-10-28 17:54
0
雪    币: 118
活跃值: (72)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
愿楼主能早日找到合适的工作。
2015-10-28 19:34
0
雪    币: 716
活跃值: (158)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
谢谢!
2015-10-28 21:06
0
雪    币: 9479
活跃值: (757)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
把一个基础程序的反编译拿出来想证明什么的,你应该说点有技术含量的点
2015-10-28 21:59
0
雪    币: 716
活跃值: (158)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
OK,我会证明的。要不你给我提供目标。
2015-10-28 22:51
0
雪    币: 118
活跃值: (72)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
楼主可以试着找几个热门恶意程序分析一下.这样更能展示自己的分析功底.
2015-10-29 10:05
0
雪    币: 716
活跃值: (158)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
想法不错,不过这几天有点事,这篇文章也是随便拿来练手的记录而已,也算是一个简单的教程吧。
2015-10-29 11:56
0
雪    币: 7
活跃值: (95)
能力值: ( LV4,RANK:42 )
在线值:
发帖
回帖
粉丝
10
分析点api的内部实现吧  这个实在没什么看头..
2015-10-29 12:17
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
刷机这块逆向懂不懂的,rom包破解,root这方面的,懂的话可以跟我联系
2015-10-29 12:36
0
雪    币: 716
活跃值: (158)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
那就得分析kernel.dll 和ntdll.dll了
2015-10-29 20:05
0
游客
登录 | 注册 方可回帖
返回
//