Skip to content

Commit 1f0020a

Browse files
committed
Land rapid7#2946, @jlee-r7's optimization of the x86 block_api code
2 parents a67a14f + b226ecf commit 1f0020a

File tree

14 files changed

+438
-541
lines changed

14 files changed

+438
-541
lines changed
Lines changed: 125 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,128 @@
1-
#=============================================================================#
2-
# A simple python build script to build the singles/stages/stagers and
3-
# some usefull information such as offsets and a hex dump. The binary output
4-
# will be placed in the bin directory. A hex string and usefull comments will
5-
# be printed to screen.
6-
#
7-
# Example:
8-
# >python build.py stager_reverse_tcp_nx
9-
#
10-
# Example, to build everything:
11-
# >python build.py all > build_output.txt
12-
#
13-
# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
14-
#=============================================================================#
15-
import os, sys, time
16-
from subprocess import Popen
17-
from struct import pack
18-
#=============================================================================#
19-
def clean( dir="./bin/" ):
20-
for root, dirs, files in os.walk( dir ):
21-
for name in files:
22-
os.remove( os.path.join( root, name ) )
23-
#=============================================================================#
24-
def locate( src_file, dir="./src/" ):
25-
for root, dirs, files in os.walk( dir ):
26-
for name in files:
27-
if src_file == name:
28-
return root
29-
return None
30-
#=============================================================================#
31-
def build( name ):
32-
location = locate( "%s.asm" % name )
33-
if location:
34-
input = os.path.normpath( os.path.join( location, name ) )
35-
output = os.path.normpath( os.path.join( "./bin/", name ) )
36-
p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] )
37-
p.wait()
38-
xmit( name )
39-
else:
40-
print "[-] Unable to locate '%s.asm' in the src directory" % name
41-
#=============================================================================#
42-
def xmit_dump_ruby( data, length=16 ):
43-
dump = ""
44-
for i in xrange( 0, len( data ), length ):
45-
bytes = data[ i : i+length ]
46-
hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) )
47-
if i+length <= len(data):
48-
hex += " +"
49-
dump += "%s\n" % ( hex )
50-
print dump
51-
#=============================================================================#
52-
def xmit_offset( data, name, value ):
53-
offset = data.find( value );
54-
if offset != -1:
55-
print "# %s Offset: %d" % ( name, offset )
56-
#=============================================================================#
57-
def xmit( name, dump_ruby=True ):
58-
bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) )
59-
f = open( bin, 'rb')
60-
data = f.read()
61-
print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) )
62-
xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444
63-
xmit_offset( data, "LEPort", pack( "<H", 4444 ) ) # 4444
64-
xmit_offset( data, "Host", pack( ">L", 0x7F000001 ) ) # 127.0.0.1
65-
xmit_offset( data, "IPv6Host", pack( "<Q", 0xBBBBBBBBBBBBBBB1 ) ) # An IPv6 Address
66-
xmit_offset( data, "IPv6ScopeId", pack( "<L", 0xAAAAAAA1 ) ) # An IPv6 Scope ID
67-
xmit_offset( data, "HostName", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\x00" ) # hostname filler
68-
xmit_offset( data, "RetryCounter", "\x6a\x05" ) # socket retry
69-
xmit_offset( data, "CodeLen", pack( "<L", 0x12345678 ) ) # Filler
70-
xmit_offset( data, "Hostname", "https" )
71-
xmit_offset( data, "ExitFunk", pack( "<L", 0x0A2A1DE0 ) ) # kernel32.dll!ExitThread
72-
xmit_offset( data, "ExitFunk", pack( "<L", 0x56A2B5F0 ) ) # kernel32.dll!ExitProcess
73-
xmit_offset( data, "ExitFunk", pack( "<L", 0xEA320EFE ) ) # kernel32.dll!SetUnhandledExceptionFilter
74-
xmit_offset( data, "ExitFunk", pack( "<L", 0xE035F044 ) ) # kernel32.dll!Sleep
75-
xmit_offset( data, "EggTag1", pack( "<L", 0xDEADDEAD ) ) # Egg tag 1
76-
xmit_offset( data, "EggTag2", pack( "<L", 0xC0DEC0DE ) ) # Egg tag 2
77-
xmit_offset( data, "EggTagSize", pack( ">H", 0x1122 ) ) # Egg tag size
78-
xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key
79-
xmit_offset( data, "XORKey", "XORK") # XOR key
80-
if( name.find( "egghunter" ) >= 0 ):
81-
null_count = data.count( "\x00" )
82-
if( null_count > 0 ):
83-
print "# Note: %d NULL bytes found." % ( null_count )
84-
if dump_ruby:
85-
xmit_dump_ruby( data )
861
#=============================================================================#
87-
def main( argv=None ):
88-
if not argv:
89-
argv = sys.argv
90-
try:
91-
if len( argv ) == 1:
92-
print "Usage: build.py [clean|all|<name>]"
93-
else:
94-
print "# Built on %s\n" % ( time.asctime( time.localtime() ) )
95-
if argv[1] == "clean":
96-
clean()
97-
elif argv[1] == "all":
98-
for root, dirs, files in os.walk( "./src/egghunter/" ):
99-
for name in files:
100-
build( name[:-4] )
101-
for root, dirs, files in os.walk( "./src/migrate/" ):
102-
for name in files:
103-
build( name[:-4] )
104-
for root, dirs, files in os.walk( "./src/single/" ):
105-
for name in files:
106-
build( name[:-4] )
107-
for root, dirs, files in os.walk( "./src/stage/" ):
108-
for name in files:
109-
build( name[:-4] )
110-
for root, dirs, files in os.walk( "./src/stager/" ):
111-
for name in files:
112-
build( name[:-4] )
113-
for root, dirs, files in os.walk( "./src/kernel/" ):
114-
for name in files:
115-
build( name[:-4] )
116-
else:
117-
build( argv[1] )
118-
except Exception, e:
119-
print "[-] ", e
120-
#=============================================================================#
121-
if __name__ == "__main__":
122-
main()
2+
# A simple python build script to build the singles/stages/stagers and
3+
# some usefull information such as offsets and a hex dump. The binary output
4+
# will be placed in the bin directory. A hex string and usefull comments will
5+
# be printed to screen.
6+
#
7+
# Example:
8+
# >python build.py stager_reverse_tcp_nx
9+
#
10+
# Example, to build everything:
11+
# >python build.py all > build_output.txt
12+
#
13+
# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
12314
#=============================================================================#
15+
import os, sys, time
16+
from subprocess import Popen
17+
from struct import pack
18+
#=============================================================================#
19+
def clean( dir="./bin/" ):
20+
for root, dirs, files in os.walk( dir ):
21+
for name in files:
22+
os.remove( os.path.join( root, name ) )
23+
#=============================================================================#
24+
def locate( src_file, dir="./src/" ):
25+
for root, dirs, files in os.walk( dir ):
26+
for name in files:
27+
if src_file == name:
28+
return root
29+
return None
30+
31+
#=============================================================================#
32+
def build( name ):
33+
location = locate( "%s.asm" % name )
34+
if location:
35+
input = os.path.normpath( os.path.join( location, name ) )
36+
output = os.path.normpath( os.path.join( "./bin/", name ) )
37+
p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] )
38+
p.wait()
39+
xmit( name )
40+
else:
41+
print "[-] Unable to locate '%s.asm' in the src directory" % name
12442

