软件逆向工程上机作业

实验一

已知notepad.exe的各节区的RVA地址/RAW偏移范围如下:

节区名称 RVA起始地址 节区在内存中大小 RAW起始偏移量 节区在外存中大小
.text 00001000H 7748H 00000400H 7800H
.data 00009000H 1BA8H 00007C00H 800H
.rsrc 0000B000H 7F20H 00008400H 8000H

PE头中的DataDirectory[1]这一项的RAW偏移在160H~167H。

  1. 解析notepad.exe的IMAGE_IMPORT_DESCRIPTOR数组的内容,填充下面表格:
OriginalFirstThunk Name FirstThunk
RVA RAW RVA RAW 库文件名 RVA RAW
00007990H 00006A04H 00007AACH 00006EACH comdlg32.dll 000012C4H 000006C4H
00007840H 00006C40H 00007AFAH 00006EFAH SHELL32.dll 00001174H 00000574H
00007980H 00006D80H 00007B3AH 00006F3AH WINSPOOL.DRV 000012B4H 000006B4H
000076ECH 00006AECH 00007B5EH 00006F5EH COMCTL32.dll 00001020H 00000420H
000079B8H 00006DB8H 00007C76H 00007076H msvcrt.dll 000012ECH 000006ECH
000076CCH 00006ACCH 00007D08H 00007108H ADVAPI32.dll 00001000H 00000400H
00007758H 00006B58H 000080ECH 000074ECH KERNEL32.dll 0000108CH 0000048CH
000076F4H 00006AF4H 0000825EH 0000765EH GDI32.dll 00001028H 00000428
00007854H 00006C54H 0000873CH 00007B3CH USER32.dll 00001188H 00000588H
  1. 查找所使用的KERNEL32.dll库的第一个函数的名称。

GetCurrentThreadId (RAW:00007424)

  1. 查找该函数在IAT中的IMAGE_THUNK_DATA32结构的内容。

7C8097B8

  1. 用OllyDbg调试notepad.exe,找出该函数被装载到的实际内存地址,比较其与(3)的IMAGE_THUNK_DATA32结构内容是否相同。解释为什么。

在不同的电脑、不同的操作系统中这个值都不相同,IAT不相同,所以实际函数被装载到内存地址也不相同。

实验二

用x86汇编语言实现以下C语言函数功能

函数 功能
unsigned intstrlen(char *s); 计算给定字符串的长度,不包括'\0'在内
char strchr(const char s,charc); 查找字符串s中首次出现字符c的位置,返回首次出现c的位置的指针,如果s中不存在c则返回0
intstrcmp(const char *s1,const char *s2); 当s1<s2时,返回为-1;当s1==s2时,返回值= 0;当s1>s2时,返回1。
char *strset(char *s, char c); 把字符串s中的所有字符都设置成字符c

实验要求:

  1. 学习用MASM32 + Visual Studio进行汇编程序开发的基本方法。
  2. 在老师提供的程序框架下,依次实现上表中的4个函数功能。
  3. 完成上机报告(word或pdf),详细陈述程序设计思路和关键语句含义。

实验过程及结果:

strchr

实验思路:使用SCASB将给定字符串诸位与chr进行比较,若一样则记录当前地址,要计算的偏移量为当前地址与开始地址的差值。

代码及注释:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
; strchr.asm

.386
.model flat, stdcall

include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
szText  db  "Reverse Engineering", 0
chr     db  'i'
format  db  "%d", 0AH, 0

.code

main PROC
    LEA EDI, szText             ;将字符串偏移地址送给EDI
    MOV ECX,0FFFFFFFFH          ;ECX置为-1
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;strchr逻辑
    ;SCAS:将AL/AX/EAX与[EDI]比较,并对EDI自增/自减
    MOV AL, chr                 ;将要比较的字符'i'送给AL
    MOV EBX, EDI                ;保存szText首地址
    REPNE SCASB                 ;执行SCASB逻辑
    SUB EDI, EBX                ;'i'在字符串中的位置=EDI-EBX

    INVOKE crt_printf, addr format, EDI;输出结果
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    INVOKE crt_getchar
    INVOKE ExitProcess, 0
