Skip to content

Instantly share code, notes, and snippets.

@wving5
Created August 1, 2023 16:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wving5/f904d9d5698159f8262619765db6dcb5 to your computer and use it in GitHub Desktop.
Save wving5/f904d9d5698159f8262619765db6dcb5 to your computer and use it in GitHub Desktop.
Practice Frida with Zombotron(U3D)
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