shellcode编写探究

VSole2022-06-09 15:34:57

前言

shellcode是不依赖环境,放到任何地方都可以执行的机器码。shellcode的应用场景很多,本文不研究shellcode的具体应用,而只是研究编写一个shellcode需要掌握哪些知识。

ShellCode编写原则

1、不能有全局变量

因为我们编写shellcode时,使用的全局变量是自己的进程里面的全局变量,注入到别的进程里,这个地址就没用了。

2、不能使用常量字符串

和第一点原因一样,字符串常量值也是全局变量,注入到别的进程里,根本没有这个字符串。

要使用字符串,需要使用字符数组。

char s[] = {'1','2',0};

3、不能直接调用系统函数

调用系统函数的方式是间接调用(FF15),需要从IAT表里获取API地址,每个进程的IAT表位置不同,且对方的进程可能没有导入你需要调用的函数的DLL,那么你是不能调用这个系统函数的。

所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。

但是 LoadLibrary,GetProcAddress 本身就是系统函数,它们本身就依赖IAT表,咋办呢?

解决方案是这样的:通过FS:[0x30] 找到PEB,然后通过PEB里的LDR链表 [PEB+0x0C]找到 kernel32.dll 的地址,然后我们遍历它的 IAT表,找到 LoadLibrary 和 GetProcAddress 函数。

4、不能嵌套调用其他函数

和前两点道理是一样的,本进程里的函数地址,拿到别的进程的虚拟地址空间是无效的。

TEB/PEB

每个线程都有一个TEB结构来存储线程的一些属性结构,TEB的地址用fs:[0]来获取

在0x30这个地址有一个指针指向PEB结构,PEB就是进程用来记录自己信息的一个结构

完整结构如下

在PEB的0x00c偏移有一个 Ldr _PEB_LDR_DATA结构跟进去

可以得到3个结构如下所示

InLoadOrderModuleList:模块加载的顺序
InMemoryOrderModuleList:模块在内存的顺序
InInitializationOrderModuleList:模块初始化的顺序

思路

我们一般使用api会直接使用LoadLibraryGetProcessAddress,但是这里肯定会依赖IAT表,所以这里我们就需要自己实现api所完成的功能

TEB -> PEB -> PEB + 0x0C -> Ldr _PEB_LDR_DATA -> InLoadOrderModuleList -> kernel32.dll -> 导出表定位GetProcessAddress -> 通过找到的GetProcessAddress实现LoadLibrary

实现过程

首先我们自己定义几个结构体,因为我们不依赖系统自己实现

typedef struct _UNICODE_STRING {
 USHORT Length;
 USHORT MaximumLength;
 PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
 DWORD Length;
 bool Initialized;
 PVOID SsHandle; 
 LIST_ENTRY InLoadOrderModuleList;
 LIST_ENTRY InMemoryOrderModuleList;
 LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
 LIST_ENTRY InLoadOrderLinks;
 LIST_ENTRY InMemoryOrderLinks;
 LIST_ENTRY InInitializationOrderLinks;
 PVOID DllBase;
 PVOID EntryPoint;
 UINT32 SizeOfImage;
 UNICODE_STRING FullDllName;
 UNICODE_STRING BaseDllName;
 UINT32 Flags;
 USHORT LoadCount;
 USHORT TlsIndex;
 LIST_ENTRY HashLinks;
 PVOID SectionPointer;
 UINT32 CheckSum;
 UINT32 TimeDateStamp;
 PVOID LoadedImports;
 PVOID EntryPointActivationContext;
 PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);

然后定义shellcode,这里因为kernel32.dll是unicode字符串所以用两字节存储

 char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
 char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
 char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
 char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
 char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
 char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};

找到InLoadOrderModuleList存入寄存器

 __asm
 {
  mov eax,fs:[0x30] // PEB
  mov eax,[eax+0x0C] // PEB->LDR
  add eax,0x0C // LDR->InLoadOrderModuleList
  mov pBeg,eax
  mov eax,[eax]
  mov pPLD,eax
 }