main ENDP

END main

img

strcmp

实验思路:使用SCASB和STOSB将两个字符串逐位进行比较。逐个比较字符的过程中,若不相等则根据实际的大小关系得出结果;若为空则比较完成,跳转到返回结果初;若相等则继续比较下一个字符。

代码及注释:

; strcmp.asm

.386
.model flat, stdcall

include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
format      db  "%d", 0AH, 0
szText      db  "Reverse Engineering", 0
szText2     db  "Reverse Engineering", 0    ;szText==szText2
szText3     db  "Reverse Eng", 0            ;szText>szText3
szText4     db  "Reverse Engj", 0           ;szText<szText4
szText5     db  "Reverse Engh", 0           ;szText>szText5

.code

main PROC
;重复将四次比较结果输出
    LEA ESI, szText
    LEA EDI, szText2    ;result=0
    call compare
    LEA ESI, szText
    LEA EDI, szText3    ;result=1
    call compare
    LEA ESI, szText
    LEA EDI, szText4    ;result=-1
    call compare
    LEA ESI, szText
    LEA EDI, szText5    ;result=1
    call compare
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;strcmp逻辑

    INVOKE crt_getchar
    INVOKE ExitProcess, 0

COMPARE:
    LODSB               ;将ds:esi的第一个字节装入寄存器AL,同时[esi]+1
    SCASB               ;将es:edi的第一个字节和AL相减,同时[edi]+1
                        ;cmpsb 将edi 和 esi的字节相减
    JNE NOTEQ           ;不相等,转到NOTEQ处理
             
    TEST AL, AL         ;看看AL是否为NULL
    JNE COMPARE         ;不为空,则比较下一个
    XOR EAX, EAX        ;为空,将寄存器EAX清空为0
    JMP ENDCMP          ;跳转到返回结果的地方

NOTEQ:                  ;不相等
    MOV EAX, -1         ;不相等时的处理,将EAX置-1
    JL ENDCMP           ;如果是大于的话,跳到返回结果的地方
    NEG EAX             ;将EAX取反,变为1
             
ENDCMP:       
    MOV EDI, EAX        ;结果存入result
    INVOKE crt_printf, addr format, EDI
    RET
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
main ENDP

END main

img

strlen

实验思路:依次比较字符串每个字符是否为空(空位字符串结束标志),在二进制表示下,计算ECX的值,最后将结果放入EDI中。

代码及注释:

; strlen.asm

.386
.model flat, stdcall

include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
szText  db  "Reverse Engineering", 0
format  db  "length = %d", 0AH, 0

.code

main PROC
    LEA EDI, szText             ;将字符串偏移地址送给EDI
    MOV ECX,0FFFFFFFFH          ;ECX置为-1
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;strlen逻辑
    XOR EAX, EAX                ;EAX清零
    REPNE SCASB                 ;执行SCASB逻辑,ECX=-1-(字符串长度+1)
    ADD ECX, 2                  ;ECX=-(字符串长度)
    NEG ECX                     ;ECX=字符串长度
    MOV EDI, ECX                ;将结果送到EDI
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    INVOKE crt_printf, addr format, EDI;输出结果

    INVOKE crt_getchar
    INVOKE ExitProcess, 0
main ENDP

END main

img

strset

实验思路:先用strlen将字符串长度读取并存入ECX中,再用STOSB将chr所存放的字符逐位替换原字符串中的字符。

关键代码及注释:

; strset.asm

.386
.model flat, stdcall

include kernel32.inc
includelib kernel32.lib

include msvcrt.inc
includelib msvcrt.lib

.data
szText  db  "Reverse Engineering", 0
chr     db  'j'

.code

