Tags: frida
Rating: 5.0
The binary was an ELF that implemented a simple VM with copy, add, subtract, xor and compare operations.
The bytecode code was decoding part of itself with by xoring it again and again - the operation was done 4096^4 times. Hence it took a lot of time.
This is the disassembled bytecode that shows the 4 nested loops of 4096
```
0x801 : mov data[1], 1 : 0
...
0x813 : mov data[26], 4095 : 0
0x814 : if !data[26] jmp ip+84 : 4095
0x815 : sub data[26], data[1] : 4095 : 1
0x816 : mov data[25], 4095 : 0
0x817 : if !data[25] jmp ip+80 : 4095
0x818 : sub data[25], data[1] : 4095 : 1
0x819 : mov data[24], 4095 : 0
0x81a : if !data[24] jmp ip+76 : 4095
0x81b : sub data[24], data[1] : 4095 : 1
0x81c : mov data[23], 4095 : 0
0x81d : if !data[23] jmp ip+72 : 4095
0x81e : sub data[23], data[1] : 4095 : 1
```
We patch these 4 vm instructions and set 4096 to 1 and run the binary. The disassembler can be written easily as there was no obfuscation. This is a simple tracer that disassembles and prints the bytes read.
```js
setImmediate(function () {
var base;
var modules = Process.enumerateModules();
modules.forEach((i) => {
// console.log(i.name);
if (i.name === "chall") {
base = i.base;
}
});
console.log(base);
var tohook = base.add(0x10A7);
var comphook = base.add(0x1214);
var data = base.add(0x4038);
Interceptor.attach(tohook,
function (args) {
var i = this.context.rdx;
var op = (i >> 24) & 0xff;
var arg1 = (i >> 12) & 0xfff;
var arg2 = i & 0xfff;
console.log(this.context.rax);
if (op == 0xd6) {
var d1 = data.add(Number(arg1 * 4)).readU32();
var d2 = data.add(Number(arg2 * 4)).readU32();
console.log(`mov data[${arg1}], data[${arg2}] : ${d1} : ${d2}`);
}
else if (op == 0xd5) {
var d1 = data.add(Number(arg1 * 4)).readU32();
console.log(`data[${arg1}] = getchar() : ${d1}`);
}
else if (op == 0xd8) {
var d1 = data.add(Number(arg1 * 4)).readU32();
console.log(`mov data[${arg1}], ${arg2} : ${d1}`);
}
else if (op == 0xf6) {
var d1 = data.add(Number(arg1 * 4)).readU32();
console.log(`putchar(data[${arg1}])`);
}
else if (op == 0x18) {
var d1 = data.add(Number(arg1 * 4)).readU32();
var d2 = data.add(Number(arg2 * 4)).readU32();
console.log(`xor data[${arg1}], data[${arg2}] : ${d1} : ${d2}`);
}
else if (op == 0x69) {
var d1 = data.add(Number(arg1 * 4)).readU32();
console.log(`if !data[${arg1}] jmp ip+${arg2} : ${d1}`);
}
else if (op == 0xa6) {
console.log(`jmp ${arg1}`);
}
else if (op == 0x16) {
var d1 = data.add(Number(arg1 * 4)).readU32();
var d2 = data.add(Number(arg2 * 4)).readU32();
console.log(`add data[${arg1}], data[${arg2}] : ${d1} : ${d2}`);
}
else if (op == 0x17) {
var d1 = data.add(Number(arg1 * 4)).readU32();
var d2 = data.add(Number(arg2 * 4)).readU32();
console.log(`sub data[${arg1}], data[${arg2}] : ${d1} : ${d2}`);
}
else {
console.log(`${op} ${arg1} ${arg2}`);
}
}
);
Interceptor.attach(comphook,
function (args) {
var readat = ptr(Number(Number(this.context.rax * 4) + Number(this.context.rbp)));
console.log(readat, readat.readU32());
}
);
});
```