43+
#=============================================================================#
44+
def xmit_dump_ruby( data, length=16 ):
45+
dump = ""
46+
for i in xrange( 0, len( data ), length ):
47+
bytes = data[ i : i+length ]
48+
hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) )
49+
if i+length <= len(data):
50+
hex += " +"
51+
dump += "%s\n" % ( hex )
52+
print dump
53+
54+
#=============================================================================#
55+
def xmit_offset( data, name, value, match_offset=0 ):
56+
offset = data.find( value );
57+
if offset != -1:
58+
print "# %s Offset: %d" % ( name, offset + match_offset )
59+
60+
#=============================================================================#
61+
def xmit( name, dump_ruby=True ):
62+
bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) )
63+
f = open( bin, 'rb')
64+
data = f.read()
65+
print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) )
66+
xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444
67+
xmit_offset( data, "LEPort", pack( "<H", 4444 ) ) # 4444
68+
xmit_offset( data, "Host", pack( ">L", 0x7F000001 ) ) # 127.0.0.1
69+
xmit_offset( data, "IPv6Host", pack( "<Q", 0xBBBBBBBBBBBBBBB1 ) ) # An IPv6 Address
70+
xmit_offset( data, "IPv6ScopeId", pack( "<L", 0xAAAAAAA1 ) ) # An IPv6 Scope ID
71+
xmit_offset( data, "HostName", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\x00" ) # hostname filler
72+
xmit_offset( data, "RetryCounter", "\x6a\x05", 1 ) # socket retry
73+
xmit_offset( data, "CodeLen", pack( "<L", 0x12345678 ) ) # Filler
74+
xmit_offset( data, "Hostname", "https" )
75+
xmit_offset( data, "ExitFunk", pack( "<L", 0x0A2A1DE0 ) ) # kernel32.dll!ExitThread
76+
xmit_offset( data, "ExitFunk", pack( "<L", 0x56A2B5F0 ) ) # kernel32.dll!ExitProcess
77+
xmit_offset( data, "ExitFunk", pack( "<L", 0xEA320EFE ) ) # kernel32.dll!SetUnhandledExceptionFilter
78+
xmit_offset( data, "ExitFunk", pack( "<L", 0xE035F044 ) ) # kernel32.dll!Sleep
79+
xmit_offset( data, "EggTag1", pack( "<L", 0xDEADDEAD ) ) # Egg tag 1
80+
xmit_offset( data, "EggTag2", pack( "<L", 0xC0DEC0DE ) ) # Egg tag 2
81+
xmit_offset( data, "EggTagSize", pack( ">H", 0x1122 ) ) # Egg tag size
82+
xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key
83+
xmit_offset( data, "XORKey", "XORK") # XOR key
84+
if( name.find( "egghunter" ) >= 0 ):
85+
null_count = data.count( "\x00" )
86+
if( null_count > 0 ):
87+
print "# Note: %d NULL bytes found." % ( null_count )
88+
if dump_ruby:
89+
xmit_dump_ruby( data )
90+
91+
#=============================================================================#
92+
def main( argv=None ):
93+
if not argv:
94+
argv = sys.argv
95+
try:
96+
if len( argv ) == 1:
97+
print "Usage: build.py [clean|all|<name>]"
98+
else:
99+
print "# Built on %s\n" % ( time.asctime( time.localtime() ) )
100+
if argv[1] == "clean":
101+
clean()
102+
elif argv[1] == "all":
103+
for root, dirs, files in os.walk( "./src/egghunter/" ):
104+
for name in files:
105+
build( name[:-4] )
106+
for root, dirs, files in os.walk( "./src/migrate/" ):
107+
for name in files:
108+
build( name[:-4] )
109+
for root, dirs, files in os.walk( "./src/single/" ):
110+
for name in files:
111+
build( name[:-4] )
112+
for root, dirs, files in os.walk( "./src/stage/" ):
113+
for name in files:
114+
build( name[:-4] )
115+
for root, dirs, files in os.walk( "./src/stager/" ):
116+
for name in files:
117+
build( name[:-4] )
118+
for root, dirs, files in os.walk( "./src/kernel/" ):
119+
for name in files:
120+
build( name[:-4] )
121+
else:
122+
build( argv[1] )
123+
except Exception, e:
124+
print "[-] ", e
125+
#=============================================================================#
126+
if __name__ == "__main__":
127+
main()
128+
#=============================================================================#

external/source/shellcode/windows/x86/src/block/block_api.asm

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ api_call:
2323
mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list
2424
next_mod: ;
2525
mov esi, [edx+40] ; Get pointer to modules name (unicode string)
26-
movzx ecx, word [edx+38] ; Set ECX to the length we want to check
26+
movzx ecx, word [edx+38] ; Set ECX to the length we want to check
2727
xor edi, edi ; Clear EDI which will store the hash of the module name
2828
loop_modname: ;
2929
xor eax, eax ; Clear EAX
@@ -34,22 +34,25 @@ loop_modname: ;
3434
not_lowercase: ;
3535
ror edi, 13 ; Rotate right our hash value
3636
add edi, eax ; Add the next byte of the name
37-
loop loop_modname ; Loop untill we have read enough
37+
loop loop_modname ; Loop until we have read enough
38+
3839
; We now have the module hash computed
3940
push edx ; Save the current position in the module list for later
4041
push edi ; Save the current module hash for later
41-
; Proceed to itterate the export address table,
42+
; Proceed to iterate the export address table,
4243
mov edx, [edx+16] ; Get this modules base address
4344
mov eax, [edx+60] ; Get PE header
44-
add eax, edx ; Add the modules base address
45-
mov eax, [eax+120] ; Get export tables RVA
46-
test eax, eax ; Test if no export address table is present
47-
jz get_next_mod1 ; If no EAT present, process the next module
48-
add eax, edx ; Add the modules base address
49-
push eax ; Save the current modules EAT
50-
mov ecx, [eax+24] ; Get the number of function names
51-
mov ebx, [eax+32] ; Get the rva of the function names
45+
46+
; use ecx as our EAT pointer here so we can take advantage of jecxz.
47+
mov ecx, [eax+edx+120] ; Get the EAT from the PE header
48+
jecxz get_next_mod1 ; If no EAT present, process the next module
49+
add ecx, edx ; Add the modules base address
50+
push ecx ; Save the current modules EAT
51+
mov ebx, [ecx+32] ; Get the rva of the function names
5252
add ebx, edx ; Add the modules base address
53+
mov ecx, [ecx+24] ; Get the number of function names
54+
; now ecx returns to its regularly scheduled counter duties
55+
5356
; Computing the module hash + function hash
5457
get_next_func: ;
5558
jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module
@@ -66,14 +69,15 @@ loop_funcname: ;
6669
cmp al, ah ; Compare AL (the next byte from the name) to AH (null)
6770
jne loop_funcname ; If we have not reached the null terminator, continue
6871
add edi, [ebp-8] ; Add the current module hash to the function hash
69-
cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for
72+
cmp edi, [ebp+36] ; Compare the hash to the one we are searching for
7073
jnz get_next_func ; Go compute the next function hash if we have not found it
74+
7175
; If found, fix up stack, call the function and then value else compute the next one...
7276
pop eax ; Restore the current modules EAT
73-
mov ebx, [eax+36] ; Get the ordinal table rva
77+
mov ebx, [eax+36] ; Get the ordinal table rva
7478
add ebx, edx ; Add the modules base address
7579
mov cx, [ebx+2*ecx] ; Get the desired functions ordinal
76-
mov ebx, [eax+28] ; Get the function addresses table rva
80+
mov ebx, [eax+28] ; Get the function addresses table rva
7781
add ebx, edx ; Add the modules base address
7882
mov eax, [ebx+4*ecx] ; Get the desired functions RVA
7983
add eax, edx ; Add the modules base address to get the functions actual VA
@@ -88,10 +92,11 @@ finish:
8892
push ecx ; Push back the correct return value
8993
jmp eax ; Jump into the required function
9094
; We now automagically return to the correct caller...
95+
9196
get_next_mod: ;
9297
pop eax ; Pop off the current (now the previous) modules EAT
9398
get_next_mod1: ;
9499
pop edi ; Pop off the current (now the previous) modules hash
95100
pop edx ; Restore our position in the module list
96101
mov edx, [edx] ; Get the next module
97-
jmp short next_mod ; Process this module
102+
jmp short next_mod ; Process this module

0 commit comments

Comments
 (0)