找到kernel32.dll,通过遍历的方式来寻找,通过LDR指向DllBase获取基址

 // Find Kerner32.dll
 while (pPLD != pBeg)
 {
  pLast = (WORD*)pPLD->BaseDllName.Buffer;
  pFirst = (WORD*)szKernel32;
  while (*pFirst && *pLast == *pFirst)
   pFirst++,pLast++;
  if (*pFirst == *pLast)
  {
   dwKernelBase = (DWORD)pPLD->DllBase;
   break;
  }
  pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
 }

然后通过指针定位到导出表

  // 通过指针定位到导出表
  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
  PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
  PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
  PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
  PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
  PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);
  // 导出函数地址表RVA
  DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions);
  // 导出函数名称表RVA
  WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals);
  // 导出函数序号表RVA
  DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);

还是通过遍历找到GetProcessAddress,用指针指向这个地址

  DWORD dwCnt = 0;
  char* pFinded = NULL, *pSrc = szGetProcAddress;
  for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
  {
   pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
   while (*pFinded && *pFinded == *pSrc)
    pFinded++, pSrc++;
   if (*pFinded == *pSrc)
   {
    pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
    break;
   }
   pSrc = szGetProcAddress;
  }

然后就可以使用pGetProcessAddress实现LoadLibraryMessageBox

 // 通过pGetProcAddress进行调用
 pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
 pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
 pMessageBox(NULL,szHelloShellCode,0,MB_OK);

完整代码如下

// shellcode.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
typedef struct _UNICODE_STRING {
 USHORT Length;
 USHORT MaximumLength;
 PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
 DWORD Length;
 bool Initialized;
 PVOID SsHandle; 
 LIST_ENTRY InLoadOrderModuleList;
 LIST_ENTRY InMemoryOrderModuleList;
 LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
 LIST_ENTRY InLoadOrderLinks;
 LIST_ENTRY InMemoryOrderLinks;
 LIST_ENTRY InInitializationOrderLinks;
 PVOID DllBase;
 PVOID EntryPoint;
 UINT32 SizeOfImage;
 UNICODE_STRING FullDllName;
 UNICODE_STRING BaseDllName;
 UINT32 Flags;
 USHORT LoadCount;
 USHORT TlsIndex;
 LIST_ENTRY HashLinks;
 PVOID SectionPointer;
 UINT32 CheckSum;
 UINT32 TimeDateStamp;
 PVOID LoadedImports;
 PVOID EntryPointActivationContext;
 PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);
DWORD WINAPI ShellCode();
int main(int argc, char* argv[])
{
 ShellCode();
 getchar();
 return 0;
}
DWORD WINAPI ShellCode()
{
 PGETPROCADDRESS pGetProcAddress = NULL;
 PLOADLIBRARY pLoadLibrary = NULL;
 PMESSAGEBOX  pMessageBox = NULL;
 PLDR_DATA_TABLE_ENTRY pPLD;
 PLDR_DATA_TABLE_ENTRY pBeg;
 WORD *pFirst = NULL;
 WORD *pLast = NULL;
 DWORD ret = 0, i = 0;
 DWORD dwKernelBase = 0;
 char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
 char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
 char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
 char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
 char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
 char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};
 __asm
 {
  mov eax,fs:[0x30] // PEB
  mov eax,[eax+0x0C] // PEB->LDR
  add eax,0x0C // LDR->InLoadOrderModuleList
  mov pBeg,eax
  mov eax,[eax]
  mov pPLD,eax
 }
 // Find Kerner32.dll
 while (pPLD != pBeg)
 {
  pLast = (WORD*)pPLD->BaseDllName.Buffer;
  pFirst = (WORD*)szKernel32;
  while (*pFirst && *pLast == *pFirst)
   pFirst++,pLast++;
  if (*pFirst == *pLast)
  {
   dwKernelBase = (DWORD)pPLD->DllBase;
   break;
  }
  pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
 }
 // Kernel32.dll -> GetProcAddress
 if (dwKernelBase != 0)
 {
  // 通过指针定位到导出表
  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
  PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
  PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
  PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
  PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
  PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);
  // 导出函数地址表RVA
  DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions);
  // 导出函数名称表RVA
  WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals);
  // 导出函数序号表RVA
  DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);
  DWORD dwCnt = 0;
  char* pFinded = NULL, *pSrc = szGetProcAddress;
  for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
  {
   pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
   while (*pFinded && *pFinded == *pSrc)
    pFinded++, pSrc++;
   if (*pFinded == *pSrc)
   {
    pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
    break;
   }
   pSrc = szGetProcAddress;
  }
 }
 // 通过pGetProcAddress进行调用
 pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
 pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
 pMessageBox(NULL,szHelloShellCode,0,MB_OK);
 
 return 0;
}

