Created
August 1, 2023 16:38
-
-
Save wving5/f904d9d5698159f8262619765db6dcb5 to your computer and use it in GitHub Desktop.
Practice Frida with Zombotron(U3D)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Color = { | |
RESET: "\x1b[39;49;00m", | |
Black: "\x1b[30;01m", | |
Blue: "\x1b[34;01m", | |
Cyan: "\x1b[36;01m", | |
Gray: "\x1b[37;11m", | |
Green: "\x1b[32;01m", | |
Purple: "\x1b[35;01m", | |
Red: "\x1b[31;01m", | |
Yellow: "\x1b[33;01m", | |
Light: { | |
Black: "\x1b[30;11m", | |
Blue: "\x1b[34;11m", | |
Cyan: "\x1b[36;11m", | |
Gray: "\x1b[37;01m", | |
Green: "\x1b[32;11m", | |
Purple: "\x1b[35;11m", | |
Red: "\x1b[31;11m", | |
Yellow: "\x1b[33;11m", | |
}, | |
}; | |
function patchAction(targetAddress, cw_action) { | |
logger("------------------------------"); | |
logger("*** patchAction: " + targetAddress); | |
const region = Process.findRangeByAddress(targetAddress); // {"base":"0x1698a2000","size":65536,"protection":"rwx"} | |
logger(" get region: " + JSON.stringify(region)); | |
// const ret = Memory.protect(ptr(region.base), region.size, 'rwx'); | |
// logger(` Memory.protect: => ${ret}`) | |
var insOld = Instruction.parse(targetAddress); | |
var insOldNext = Instruction.parse(insOld.next); | |
logger(`\nins Before patch: | |
[${insOld.size}] => ${insOld.toString()} | |
[${insOldNext.size}] => ${insOldNext.toString()} | |
`); | |
logger( | |
"\n" + | |
hexdump(targetAddress, { | |
offset: 0, | |
length: 0xf, | |
header: true, | |
ansi: true, | |
}) | |
); | |
const maxPatchSize = 64; | |
Memory.patchCode(targetAddress, maxPatchSize, function (code) { | |
if (!cw_action) return; | |
const cw = new X86Writer(code, { pc: targetAddress }); | |
cw_action(cw); | |
// if (cw_action()) { | |
// cw.putSubRegImm("eax", 1); // ???: always crash except patch NOP | |
// cw.putNop(); | |
// } else { | |
// cw.putNopPadding(4); | |
// } | |
cw.flush(); | |
}); | |
insOld = Instruction.parse(targetAddress); | |
insOldNext = Instruction.parse(insOld.next); | |
logger_succ(`\nins After patch: | |
[${insOld.size}] => ${insOld.toString()} | |
[${insOldNext.size}] => ${insOldNext.toString()} | |
`); | |
logger( | |
"\n" + | |
hexdump(targetAddress, { | |
offset: 0, | |
length: 0xf, | |
header: true, | |
ansi: true, | |
}) | |
); | |
} | |
function find_matched_ins(library, pattern, silent = false) { | |
const results = Memory.scanSync(library.base, library.size, pattern) ?? []; | |
if (results.length === 0) { | |
return results; | |
} | |
logger("\n------------------------------", silent); | |
logger( | |
`*** Find Ins: ${pattern} | |
from << ${JSON.stringify(library)} | |
`, | |
silent | |
); | |
if (results.length > 1) { | |
logger_err(" scan results > 1", silent); | |
} | |
logger(" scanSync: " + pretty_print_array(results), silent); | |
return results; // {"address":"0x121d2b3f5","size":8} | |
} | |
function get_app_rx_4096_ranges() { | |
// {"base":"0x122e70000","size":65536,"protection":"rwx"} | |
const matchedRanges = Process.enumerateRanges("r-x").filter((range) => { | |
return ( | |
range.base.compare(ptr(0x100000000)) > 0 && | |
range.base.compare(ptr(0x200000000)) < 0 && | |
(range.size === 4096 || range.size === 65536) | |
); | |
// range.protection === 'rwx' | |
}); | |
logger("\n------------------------------"); | |
logger("*** Filter Regions: " + pretty_print_array(matchedRanges)); | |
return matchedRanges; | |
} | |
function find_region(regions, pattern) { | |
const ins_ranges = | |
regions.filter((range) => { | |
const res = find_matched_ins(range, pattern); | |
return res && res.length === 1; | |
}) ?? []; | |
logger("\n------------------------------"); | |
if (ins_ranges.length === 0) { | |
logger_err("*** NONE region matched *** "); | |
return [0, null]; | |
} else if (ins_ranges.length === 1) { | |
logger_succ("*** Exactly 1 region matched *** "); | |
const res = find_matched_ins(ins_ranges[0], pattern, true); | |
const { address } = res[0]; | |
return [1, address]; | |
} else { | |
logger_err("*** Matched regions > 1. Abort patching ... "); | |
return [-1, null]; | |
} | |
} | |
function logger_warn(str, silent = false) { | |
if (!silent) console.log(Color.Yellow + str); | |
} | |
function logger_err(str, silent = false) { | |
if (!silent) console.log(Color.Red + str); | |
} | |
function logger_succ(str, silent = false) { | |
if (!silent) console.log(Color.Green + str); | |
} | |
function logger(str, silent = false) { | |
if (!silent) console.log(Color.Cyan + str); | |
} | |
function pretty_print_array(array) { | |
return ( | |
"[" + | |
array.reduce((acc, cur) => acc + `\n${JSON.stringify(cur)}`, "") + | |
"\n]" | |
); | |
} | |
function str2ab(str) { | |
let buf = new ArrayBuffer(str.length); | |
let bufView = new Uint8Array(buf); | |
for (var i = 0, strLen = str.length; i < strLen; i++) { | |
bufView[i] = str.charCodeAt(i); | |
} | |
return buf; | |
} | |
function buf2hex(buffer) { | |
// buffer is an ArrayBuffer | |
return [...new Uint8Array(buffer)] | |
.map((x) => x.toString(16).padStart(2, "0")) | |
.join(""); | |
} | |
function arr2ab(arr) { | |
let buf = new ArrayBuffer(arr.length); | |
let bufView = new Uint8Array(buf); | |
for (var i = 0, len = arr.length; i < len; i++) { | |
bufView[i] = arr[i]; | |
} | |
return buf; | |
} | |
function DEBUG_patchCode_using_alloc_bytes() { | |
// Failed #1 | |
const orgin_bytes = arr2ab([0x2b, 0x44, 0x24, 0x20]); | |
const nop_bytes = arr2ab([0x0f, 0x1f, 0x40, 0x00]); | |
// Failed #2 | |
const psize = 4; | |
const nop = Memory.alloc(psize); | |
nop.writeByteArray([0x0f, 0x1f, 0x40, 0x00]); | |
const nop_alloc = nop.readByteArray(psize); | |
const protect = Memory.protect(nop, psize, "rwx"); | |
logger_warn( | |
`### print nop_alloc<${nop.toString()}>: ` + | |
buf2hex(nop_alloc) + | |
", protect(): " + | |
protect | |
); | |
// Failed #3 | |
if (Kernel.available) { | |
// seems always false on dist macos build | |
logger("*** Kernel.pageSize: \n" + Kernel.pageSize); | |
const nop = Kernel.alloc(psize); | |
Kernel.writeByteArray(nop, [0x0f, 0x1f, 0x40, 0x00]); | |
const nop_kern = Kernel.readByteArray(nop, psize); | |
const protect = Kernel.protect(nop, psize, "rwx"); | |
logger_warn( | |
`### print nop_kern${nop_kern.toString()} ` + | |
buf2hex(nop_kern) + | |
", protect(): " + | |
protect | |
); | |
} else { | |
logger_err("*** Kernel API NOT available"); | |
} | |
// ... patchCode stuff | |
} | |
/* x86 Bytes from CE | |
sub eax,[rsp+20] // 0x2B,0x44,0x24,0x20 | |
add eax,[rsp+20] // 0x03,0x44,0x24,0x20 | |
sub eax,0 // 0x83,0xe8,0x00 | |
nop // 0x90 | |
*/ | |
// Failed attempt. NOT working except for patching NOP | |
function try_patch() { | |
try { | |
var game_module = Process.findModuleByName("Zombotron"); | |
logger("\n------------------------------"); | |
logger("*** Zombotron module info: \n" + JSON.stringify(game_module)); | |
logger("*** Process.pageSize: \n" + Process.pageSize); | |
const matched_ranges = get_app_rx_4096_ranges(); | |
var pattern = "2b 44 24 20 41 89 46 30"; | |
const [succ, address] = find_region(matched_ranges, pattern); | |
if (succ === 1) { | |
// Neither worked... except patching NOP | |
patchAction(address, (cw) => { | |
// cw.putSubRegImm('eax', 1); | |
// cw.putNop(); | |
// cw.putNopPadding(4); | |
// FIXME: ??? Exactly the same bytes as orgin program, always CRASH when get called // Patch NOP wont crash | |
cw.putBytes([0x2b, 0x44, 0x24, 0x20]); | |
// cw.putU8(0x2b); | |
// cw.putU8(0x44); | |
// cw.putU8(0x24); | |
// cw.putU8(0x20); | |
}); | |
logger_succ("*** PATCH success *** "); | |
} else if (succ === -1) { | |
logger_err("*** PATCH failed *** "); | |
} else { | |
logger_warn("*** Try to restore origin bytes ***"); | |
pattern = "83 e8 01 90 41 89 46 30"; // sub eax,1 | |
pattern = "0f 1f 40 00 41 89 46 30"; // NopPadding(4) | |
const [ret, addr] = find_region(matched_ranges, pattern); | |
if (ret === 1) { | |
patchAction(addr, (cw) => { | |
cw.putBytes(orgin_bytes); // orgin program code | |
}); | |
logger_succ("*** RESTORE success *** "); | |
} else { | |
logger_err("*** RESTORE failed *** "); | |
} | |
} | |
} catch (e) { | |
console.log("[-] ", e.stack); | |
} | |
} | |
function hook_appDidBecomeActive() { | |
try { | |
Interceptor.attach( | |
ObjC.classes.PlayerAppDelegate["- applicationDidBecomeActive:"] | |
.implementation, | |
{ | |
onEnter: function (args) { | |
console.log("[+] applicationDidBecomeActive"); | |
// try_patch(); | |
}, | |
} | |
); | |
} catch (e) { | |
console.log("[-] ", e.stack); | |
} | |
} | |
function DEBUG_region_scan() { | |
// FIXME: diffs ??? /usr/bin/vmmap <--> Process.findRangeByAddress() | |
const problem_addr = ptr(0x169f6c9a9); | |
var region; | |
region = Process.findRangeByAddress(problem_addr); | |
logger(" Find region: " + JSON.stringify(region)); // => null | |
region = Process.findModuleByAddress(problem_addr); | |
logger(" Find module: " + JSON.stringify(region)); // null | |
var ins = Instruction.parse(problem_addr); | |
logger(" Parse ins: " + ins); // => mov dword ptr [r14 + 0x30], eax | |
region = Process.findRangeByAddress(ptr(0x169f64000)); | |
logger(" Find 0x169f64000: " + JSON.stringify(region)); // OK | |
/* | |
vmmap output: | |
-------------- | |
IOAccelerator 169f60000-169f61000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
IOAccelerator 169f61000-169f62000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
IOAccelerator 169f62000-169f63000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
IOAccelerator 169f63000-169f64000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
VM_ALLOCATE 169f64000-169f74000 [ 64K 64K 64K 0K] rwx/rwx SM=PRV | |
IOAccelerator 169f74000-169f75000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
IOAccelerator 169f77000-169f78000 [ 4K 0K 0K 4K] rw-/rw- SM=SHM | |
-------------- | |
findModuleByAddress() output | |
-------------- | |
0x169f60000 - 0x169f61000 4096 -1 | |
0x169f61000 - 0x169f62000 4096 -1 | |
0x169f62000 - 0x169f63000 4096 -1 | |
0x169f63000 - 0x169f64000 4096 -1 | |
0x169f64000 - 0x169f69000 20480 -1 <<< ???: missing 0x169f69000-169f74000 | |
0x169f74000 - 0x169f75000 4096 1 | |
0x169f75000 - 0x169f77000 8192 1 | |
0x169f77000 - 0x169f78000 4096 1 | |
0x169f78000 - 0x169f79000 4096 1 | |
-------------- | |
findModuleByAddress() output detail | |
{"base":"0x169f60000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f61000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f62000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f63000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f64000","size":20480,"protection":"rwx","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f74000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f75000","size":8192,"protection":"r--","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
{"base":"0x169f77000","size":4096,"protection":"rw-","file":{"path":"/private/var/folders/3x/pswstjxn62x4rrsky_19zykr0000gn/C/com.apple.scriptmanager2.le.cache","offset":0,"size":19328}} | |
-------------- | |
*/ | |
logger( | |
"*** List All Regions: " + | |
pretty_print_array( | |
Process.enumerateRanges({ protection: "---", coalesce: false }) | |
.filter((range) => { | |
return ( | |
range.base.compare(ptr(0x160000000)) > 0 && | |
range.base.compare(problem_addr) <= 0 | |
); | |
}) | |
.filter((range) => { | |
const range_end = range.base.add(range.size); | |
// logger(range.base + ' - '+ range_end +' '+ range.size + '\t' + range_end.compare(problem_addr)) | |
return range_end.compare(problem_addr) > 0; | |
}) | |
) | |
); | |
const rangeMissing = { | |
base: ptr(0x169f64000), | |
size: 0x169f74000 - 0x169f64000, | |
}; | |
find_matched_ins(rangeMissing, "2b 44 24 20 41 89 46 30"); // => Found ins | |
} | |
function find_callee_addr() { | |
const matched_ranges = get_app_rx_4096_ranges(); | |
var pattern = "2b 44 24 20 41 89 46 30"; | |
const [succ, address] = find_region(matched_ranges, pattern); | |
logger_warn(`### succ:${succ}, address: ${address}`); | |
if (succ !== 1) { | |
logger_err("Exit"); | |
return; | |
} | |
var print_ptr = address.sub(0x40); | |
logger(`Find \`call r11\` from: ${print_ptr}`); | |
var jump_callee_ins; | |
var jump_callee_ins_prev; | |
var last_ins; | |
while (print_ptr.compare(address) <= 0) { | |
const ins = Instruction.parse(print_ptr); | |
print_ptr = ins.next; | |
var useLogger; | |
if (ins.mnemonic === "call" && ins.opStr === "r11") { | |
useLogger = logger_succ; | |
jump_callee_ins = ins; | |
jump_callee_ins_prev = last_ins; | |
} else { | |
useLogger = logger; | |
} | |
useLogger( | |
`${ins.address} \t${ins.size}\t ${ins.toString()} \t ------ M:${ | |
ins.mnemonic | |
} \tOP:${ins.opStr}` | |
); | |
last_ins = ins; | |
} | |
if (jump_callee_ins_prev) { | |
logger("\n-----------------------"); | |
logger(`Found --> ${jump_callee_ins_prev}`); | |
logger(JSON.stringify(jump_callee_ins_prev.operands)); | |
const [reg, op] = jump_callee_ins_prev.operands; | |
const callee_addr = ptr(op.value); | |
if (callee_addr.isNull()) return; | |
const callee_func = new NativeFunction(callee_addr, "pointer", [ | |
"pointer", | |
"pointer", | |
"int", | |
]); | |
Interceptor.attach(callee_func, { | |
onEnter(args) { | |
logger( | |
`onEnter ${ptr(args[0])} - ${ptr(args[1])} - ${ptr( | |
args[2] | |
)}` | |
); | |
}, | |
onLeave(retval) { | |
if (!retval) return; | |
if (ptr(retval).isNull()) return; | |
const val = ptr(retval).add(0x30).readU32(); | |
logger(`onLeave -> ${retval}+0x30.readU32() => ${val}`); | |
if (val && val != 11) { | |
// logger(`onLeave -> ${retval}+0x30.readU32() = ${val}`); | |
// if (val === 7) { | |
// ptr(retval).add(0x30).writeU32(0xff) | |
// } | |
} | |
}, | |
}); | |
} | |
} | |
find_callee_addr(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment