Files
endgame-exploit/shellcode.asm
2024-02-19 14:54:34 -05:00

334 lines
11 KiB
NASM

;
; 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