main PROC
    LEA EDI, szText             ;将字符串偏移地址送给EDI
    MOV ECX,0FFFFFFFFH          ;ECX置为-1
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;strlen逻辑
    XOR EAX, EAX                ;EAX清零
    REPNE SCASB                 ;执行SCASB逻辑,ECX=-1-(字符串长度+1)
    ADD ECX, 2                  ;ECX=-(字符串长度)
    NEG ECX                     ;ECX=字符串长度
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;strset逻辑
    ;STOS:将AL/AX/EAX的值写入EDI指向的内存
    MOV AL, chr                 ;将要写入的字符串送到AL
    LEA EDI, szText             ;重新将字符串偏移地址送给EDI
    REP STOSB                   ;执行STOSB逻辑
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    INVOKE crt_printf, addr szText;输出结果

    INVOKE crt_getchar
    INVOKE ExitProcess, 0
main ENDP

END main

img

实验三

实验内容:

修改第6章的HookDll.cpp,钩取对notepad的输入,使得:

  1. 输入文本仍能正常显示;
  2. 所有输入文本能够记录到input.txt文件中。

实验过程及结果:

实验思路:原HookDll.cpp的关键回调函数如下,主要更改代码以实现功能的地方用“**”标注:

1
2
3
4
5
6
7
8
9
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){
……
        if( !_stricmp(p + 1, "notepad.exe") )
                 return 1;
                 //**主要更改的逻辑在这里**
……
    // 当前进程不是notepad.exe,将消息传递给下一个钩子
        return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

return 1;的意思是:如果进程是notepad.exe,则不会将消息传递给下一个钩子,而实验要求消息可以正常显示出来,并且将所打印的消息记录到另一个文件夹中,则可以将问题分解为2个:

  1. 将键盘输入的消息记录到某处,并且不会在这个时候return 1而是继续将消息传递给下一个钩子。解决方案:此回调函数的wParam参数包含了WPARAM类型的按键的虚键码,将此参数的内容转换成可读的字符类型即可。

  2. 将保存着wParam内容的参数用文件读写的方式以”a”的方式写入,即可在一个文件夹内保存。解决方案:使用C的fopen函数或者Windows编程中的CreateFile函数

    参考来自:https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include "stdio.h"
#include "windows.h"
#include "tchar.h"
#include "ctype.h"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		g_hInstance = hinstDLL;
		break;

	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	char szPath[MAX_PATH] = {
		0,
	};
	char *p = NULL;
	char ch;  //ch存放按键的内容
	FILE *fp; //打开文件流
	//ToAscii函数参数
	byte ks[256];
	GetKeyboardState(ks); //函数功能:该函数将256个虚拟键的状态拷贝到指定的缓冲区中
	WORD w;
	UINT scan = 0;

	if (nCode >= 0)
	{
		// bit 31 : 0 => press, 1 => release
		if (!(lParam & 0x80000000))
		{
			GetModuleFileNameA(NULL, szPath, MAX_PATH);
			p = strrchr(szPath, '\\');
			//若加载当前DLL的进程的可执行文件名称为notepad.exe,则消息不会传递给下一个钩子
			if (!_stricmp(p + 1, "notepad.exe"))
			{
				ToAscii(wParam, scan, ks, &w, 0);
				//函数功能:该函数将指定的虚拟键码和键盘状态翻译为相应的字符或字符串。
				//该函数使用由给定的键盘布局句柄标识的物理键盘布局和输入语言来翻译代码。
				//函数原型:int ToAscii(UINT uVirtKey,UINT uScanCode,PBYTE lpKeyState,LPWORD lpChar,UINT uFlags);
				ch = (char)w;

				fp = fopen("input.txt", "a"); //打开流
				fprintf(fp, "%c", ch);		  //写入文件操作
				fclose(fp);					  //关闭流
											  //return 1;
			}
		}
	}
	// 当前进程不是notepad.exe,将消息传递给下一个钩子
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C"
{
#endif
	__declspec(dllexport) void HookStart()
	{
		g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
	}

	__declspec(dllexport) void HookStop()
	{
		if (g_hHook)
		{
			UnhookWindowsHookEx(g_hHook);
			g_hHook = NULL;
		}
	}
#ifdef __cplusplus
}
#endif

实验四

InjectDll.cpp

_tmain

  • 检查输入参数正确性
  • 为程序设置权限
  • 执行注入的操作

InjectDll

  • 获得ID对应目标进程句柄[OpenProcess]
  • 将szDllPath路径字符串写入在目标进程地址空间中开辟一块存储空间存放DLL路径名(szDllPath)[VirtualAllocEx/WriteProcessMemory]
  • 获取当前进程地址空间中LoadLibraryW()函数的地址,该函数由kernel32.dll导入[GetModuleHandle/GetProcAddress]
  • 在目标进程中运行线程,该线程执行LoadLibraryW()函数并传入被注入DLL路径作为参数[CreateRemoteThread/WaitForSingleObject]

SetPrivilege

  • 获取进程的令牌句柄[OpenProcessToken]
  • 函数查看系统权限的特权值,返回信息到一个LUID结构体里[LookupPrivilegeValue]
  • 启用或禁止,指定访问令牌的特权[AdjustTokenPrivileges]
  • 有错误则输出错误信息[GetLastError]
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include "windows.h"
#include "tchar.h"

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;
    
    //OpenProcessToken->获取进程的令牌句柄
    /*
      参数:
        1.当前进程为GetCurrentProcess()
        2.访问令牌特权
        3.AdjustTokenPrivileges的第一个参数
    */
    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
                          &hToken) ){
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }


    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    /*
    AdjustTokenPrivilege
    BOOL WINAPI AdjustTokenPrivileges(   
                                __in          HANDLE TokenHandle,   
                                __in          BOOL DisableAllPrivileges,   
                                __in_opt      PTOKEN_PRIVILEGES NewState,   
                                __in          DWORD BufferLength,   
                                __out_opt     PTOKEN_PRIVILEGES PreviousState,   
                                __out_opt     PDWORD ReturnLength   
                                );  
                                1.OpenProcessToken第三个指针参数传出的句柄值
                                2.是否禁用所有所有的特权(这里填false)
                                3.新的TOKEN_PRIVILEGES的特权结构体指针
                                4.是上面结构体的字节长度(sizeof)
                                5.接受原先的特权的结构体
                                6.这个结构体的字节长度的指针
    */
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
    { 
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
    } 

    //输出错误信息
    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    } 

    return TRUE;
}

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
    HANDLE hProcess = NULL, hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;
    DWORD dwBufSize = (DWORD)(lstrlen(szDllPath) + 1) * sizeof(TCHAR);
    LPTHREAD_START_ROUTINE pThreadProc;

    // 获得dwPID进程ID对应的目标进程句柄
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
        return FALSE;

    // 在目标进程地址空间中为DLL路径名szDllPath开辟一块存储空间,将szDllPath路径字符串写入该空间
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

    // 获取当前进程地址空间中LoadLibraryW()函数的地址,该函数由kernel32.dll导入
    hMod = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");
    
    // 在目标进程中运行线程,该线程执行LoadLibraryW()函数并传入被注入DLL路径作为参数
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

