; ; compile: nasm shellcode.asm ; ; NOTE: something to keep in mind when reading/editing this file is that we ; are executing this page out of write-combining memory. we regularly use ; sfence/wbinvd to try and flush things out and keep everything happy. ; ; otherwise, if you are not careful, there can be.. strange side effects. ; BITS 32 ; ; setup EBP to point at the base of this shellcode payload. this will allow ; us to easily make relative references to shellcode labels, making this ; payload position independent. ; start: call $+5 pop ebp sub ebp, $-1 ; ; check if we are the first thread to enter the ENDGAME shellcode page, ; if so, take the 'lock' to prevent the possibility of another thread ; coming through should we get preempted. this isn't totally out of the ; question since we sinkholed a page of kernel .text ... ; check_lock: mov eax, 0 mov ebx, 1 lock cmpxchg [ebp+locked], ebx jz repair_pte .inf: hlt jmp .inf ; trap any threads that could have chased us into this page ; ; repair the kernel .text PTE we corrupted to hijack code execution. please ; note that this must reconcile with the address/values in main.py ; repair_pte: mov eax, 0xc0200088 mov dword [eax], 0x22461 invlpg [0x80022000] wbinv ; ; dynamically resolve kernel exports that the shellcode will use ; locate_exports: cld ; clear the direction flag so the string instructions increment the address mov esi, 80010000h ; kernel base address mov eax, [esi+3Ch] ; value of e_lfanew (File address of new exe header) mov ebx, [esi+eax+78h] ; value of IMAGE_NT_HEADERS32 -> IMAGE_OPTIONAL_HEADER32 -> IMAGE_DATA_DIRECTORY -> ibo32 (Virtual Address) (0x02e0) add ebx, esi mov edx, [ebx+1Ch] ; value of IMAGE_DIRECTORY_ENTRY_EXPORT -> AddressOfFunctions (0x0308) add edx, esi ; address of kernel export table lea edi, [ebp+kexports] ; address of the local kernel export table .get_exports: mov ecx, [edi] ; load the entry from the local table jecxz .done_exports sub ecx, [ebx+10h] ; subtract the IMAGE_DIRECTORY_ENTRY_EXPORT -> Base mov eax, [edx+4*ecx] ; load the export by number from the kernel table test eax, eax jz .empty ; skip if the export is empty add eax, esi ; add kernel base address to the export to construct a valid pointer .empty: stosd ; save the value back to the local table and increment EDI by 4 jmp .get_exports .done_exports: ; ; find XAPI's CopyFile() - "55 [8D 6C 24 A0 81 EC 9C 00] ..." ; find_copy_file: mov ecx, 0x30000 ; approximate memory address in the dash to start searching forward from mov edx, 0xA0000 ; approximate memory address in the dash to stop searching .check_pattern: inc ecx ; increment the search pointer cmp ecx, edx ; compare current address with end address jge .error ; if past the end of the search space, end the search mov eax, [ecx] ; move 4 bytes from the current address (dash code) into EAX cmp eax, 0xa0246c8d ; compare with the first part of the egg (reversed due to endianness) jnz .check_pattern ; if not equal, continue searching mov eax, [ecx+4] ; move the next 4 bytes from memory into EAX cmp eax, 0x009cec81 ; compare with the second part of the egg (reversed due to endianness) jnz .check_pattern ; if not equal, continue searching dec ecx ; found it! decrement ECX since the pattern is +1 into the func lea edi, [ebp+CopyFileEx] mov [edi], ecx jmp .done_resolution .error: jmp .error .done_resolution: wbinvd ; ; drop IRQL to PASSIVE because the thread we hijacked may have come in at a ; higher level and this can cause issues when calling kernel exports or XAPI ; lower_irql: mov ecx, 0 call dword [ebp+KfLowerIrql] %ifdef DEBUG ; ; locate the of address our 'helper' allocation in memory. this is purely ; used for debug / testing of ENDGAME -- it helped provide some introspection ; on where our stuff was getting mapped on retail hardware. ; find_helper: mov ebx, 0xF1000000 ; where to start searching memory .loop: add ebx, 0x1000 ; increment to the next page push ebx call [ebp+MmIsAddressValid] ; check if the address is safe to dereference jz .loop cmp dword [ebx], 0x71615141 ; does this page start with our magic marker? jnz .loop dbg_print: ; ; sprintf(...) ; sub esp, 0x100 ; make a 256 byte buffer on the stack mov ecx, esp push ebx ; arg1 for format string lea eax, [ebp+fmt_str] push eax ; format string push ecx ; buffer sfence call [ebp+sprintf] add esp, 0x0C ; ; OutputDebugString(...) ; push esp ; Buffer push 0 mov word [esp+0], ax ; Length mov word [esp+2], 0x100 ; MaxLength sfence mov ecx, esp mov eax, 1 int 0x2D ; debug print to superio int3 ; do not remove (required for proper int 2Dh handling) add esp, 0x108 ; cleanup buffer (0x100) + debug print structure (0x8) %endif ; ; loop through each memory card drive letter and attempt to copy payload.xbe ; from the MU to E:\payload.xbe. Once a copy succeeds, break from the loop. ; copy_file: cmp byte [ebp+mu_path], 'N' ; have we made it through all the memory card slots? je copy_failure xor eax, eax push eax ; - dwCopyFlags push eax ; - pbCancel push eax ; - lpData push eax ; - lpProgressRoutine lea eax, [ebp+hdd_path] push eax ; - lpNewFileName lea eax, [ebp+mu_path] push eax ; - lpExistingFilename call dword [ebp+CopyFileEx] ; CopyFileEx(MU_X, HDD, NULL, NULL, NULL, NULL); inc byte [ebp+mu_path] ; increment drive letter to try copying from the next MU wbinvd test eax, eax ; if CopyFileEx(...) did not indicate it copied a file, keep looping jz copy_file copy_success: mov ecx, 0D7h ; red-orange-green (success) lea eax, [ebp+blink_led] call eax jmp make_habibi copy_failure: mov ecx, 0A0h ; red blinking (failure) call blink_led .inf: jmp short .inf ; ; modify the RSA key data to make it habibi compatible ; make_habibi: mov ebx, [ebp+XePublicKeyData] or ebx, 0xF0000000 pushf cli ; disable interrupts xor dword [ebx+110h], 2DD78BD6h ; alter the last 4 bytes of the public key mov ecx, cr3 ; invalidate TLB mov cr3, ecx popf ; re-enable interrupts ; ; cribbed from past softmods, roughly a re-creation of the following: ; - https://github.com/XboxDev/OpenXDK/blob/master/src/hal/xbox.c#L36 ; launch_xbe: mov esi, [ebp+LaunchDataPage] ; https://xboxdevwiki.net/Kernel/LaunchDataPage mov ebx, [esi] mov edi, 1000h test ebx, ebx ; check the LaunchDataPage pointer jnz .mem_ok ; jump if it's not NULL push edi call dword [ebp+MmAllocateContiguousMemory] ; otherwise, allocate a memory page mov ebx, eax ; and store the pointer to the allocated page in EBX mov [esi], eax ; store the pointer back to the kernel as well .mem_ok: push byte 1 push edi push ebx call dword [ebp+MmPersistContiguousMemory] mov edi,ebx xor eax,eax mov ecx,400h rep stosd ; fill the whole LaunchDataPage memory page (4096 Bytes) with zeros or dword [ebx], byte -1 ; set LaunchDataPage.launch_data_type to 0xFFFFFFFF mov [ebx+4], eax ; set LaunchDataPage.title_id to 0 lea edi, [ebx+8] ; copy the address of LaunchDataPage.launch_path string lea esi, [ebp+xbe_str] push byte xbe_strlen pop ecx rep movsb ; copy the executable path to the LaunchDataPage.launch_path push byte 2 ; 2 stands for ReturnFirmwareQuickReboot sfence wbinvd ; flush the CPU caches to ensure all our writes are in main memory call dword [ebp+HalReturnToFirmware] .inf: jmp short .inf ; ; blink LED to demonstrate code execution ; blink_led: push ecx push byte 0 push byte 8 push byte 20h call [ebp+HalWriteSMBusValue] ; set LED pattern push byte 1 push byte 0 push byte 7 push byte 20h call [ebp+HalWriteSMBusValue] ; enable LED override ret ; ; '.data' section for our shellcode ; kexports: HalReturnToFirmware dd 49 HalWriteSMBusValue dd 50 KfLowerIrql dd 161 LaunchDataPage dd 164 MmAllocateContiguousMemory dd 165 MmPersistContiguousMemory dd 178 XePublicKeyData dd 355 ; ENDGAME debug helpers %ifdef DEBUG MmIsAddressValid dd 174 sprintf dd 362 %endif ; end of local export table dd 0 xapi: CopyFileEx dd 0 strings: mu_path db 'F:\payload.xbe', 0 ; first memory card slot hdd_path db 'C:\payload.xbe', 0 ; E drive (as aliased in dash) xbe_str db '\Device\Harddisk0\Partition1\payload.xbe', 0 xbe_strlen equ $-xbe_str %ifdef DEBUG fmt_str db 'Spray base 0x%08X', 0x0a, 0x0d, 0 %endif misc: locked dd 0 version dd 0x00010000 ; [-unused-].[major].[minor].[patch] ; ; no purpose but to serve as a static marker for the end of our shellcode ; end: dd 0xCCCCCCCC