成功弹窗

这里我们进反汇编看一下,是有检测堆栈平衡的代码的

在物理机里面查看也是有的

这里关闭一下堆栈平衡的检测,默认情况如下

修改为禁用安全检查

即可生成没有检查堆栈平衡的代码

typedefchar函数
本作品采用《CC 协议》,转载必须注明作者和本文链接
本文示例是来自corCTF 2021中 的两个内核题,由 BitsByWill 和 D3v17 所出。针对UAF漏洞,漏洞对象从kmalloc-64到kmalloc-4096,都能利用 msg_msg 结构实现任意写。
看雪论坛作者ID:NYSECbao
进而如何配合userfaultfd实现对于当前进程的task_struct,以及cred的进攻利用,实现权限提升> 的这样一种技术。"钩子",这些钩子是数据包穿越该协议栈过程中的明确定义的hook point。
如何调包Win32API函数?其实就是HookPE文件自己的IAT表。
lib文件在windows下有两种形式出现,第一种就是普通的静态库,第二种是作为dll的导入库。接下来我来分享一下如何在这两种lib文件中注入后门代码,使程序编译后生成的exe在运行时候自动。执行我们后门,并且不影响正常lib的功能。lib实际上就是一堆Obj文件打包在了一起,当然还有一些额外的信息,这个之后再说。
MRCTF2022 stuuuuub 题解
2023-02-07 10:15:04
Overview学了这么一段时间的Android,难得见到的一道比较对口的逆向题。e.c()通过执行which su命令后读取输出来检查是否有su文件。读取res.dat文件后调用了decodeSo函数进行解密存放在应用的数据目录下的libnative.so,而decodeSo是libstub.so里的native函数。但是在libstub.so里却没有直接找到decodeSo函数,因此应该是JNI_OnLoad里动态注册的。decode String另外libstub.so使用了Ollvm的字符串加密和控制流平坦化。这里参考官方给的WP中使用了AndroidNativeEmu框架。
巧解一道CTF Android题
2022-08-10 16:15:40
无须还原代码,穷举爆破。我们打开jeb工具,定位到当前activity。我们看一下saveSN方法,可以看到这是一个native方法。我们解包一下apk,获取到so文件。下面进入ida分析。导出函数并没有相关java的native方法,说明是动态注册。我们看下JNI_ONLOAD函数:jint JNI_OnLoad{ if ( !
SAMPLE服务器的行为及其漏洞取决于一段时间内交换的一系列消息,这些消息决定了服务器的状态。客户端发送的消息序列以红色突出显示。AFLNET读取响应报文并提取协议指定的状态码,确定当前的执行状态。所出现了新的状态序列哈希值则认为当前的测试用例是Interesting的。
依赖于特定硬件环境的固件无法完整模拟,需要hook掉其中依赖于硬件的函数。LD_PRELOAD的劫持对于特定函数的劫持技术分为动态注入劫持和静态注入劫持两种。网上针对LD_PRELOAD的劫持也有大量的描述
结果分析Hook前Hook后,我们的弹窗本该是hello的但是hook后,程序流程被我们修改了。760D34B2 55 push ebp760D34B3 8BEC mov ebp,esp通过这两条指令,函数就可以在堆栈中为局部变量分配存储空间,并在函数执行过程中保存和恢复现场。这样做的好处是可以避免局部变量和其他函数之间的冲突,同时也可以提高函数的可读性和可维护性。
VSole
网络安全专家