int _tmain(int argc, TCHAR *argv[])
{//程序入口

    //如果参数数量不对则退出并提示
    if( argc != 3) {
        _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
        return 1;
    }

    //为程序提升权限
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // inject dll
    if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
        _tprintf(L"InjectDll(\"%s\") success.\n", argv[2]);
    else
        _tprintf(L"InjectDll(\"%s\") failed.\n", argv[2]);

    return 0;
}

MyDll.cpp

DllMain

  • 获得当前DLL的句柄
  • 根据fdwReason执行创建线程的程序部分[CreateThread]
  • 关闭一个打开的对象句柄[CloseHandle]

ThreadProc

  • 得到当前运行程序所在目录[GetModuleFileName]
  • 从右查找以“\”起始的字符串[_tcsrchr]
  • 拷贝字符串并添加文件名:所在文件夹目录名+"index.html"[_tcscpy_s]
  • 下载网页成html[URLDownloadToFile]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "windows.h"
#include "tchar.h"

//和URlDownloadToFile()函数有关
#pragma comment(lib, "urlmon.lib")

//当前DLL实例的句柄
HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam) {
    TCHAR szPath[MAX_PATH] = {0,};

    //GetModuleFileName->得到当前运行程序所在目录
    if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
        return FALSE;

    TCHAR *p = _tcsrchr( szPath, '\\' );
    //_tcsrchr->从右查找以“\\”起始的字符串

    if( !p )
        return FALSE;

    //_tcscpy_s->拷贝字符串并添加文件名:所在文件夹目录名+"index.html"
    _tcscpy_s(p+1, MAX_PATH, L"index.html");
    //也可写作lstrcpy(p+1, _T(“index.html”) );
    
    //URLDownloadToFile->下载网页成html
    URLDownloadToFile(NULL, L"http://www.xidian.edu.cn" , szPath, 0, NULL);

    return 0;
}

