├── ExampleUsage.c ├── README.md ├── Shellcode.asm └── djb2.py /ExampleUsage.c: -------------------------------------------------------------------------------- 1 | // Driver example using the shellcode to resolve function addresses. 2 | #include 3 | #pragma warning(disable:4152) // Allow nonstandard function/data pointer conversions 4 | 5 | typedef DWORD64 QWORD; 6 | 7 | NTSTATUS DriverUnload(PDRIVER_OBJECT DriverObject) 8 | { 9 | UNREFERENCED_PARAMETER(DriverObject); 10 | return(STATUS_SUCCESS); 11 | } 12 | 13 | unsigned char shellcode[] = { 14 | 0x48, 0x83, 0xEC, 0x20, 0x57, 0x56, 0x55, 0x53, 0x49, 0x89, 0xC9, 0x65, 15 | 0x48, 0x8B, 0x14, 0x25, 0x38, 0x00, 0x00, 0x00, 0x8B, 0x4A, 0x08, 0x48, 16 | 0xC1, 0xE1, 0x10, 0x66, 0x03, 0x4A, 0x06, 0x48, 0xC1, 0xE1, 0x10, 0x66, 17 | 0x03, 0x0A, 0x48, 0x89, 0xCA, 0x48, 0xC1, 0xEA, 0x0C, 0x48, 0xC1, 0xE2, 18 | 0x0C, 0x48, 0x81, 0xEA, 0x00, 0x10, 0x00, 0x00, 0x48, 0x8B, 0x32, 0x66, 19 | 0x81, 0xFE, 0x4D, 0x5A, 0x75, 0xEF, 0x48, 0x89, 0xD1, 0x8B, 0x5A, 0x3C, 20 | 0x48, 0x01, 0xD9, 0x8B, 0x99, 0x88, 0x00, 0x00, 0x00, 0x48, 0x01, 0xD3, 21 | 0x8B, 0x7B, 0x20, 0x48, 0x01, 0xD7, 0x31, 0xED, 0x8B, 0x34, 0xAF, 0x48, 22 | 0x01, 0xD6, 0xFF, 0xC5, 0xB9, 0x05, 0x15, 0x00, 0x00, 0x31, 0xC0, 0xAC, 23 | 0x41, 0x89, 0xC0, 0x41, 0x83, 0xF8, 0x00, 0x74, 0x0E, 0x89, 0xC8, 0xC1, 24 | 0xE0, 0x05, 0x01, 0xC8, 0x44, 0x01, 0xC0, 0x89, 0xC1, 0xEB, 0xE6, 0x4C, 25 | 0x39, 0xC9, 0x75, 0xD4, 0x8B, 0x7B, 0x24, 0x48, 0x01, 0xD7, 0x66, 0x8B, 26 | 0x2C, 0x6F, 0x8B, 0x7B, 0x1C, 0x48, 0x01, 0xD7, 0x8B, 0x7C, 0xAF, 0xFC, 27 | 0x48, 0x01, 0xD7, 0x48, 0x89, 0xF8, 0x5B, 0x5D, 0x5E, 0x5F, 0x48, 0x83, 28 | 0xC4, 0x20, 0xC3 29 | }; 30 | 31 | 32 | 33 | NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) 34 | { 35 | PVOID pPool; 36 | UNREFERENCED_PARAMETER(RegistryPath); 37 | 38 | pPool = ExAllocatePool(NonPagedPool, sizeof(shellcode)); 39 | memmove(pPool, &shellcode, sizeof(shellcode)); 40 | 41 | QWORD(*PoolFunction)(DWORD) = pPool; 42 | DbgPrint("Return Value: %I64X\r\n", PoolFunction(0x86158fb1)); // IoCreateFile 43 | DbgPrint("Return Value: %I64X\r\n", PoolFunction(0x54a5a00e)); // PsTerminateSystemThread 44 | DbgPrint("Return Value: %I64X\r\n", PoolFunction(0xba869939)); // PsCreateSystemThread 45 | ExFreePool(pPool); 46 | 47 | DriverObject->DriverUnload = DriverUnload; 48 | return(STATUS_SUCCESS); 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicKernelShellcode 2 | An example of how x64 kernel shellcode can dynamically find and use kernel APIs (exported from ntoskrnl).
Tested on Windows 10 x64 (1903) 3 | The shellcode is capable of returning function addresses from ntoskrnl. For more practical use, it can easily be modified to call these functions. I used [FASM](https://flatassembler.net/) as the assembler, but there is no special syntax so others should work. The Python file included is capable of generating the hashes needed. 4 | 5 | ## Useful resources 6 | - [DoublePulsar Shellcode Information](https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html) 7 | - [Super useful information about IDT](https://ired.team/miscellaneous-reversing-forensics/windows-kernel/interrupt-descriptor-table-idt) 8 | - [PE Format Image](https://en.wikipedia.org/wiki/Portable_Executable#/media/File:Portable_Executable_32_bit_Structure_in_SVG_fixed.svg) 9 | - [Great In Depth Information on PE Format](https://github.com/corkami/docs/blob/master/PE/PE.md) 10 | - [djb2 algorithm](http://www.cse.yorku.ca/~oz/hash.html) 11 | -------------------------------------------------------------------------------- /Shellcode.asm: -------------------------------------------------------------------------------- 1 | Format Binary 2 | use64 3 | 4 | 5 | start: 6 | sub rsp, 0x20 7 | push rdi ; Preserve non volatile registers 8 | push rsi 9 | push rbp 10 | push rbx 11 | mov r9, rcx ; Move function hash into a volatile register 12 | mov rdx, [gs:0x38] ; IDT (interrupt dispatch table) from KPCR (kernel processor control region) 13 | mov ecx, [rdx + 8] ; _KIDTENTRY64.OffsetHigh (Using the first routine in the table) 14 | shl rcx, 0x10 15 | add cx, WORD [rdx + 6] ; _KIDTENTRY64.OffsetMiddle 16 | shl rcx, 0x10 17 | add cx, [rdx] ; _KIDENTRY64.OffsetLow 18 | mov rdx, rcx 19 | 20 | shr rdx, 0xC 21 | shl rdx, 0xC ; Shifts remove the specifics of the address 22 | @@: 23 | sub rdx, 0x1000 ; 0x1000 = PAGE_SIZE 24 | mov rsi, [rdx] 25 | cmp si, 0x5A4D ; Look for "MZ" string of the DOS header 26 | 27 | jne @b 28 | mov rcx, rdx ; Base address of ntoskrnl.exe 29 | mov ebx, [rdx + 0x3C] ; Get the PE header, add instruction converts from RVA to a VA 30 | add rcx, rbx 31 | mov ebx, [rcx + 0x88] ; Get the export directory, add instruction converts from RVA to a VA 32 | add rbx, rdx 33 | mov edi, [rbx + 0x20] ; In the export directory (IMAGE_EXPORT_DIRECTORY), get the AddressOfNames member 34 | add rdi, rdx 35 | xor ebp, ebp 36 | 37 | nextAPIName: 38 | mov esi, [rdi + rbp * 4] ; Cycle through each pointer, add converts RVA to VA 39 | add rsi, rdx 40 | inc ebp 41 | mov ecx, 5381d ; Convert string to hash (djb2 algorithm) 42 | djb2: 43 | xor eax, eax 44 | lodsb 45 | mov r8d, eax 46 | cmp r8d, 0 ; End of string? 47 | je @f 48 | mov eax, ecx 49 | shl eax, 5 50 | add eax, ecx 51 | add eax, r8d 52 | mov ecx, eax 53 | jmp djb2 54 | @@: 55 | cmp rcx, r9 ; Hash of string to find (first argument) 56 | jne nextAPIName 57 | 58 | mov edi, [rbx + 0x24] ; In the export directory get the AddressOfNameOrdinals member 59 | add rdi, rdx 60 | mov bp, [rdi + rbp * 2] ; This gets the name ordinal in the AddressOfNameOrdinals where our function is located (*2 for WORD values) 61 | mov edi, [rbx + 0x1C] ; In the export directory get the AddressOfFunctions member 62 | add rdi, rdx 63 | mov edi, [rdi + rbp * 4 - 4] ; This gets the function (DWORD RVA) based on the name ordinal 64 | add rdi, rdx ; Finally adding the base address, gets us the address of the function 65 | mov rax, rdi ; Actually address of function 66 | pop rbx 67 | pop rbp 68 | pop rsi 69 | pop rdi 70 | add rsp, 0x20 71 | ret 72 | -------------------------------------------------------------------------------- /djb2.py: -------------------------------------------------------------------------------- 1 | # From http://www.cse.yorku.ca/~oz/hash.html 2 | 3 | import sys 4 | 5 | def djb2Hash(string): 6 | hash = 5381 7 | for char in string: 8 | hash = (( hash << 5) + hash) + ord(char) 9 | return(hash & 0xFFFFFFFF) # limit to DWORD size 10 | 11 | # main 12 | if (len(sys.argv) == 2): 13 | print(hex(djb2Hash(sys.argv[1]))) 14 | else: 15 | print("Example: djb2.py CreateFileA") 16 | --------------------------------------------------------------------------------