//DllMain一般为DLL的函数入口
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    
    HANDLE hThread = NULL;

    //获得当前DLL的句柄
    g_hMod = (HMODULE)hinstDLL;

    switch( fdwReason )//fdwReason指明了系统调用Dll的原因
    {
    case DLL_PROCESS_ATTACH://当一个DLL文件被映射到进程的地址空间时,系统调用该DLL的DllMain函数 
        //创建进程
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
        //关闭一个打开的对象句柄
        CloseHandle(hThread);
        
        break;
    }

    return TRUE;
}

MyDll2.cpp

DllMain

  • 根据fdwReason选择执行接下来的步骤
  • 获得当前DLL被装载到的进程的可执行文件的路径到szPath中[GetModuleFileName]
  • 从右面开始查找"\"[_tcsrchr]
  • 比较程序的名称是否为notepad.exe[lstrcmpi]
  • 调用IE访问[wsprintf]
  • 创建新进程[CreateProcess]
  • 关闭一个打开的对象句柄[CloseHandle]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "windows.h"
#include "tchar.h"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {

    //存放cmd命令的字符串数组
    TCHAR szCmd[MAX_PATH]  = {0,};
    //存放路径的字符串数组
    TCHAR szPath[MAX_PATH] = {0,};
    TCHAR *p = NULL;

    //STARTUPINFO结构 该结构用于指定新进程的主窗口特性
    STARTUPINFO si = {0,};

    //该结构返回有关新进程及其主线程的信息
    PROCESS_INFORMATION pi = {0,};

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    switch( fdwReason ) {
    case DLL_PROCESS_ATTACH: 
        //获得当前DLL被装载到的进程的可执行文件的路径到szPath中
        if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
            break;

        //从右面开始查找"\\"
        if( !(p = _tcsrchr(szPath, '\\')) )
            break;

        //比较程序的名称是否为notepad.exe
        if( lstrcmpi(p+1, _T("notepad.exe")) )
            break;

        //调用IE访问www.xidian.edu.cn
        wsprintf(szCmd, _T("%s %s"), _T("c:\\Program Files\\Internet Explorer\\iexplore.exe"), _T("http://www.xidian.edu.cn"));

        //创建新进程
        if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                            NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi) )
            break;

        if( pi.hProcess != NULL )
            CloseHandle(pi.hProcess);
        break;
    }
    return TRUE;
}

EjectDll.cpp

_tmain

  • 通过名字找PID[FindProcessID]
  • 更改特权[SetPrivilege]
  • 卸载DLL[EjectDll]

FindProcessID

  • 获得系统进程的快照
  • 获取进程信息为指定的进程等的一个快照[CreateToolhelp32Snapshot]
  • 进程获取[process32First]
  • 继续枚举下个模块结构字段信息[Process32Next]
  • 关闭一个打开的对象句柄[CloseHandle]

SetPrivilege

  • 获取进程的令牌句柄[OpenProcessToken]
  • 函数查看系统权限的特权值,返回信息到一个LUID结构体里[LookupPrivilegeValue]
  • 启用或禁止,指定访问令牌的特权[AdjustTokenPrivileges]
  • 有错误则输出错误信息[GetLastError]

EjectDll

  • 获得加载到notepad进程地址空间的DLL信息[CreateToolhelp32Snapshot]
  • 继续枚举下个模块结构字段信息[Module32First/ Module32First]
  • 打开一个已存在的进程对象[OpenProcess]
  • 创建远程线程[GetModuleHandle/GetProcAddress/CreateRemoteThread]
  • 关闭一个打开的对象句柄[CloseHandle]
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// EjectDll.exe

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"


//查找该进程下有没有加载该DLL模块
    //由进程名找到进程id号
DWORD FindProcessID(LPCTSTR szProcessName) {
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    //用来存放快照进程信息的一个结构体
    PROCESSENTRY32 pe;

    // 获得系统进程的快照
    pe.dwSize = sizeof( PROCESSENTRY32 );

    //CreateToolhelp32Snapshot->可以通过获取进程信息为指定的进程、进程使用的堆[HEAP]、模块[MODULE]、线程建立一个快照
    hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

    //process32First->是一个进程获取函数
    //当利用CreateToolhelp32Snapshot()获得当前运行进程的快照后,可以利用process32First函数来获得第一个进程的句柄
    Process32First(hSnapShot, &pe);
    do {
        if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile)) {
            dwPID = pe.th32ProcessID;
            //如果已经加载,则退出
            break;
        }
    } while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);
    return dwPID;
}

//设置程序权限(同InjectDll.cpp)
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
           TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) )
        return FALSE;

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
        return FALSE; 

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, FALSE, &tp, 
         sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL) )
        return FALSE; 

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
        return FALSE;

    return TRUE;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName) {
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad进程的id号
    // 使用TH32CS_SNAPMODULE参数,获得加载到notepad进程地址空间的DLL信息
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for( ; bMore ; bMore = Module32Next(hSnapshot, &me) ){
        if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || 
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName) ){
            bFound = TRUE;
            break;
        }
    }

    if( !bFound ){
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
        return FALSE;

    hModule = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, 
                                 pThreadProc, me.modBaseAddr, 
                                 0, NULL);
    WaitForSingleObject(hThread, INFINITE); 

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

int _tmain(int argc, TCHAR* argv[]) {
    DWORD dwPID = 0xFFFFFFFF;

    dwPID = FindProcessID(L"notepad.exe");
    if( dwPID == 0xFFFFFFFF ) //没有找到notepad进程
        return 1;

    // 更改特权
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // 卸载DLL
    if( EjectDll(dwPID, L"MyDll.dll") )
        _tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, L"MyDll.dll");
    else
        _tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, L"MyDll.dll");

    return 0;
}

TestHook.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "stdio.h"
#include "conio.h"
#include "windows.h"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

void main()
{
    HMODULE         hDll = NULL;
    PFN_HOOKSTART   HookStart = NULL;
    PFN_HOOKSTOP    HookStop = NULL;

    hDll = LoadLibraryA("HookDll.dll"); // 装载HookDll.dll
    if( hDll == NULL )
        return;

    // 获取导出函数HookStart()和HookStop()的地址
    HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, "HookStart");
    HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, "HookStop");

    HookStart(); //开始钩取键盘消息

    // 等到用户输入'q'才终止钩取
    printf("press 'q' to quit!\n");
    while( _getch() != 'q' )    ;

    HookStop(); //终止钩取键盘消息

    FreeLibrary(hDll); //卸载HookDll.dll
}
updatedupdated2020-05-252020-05-25