动态看,一坨
静态看,更是依托
所以,我要写一个通用的hook脚本,hhhhhhh
Hook编写
在Linux下通过pip install Cython安装。安装完毕后执行cython --version,如果输出了版本号即安装成功。
安装完成后,我们创建一个hook_test项目,需要创建hook_test.pyx和setup.py两个文件。
# setup.py
from distutils.core import setup
from Cython.Build import cythonize
# setup() 函数是核心,它定义了如何构建你的模块
setup(
# 'name' 字段是这个模块的名称,可以自定义
name='hook_test',
ext_modules=cythonize("hook_test.pyx")
)
# hook_test.pyx
def process_data(a, b, c, d, e):
num1 = a + b
print("PyNumber_Add:", num1)
num2 = a - b
print("PyNumber_Subtract:", num2)
num3 = a * b
print("PyNumber_Multiply:", num3)
num4 = a & b
print("PyNumber_And:", num4)
num5 = a | b
print("PyNumber_Or:", num5)
num6 = a ^ b
print("PyNumber_Xor:", num6)
num7 = a > 1
print("PyNumber_Rshift:", num8)
b >>= 1
print("PyNumber_InPlaceRshift:", b)
num10 = a ** 2
print("PyNumber_Power:", num10)
num11 = a % 100
print("PyNumber_Remainder:", num11)
num13 = e[0]
print("PyNumber_Index:", num13)
c += a
print("PyNumber_InPlaceAdd:", c)
return a == b
然后使用下述命令进行编译:
python setup.py build_ext --inplace
获得pyd文件
随后:
# challenge.py
import hook_test
import os
import sys
print(sys.executable)
print(os.getpid())
if hasattr(sys, "set_int_max_str_digits"):
sys.set_int_max_str_digits(0)
# 准备输入数据
a = 1234345
b = 67890
c = 13579
d = 24680
e = [42,53]
# 调用 Cython 函数
input()
hook_test.process_data(a, b, c, d, e)
print("调用成功")
首先运行challenge.py,然后运行hook代码即可:frida -p 14240 -l hook_05.js
'use strict';
/**
* ======================================================
* Frida hook script for Python3 numeric & compare ops
* - Hooks PyNumber_* (加减乘除、位运算、取余、移位、模幂)
* - Hooks PyObject_RichCompareBool / PyObject_RichCompare (比较)
* - Hooks PyLong_FromString / PyLong_FromUnicodeObject
* - Fixed type error: use int64/uint64 instead of longlong
* - Added fixed-width HEX output (e.g. 0x12345678)
* ======================================================
*/
const logFilePath = "./log.txt";
const logFile = new File(logFilePath, "w");
function logLine(s) {
logFile.write(s + "\n");
logFile.flush();
console.log(s);
}
// ---------------------------------------------------------------------
function findPythonModule() {
const mods = Process.enumerateModulesSync();
for (const m of mods) {
const n = m.name.toLowerCase();
if (n.startsWith('python3') && n.endsWith('.dll')) return m;
if (n.startsWith('python') && n.endsWith('.dll')) return m;
}
throw new Error('python3*.dll not found. Use: frida -f python.exe -l hook_number_compare.js');
}
const pymod = findPythonModule();
logLine(' using python module: ' + pymod.name + ' ' + pymod.base);
// safe wrapper to resolve exports and print status
function exp(name) {
try {
const addr = Module.getExportByName(pymod.name, name);
logLine(`[+] export ${name} => ${addr}`);
return addr;
} catch (e) {
logLine(`[-] export ${name} not found`);
return null;
}
}
// ---------------------------------------------------------------------
// list of symbols
const symbols = {
PyUnicode_AsUTF8: exp('PyUnicode_AsUTF8'),
PyObject_Repr: exp('PyObject_Repr'),
PyObject_Str: exp('PyObject_Str'),
PyLong_Check: exp('PyLong_Check'),
PyLong_AsLongLong: exp('PyLong_AsLongLong'),
PyLong_AsUnsignedLongLongMask: exp('PyLong_AsUnsignedLongLongMask'),
PyLong_FromString: exp('PyLong_FromString'),
PyLong_FromUnicodeObject: exp('PyLong_FromUnicodeObject'),
PyNumber_Add: exp('PyNumber_Add'),
PyNumber_InPlaceAdd: exp('PyNumber_InPlaceAdd'),
PyNumber_Subtract: exp('PyNumber_Subtract'),
PyNumber_Multiply: exp('PyNumber_Multiply'),
PyNumber_And: exp('PyNumber_And'),
PyNumber_Or: exp('PyNumber_Or'),
PyNumber_Xor: exp('PyNumber_Xor'),
PyNumber_Lshift: exp('PyNumber_Lshift'),
PyNumber_Rshift: exp('PyNumber_Rshift'),
PyNumber_InPlaceRshift: exp('PyNumber_InPlaceRshift'),
PyNumber_Power: exp('PyNumber_Power'),
PyNumber_Remainder: exp('PyNumber_Remainder'),
PyNumber_Index: exp('PyNumber_Index'),
PyObject_RichCompare: exp('PyObject_RichCompare'),
PyObject_RichCompareBool: exp('PyObject_RichCompareBool')
};
function tryCreateNativeFunction(addr, retType, argTypes, friendlyName) {
if (!addr) return null;
try {
const nf = new NativeFunction(addr, retType, argTypes);
logLine(`[+] NativeFunction created for ${friendlyName}`);
return nf;
} catch (e) {
logLine(`[-] Failed creating NativeFunction for ${friendlyName}: ${e.message}`);
return null;
}
}
const PyUnicode_AsUTF8 = tryCreateNativeFunction(symbols.PyUnicode_AsUTF8, 'pointer', ['pointer'], 'PyUnicode_AsUTF8');
const PyObject_Repr = tryCreateNativeFunction(symbols.PyObject_Repr, 'pointer', ['pointer'], 'PyObject_Repr');
const PyObject_Str = tryCreateNativeFunction(symbols.PyObject_Str, 'pointer', ['pointer'], 'PyObject_Str');
const PyLong_Check = tryCreateNativeFunction(symbols.PyLong_Check, 'int', ['pointer'], 'PyLong_Check');
const PyLong_AsLongLong = tryCreateNativeFunction(symbols.PyLong_AsLongLong, 'int64', ['pointer'], 'PyLong_AsLongLong');
const PyLong_AsUnsignedLongLongMask = tryCreateNativeFunction(symbols.PyLong_AsUnsignedLongLongMask, 'uint64', ['pointer'], 'PyLong_AsUnsignedLongLongMask');
// ---------------------------------------------------------------------
function toHexFixed(num, width = 8) {
try {
let big;
if (typeof num === 'bigint') big = num;
else if (typeof num === 'number') big = BigInt(num >>> 0);
else if (typeof num === 'string') {
if (/^0x[0-9a-fA-F]+$/.test(num)) big = BigInt(num);
else if (/^-?\d+$/.test(num)) {
big = BigInt(num);
if (big `;
let hex = big.toString(16);
if (hex.length `;
}
}
function isDecimalString(s) {
return typeof s === 'string' && /^-?\d+$/.test(s);
}
function isHexString(s) {
return typeof s === 'string' && /^0x[0-9a-fA-F]+$/.test(s);
}
function formatForLogging(s, width = 8) {
if (s === undefined || s === null) return s;
if (isHexString(s)) return s;
if (isDecimalString(s)) return toHexFixed(s, width);
const m = /(-?\d+)$/.exec(String(s).trim());
if (m) return toHexFixed(m[1], width);
return s;
}
function safeUtf8FromPyStr(pyStrPtr) {
if (!pyStrPtr || pyStrPtr.isNull()) return '';
try {
if (!PyUnicode_AsUTF8) return '';
const cstr = PyUnicode_AsUTF8(pyStrPtr);
if (cstr.isNull()) return '';
return Memory.readUtf8String(cstr);
} catch {
return '';
}
}
function objToStr(pyObjPtr) {
if (!pyObjPtr || pyObjPtr.isNull()) return '';
try {
if (PyLong_Check) {
const isLong = PyLong_Check(pyObjPtr);
if (isLong === 1) {
if (PyLong_AsUnsignedLongLongMask) {
const u = PyLong_AsUnsignedLongLongMask(pyObjPtr);
return toHexFixed(u);
}
if (PyLong_AsLongLong) {
const v = PyLong_AsLongLong(pyObjPtr);
return toHexFixed(v);
}
}
}
if (PyObject_Str) {
const sObj = PyObject_Str(pyObjPtr);
if (sObj && !sObj.isNull()) return safeUtf8FromPyStr(sObj);
}
if (PyObject_Repr) {
const r = PyObject_Repr(pyObjPtr);
if (r && !r.isNull()) return safeUtf8FromPyStr(r);
}
return '';
} catch (e) {
return ``;
}
}
// ---------------------------------------------------------------------
// Hook PyLong_FromUnicodeObject / PyLong_FromString
if (symbols.PyLong_FromUnicodeObject) {
Interceptor.attach(symbols.PyLong_FromUnicodeObject, {
onEnter(args) {
this.sRaw = objToStr(args[0]);
this.base = args[1].toInt32();
},
onLeave(retval) {
const got = objToStr(retval);
logLine(`[PyLong_FromUnicodeObject] int(s, base=${this.base}) s=${this.sRaw} -> ${formatForLogging(got)}`);
}
});
}
if (symbols.PyLong_FromString) {
Interceptor.attach(symbols.PyLong_FromString, {
onEnter(args) {
try { this.s = Memory.readUtf8String(args[0]); } catch (e) { this.s = ''; }
this.base = args[2].toInt32();
},
onLeave(retval) {
const got = objToStr(retval);
logLine(`[PyLong_FromString] int(s, base=${this.base}) s=${this.s} -> ${formatForLogging(got)}`);
}
});
}
function symbolFor(name) {
const map = {
PyNumber_Add: '+',
PyNumber_InPlaceAdd: '+=',
PyNumber_Subtract: '-',
PyNumber_Multiply: '*',
PyNumber_And: '&',
PyNumber_Or: '|',
PyNumber_Xor: '^',
PyNumber_Lshift: '>',
PyNumber_InPlaceRshift: '>>=',
PyNumber_Power: '**',
PyNumber_Remainder: '%',
PyNumber_Index: 'index'
};
return map[name] || '?';
}
// ---------------------------------------------------------------------
//
// function hookPyNumber(name, ptr) {
// if (!ptr) return;
// Interceptor.attach(ptr, {
// onEnter(args) {
// this.aPtr = args[0];
// this.bPtr = args[1];
// this.aRaw = objToStr(this.aPtr);
// this.bRaw = objToStr(this.bPtr);
// this.a = formatForLogging(this.aRaw, 8);
// this.b = formatForLogging(this.bRaw, 8);
// try {
// this.cPtr = args[2];
// if (this.cPtr && !this.cPtr.isNull()) {
// this.cRaw = objToStr(this.cPtr);
// this.c = formatForLogging(this.cRaw, 8);
// } else {
// this.cPtr = null;
// }
// } catch (e) { this.cPtr = null; }
// },
// onLeave(retval) {
// if (name === 'PyNumber_Power') {
// if (this.cPtr) {
// logLine(`[${name}] pow(${this.a}, ${this.b}, ${this.c}) = ${formatForLogging(objToStr(retval), 8)}`);
// } else {
// logLine(`[${name}] ${this.a} ** ${this.b} = `);
// }
// return;
// }
// logLine(`[${name}] ${this.a} ${symbolFor(name)} ${this.b} = ${formatForLogging(objToStr(retval), 8)}`);
// }
// });
// }
function hookPyNumber(name, ptr) {
if (!ptr) return;
Interceptor.attach(ptr, {
onEnter(args) {
// onEnter 的逻辑保持不变,依然是捕获原始指针和字符串
this.aPtr = args[0];
this.bPtr = args[1];
this.aRaw = objToStr(this.aPtr);
this.bRaw = objToStr(this.bPtr);
this.a = formatForLogging(this.aRaw, 8);
this.b = formatForLogging(this.bRaw, 8);
try {
this.cPtr = args[2];
if (this.cPtr && !this.cPtr.isNull()) {
this.cRaw = objToStr(this.cPtr);
this.c = formatForLogging(this.cRaw, 8);
} else {
this.cPtr = null;
this.cRaw = null;
this.c = null;
}
} catch (e) {
this.cPtr = null;
this.cRaw = null;
this.c = null;
}
},
onLeave(retval) {
// --- 主要修改部分在这里 ---
// 1. 统一检查并准备用于日志的字符串
// 检查第一个操作数 a
let aStr = this.aRaw.length > 32 ? "num" : this.a;
// 检查第二个操作数 b
let bStr = this.bRaw.length > 32 ? "num" : this.b;
// 检查返回值 (c)
const outRaw = objToStr(retval);
let outFmt = outRaw.length > 32 ? "num" : formatForLogging(outRaw, 8);
// 2. 根据不同的函数名,使用准备好的字符串来格式化日志
if (name === 'PyNumber_Power') {
// 对于 pow(a, b, mod),第三个参数也需要检查
let modStr = this.c ? (this.cRaw.length > 32 ? "num" : this.c) : "None";
logLine(`[${name}] pow(${aStr}, ${bStr}, ${modStr}) = ${outFmt}`);
return;
}
// 对于 PyNumber_Remainder 和其他所有二元运算
logLine(`[${name}] ${aStr} ${symbolFor(name)} ${bStr} = ${outFmt}`);
}
});
}
// ---------------------------------------------------------------------
[
'PyNumber_Add',
'PyNumber_InPlaceAdd',
'PyNumber_Subtract',
'PyNumber_Multiply',
'PyNumber_And',
'PyNumber_Or',
'PyNumber_Xor',
'PyNumber_Lshift',
'PyNumber_Rshift',
'PyNumber_InPlaceRshift',
'PyNumber_Remainder',
'PyNumber_Index',
'PyNumber_Power'
].forEach(n => hookPyNumber(n, symbols[n]));
// 比较操作
const cmpOps = ['', '>='];
if (symbols.PyObject_RichCompareBool) {
Interceptor.attach(symbols.PyObject_RichCompareBool, {
onEnter(args) {
this.ptrA = args[0];
this.ptrB = args[1];
this.op = args[2].toInt32();
this.aRaw = objToStr(this.ptrA);
this.bRaw = objToStr(this.ptrB);
this.a = formatForLogging(this.aRaw, 8);
this.b = formatForLogging(this.bRaw, 8);
},
onLeave(retval) {
const opStr = cmpOps[this.op] || `op(${this.op})`;
if (this.ptrA.equals(this.ptrB)) return;
if (this.aRaw === this.bRaw && this.op === 2) return;
logLine(`[CompareBool] ${this.a} ${opStr} ${this.b} -> ${retval.toInt32()}`);
}
});
}
if (symbols.PyObject_RichCompare) {
Interceptor.attach(symbols.PyObject_RichCompare, {
onEnter(args) {
this.aRaw = objToStr(args[0]);
this.bRaw = objToStr(args[1]);
this.op = args[2].toInt32();
this.a = formatForLogging(this.aRaw, 8);
this.b = formatForLogging(this.bRaw, 8);
},
onLeave(retval) {
logLine(`[CompareObj] ${this.a} ${cmpOps[this.op]} ${this.b} -> ${objToStr(retval)}`);
}
});
}
logLine(`\n✅ Hooks installed on ${pymod.name}`);
logLine(`燐 PyNumber_* operations + Compare ops are now being traced in HEX...\n`);
但是有个问题,hook不是万能的,hook不全所有的函数,比如对于Lshift、Rshift等逻辑操作来说,是hook不到的,因为有可能他就不走 Py提供的库函数,直接在pyd中自己实现了
总结了一下,我写的hook_test:
PyNumber_Add: 1302235
PyNumber_Subtract: 1166455
PyNumber_Multiply: 83799682050
PyNumber_And: 288
PyNumber_Or: 1301947
PyNumber_Xor: 1301659
PyNumber_Lshift: 4937380
PyNumber_Rshift: 33945
PyNumber_InPlaceRshift: 33945
PyNumber_Power: 1523607579025
PyNumber_Remainder: 45
PyNumber_Index: 42
PyNumber_InPlaceAdd: 1247924
CompareObj
一共14种,但是我只hook到了9种:
using python module: python312.dll 0x7ffcf6920000
[+] export PyUnicode_AsUTF8 => 0x7ffcf6ac3c58
[+] export PyObject_Repr => 0x7ffcf6998af8
[+] export PyObject_Str => 0x7ffcf6998968
[-] export PyLong_Check not found
[+] export PyLong_AsLongLong => 0x7ffcf69e17d4
[+] export PyLong_AsUnsignedLongLongMask => 0x7ffcf6a9b880
[+] export PyLong_FromString => 0x7ffcf69e3d60
[+] export PyLong_FromUnicodeObject => 0x7ffcf69e35a0
[+] export PyNumber_Add => 0x7ffcf699fd9c
[+] export PyNumber_InPlaceAdd => 0x7ffcf6a76230
[+] export PyNumber_Subtract => 0x7ffcf6951bc8
[+] export PyNumber_Multiply => 0x7ffcf69e6808
[+] export PyNumber_And => 0x7ffcf6982ba8
[+] export PyNumber_Or => 0x7ffcf6951b10
[+] export PyNumber_Xor => 0x7ffcf6951ab4
[+] export PyNumber_Lshift => 0x7ffcf6a6f47c
[+] export PyNumber_Rshift => 0x7ffcf6982980
[+] export PyNumber_InPlaceRshift => 0x7ffcf6aadfa0
[+] export PyNumber_Power => 0x7ffcf6b6d228
[+] export PyNumber_Remainder => 0x7ffcf6951a58
[+] export PyNumber_Index => 0x7ffcf69ede34
[+] export PyObject_RichCompare => 0x7ffcf69e7610
[+] export PyObject_RichCompareBool => 0x7ffcf69913d8
[+] NativeFunction created for PyUnicode_AsUTF8
[+] NativeFunction created for PyObject_Repr
[+] NativeFunction created for PyObject_Str
[+] NativeFunction created for PyLong_AsLongLong
[+] NativeFunction created for PyLong_AsUnsignedLongLongMask
✅ Hooks installed on python312.dll
燐 PyNumber_* operations + Compare ops are now being traced in HEX...
[PyNumber_Add] 0x0012d5a9 + 0x00010932 = 0x0013dedb
[PyNumber_Subtract] 0x0012d5a9 - 0x00010932 = 0x0011cc77
[PyNumber_Multiply] 0x0012d5a9 * 0x00010932 = 0x1382d9ac02
[PyNumber_And] 0x0012d5a9 & 0x00010932 = 0x00000120
[PyNumber_Or] 0x0012d5a9 | 0x00010932 = 0x0013ddbb
[PyNumber_Xor] 0x0012d5a9 ^ 0x00010932 = 0x0013dc9b
[PyNumber_Power] pow(0x0012d5a9, 0x00000002, None) = 0x162be16a991
[PyNumber_InPlaceAdd] 0x0000350b += 0x0012d5a9 = 0x00130ab4
[CompareObj] 0x0012d5a9 == 0x00008499 -> False
可以确定的是,移位或者取值操作是hook不到的
PyNumber_Lshift
PyNumber_Rshift
PyNumber_InPlaceRshift
PyNumber_Index
PyNumber_InPlaceAdd
但是,PyNumber_Remainder可能可以
依旧国赛初赛rand0m:
((int(n,16)^2654435769)>>11)**65537%4294967293
这个运算里面的%就可以hook到...........
我写的num11 = a % 100不行,woc,凭啥啊...........
实战(我要一把梭...)
2024ciscn长城杯_rand0m
不用ida哈,直接跑hook
input:12345678876543211234567887654321
log.txt
[PyLong_FromString] int(s, base=16) s=12345678 -> 0x12345678
[PyLong_FromUnicodeObject] int(s, base=16) s=12345678 -> 0x12345678
[PyNumber_Xor] 0x12345678 ^ 0x9e3779b9 = 0x8c032fc1
[PyNumber_And] 0x123456780 & 0xfa3affff = 0x22006780
[PyNumber_Add] 0x22006780 + 0x00000001 = 0x22006781
[PyNumber_Power] pow(0x00118065, 0x00010001, None) = num
[PyNumber_Remainder] num % 0xfffffffd = 0x8c219e43
[CompareObj] 0x22006781 == 0x12287f38 -> False
[PyLong_FromString] int(s, base=16) s=87654321 -> 0x87654321
[PyLong_FromUnicodeObject] int(s, base=16) s=87654321 -> 0x87654321
[PyNumber_Xor] 0x87654321 ^ 0x9e3779b9 = 0x19523a98
[PyNumber_And] 0x876543210 & 0xfa3affff = 0x72103210
[PyNumber_Add] 0x72103210 + 0x00000008 = 0x72103218
[PyNumber_Power] pow(0x00032a47, 0x00010001, None) = num
[PyNumber_Remainder] num % 0xfffffffd = 0x045ca420
[CompareObj] 0x72103218 == 0x4a30f74d -> False
[PyLong_FromString] int(s, base=16) s=12345678 -> 0x12345678
[PyLong_FromUnicodeObject] int(s, base=16) s=12345678 -> 0x12345678
[PyNumber_Xor] 0x12345678 ^ 0x9e3779b9 = 0x8c032fc1
[PyNumber_And] 0x123456780 & 0xfa3affff = 0x22006780
[PyNumber_Add] 0x22006780 + 0x00000001 = 0x22006781
[PyNumber_Power] pow(0x00118065, 0x00010001, None) = num
[PyNumber_Remainder] num % 0xfffffffd = 0x8c219e43
[CompareObj] 0x22006781 == 0x023a1268 -> False
[PyLong_FromString] int(s, base=16) s=87654321 -> 0x87654321
[PyLong_FromUnicodeObject] int(s, base=16) s=87654321 -> 0x87654321
[PyNumber_Xor] 0x87654321 ^ 0x9e3779b9 = 0x19523a98
[PyNumber_And] 0x876543210 & 0xfa3affff = 0x72103210
[PyNumber_Add] 0x72103210 + 0x00000008 = 0x72103218
[PyNumber_Power] pow(0x00032a47, 0x00010001, None) = num
[PyNumber_Remainder] num % 0xfffffffd = 0x045ca420
[CompareObj] 0x72103218 == 0x88108807 -> False
[CompareObj] 0x00003558 == 0x00003558 -> True
明显的4部分,这不爽多了嘛hhhhhhhhhhhh
调出来一部分看:
[PyLong_FromString] int(s, base=16) s=12345678 -> 0x12345678
[PyLong_FromUnicodeObject] int(s, base=16) s=12345678 -> 0x12345678
[PyNumber_Xor] 0x12345678 ^ 0x9e3779b9 = 0x8c032fc1
[PyNumber_And] 0x123456780 & 0xfa3affff = 0x22006780
[PyNumber_Add] 0x22006780 + 0x00000001 = 0x22006781
[PyNumber_Power] pow(0x00118065, 0x00010001, None) = num
[PyNumber_Remainder] num % 0xfffffffd = 0x8c219e43
[CompareObj] 0x22006781 == 0x12287f38 -> False
复现的:
input^0x9e3779b9
a1 = (input0 & 0xfa3affff) + 0x00000001
a2 = pow(x,0x00118065,) % 0xfffffffd
真实的:
第一个返回值:((int(n,16)^2654435769)>>11)**65537%4294967293
第二个返回值:((int(n,16)>5)>>23)
就差几个移位运算,一动态就出,好的,爽!
总结
嗯,除了几个移位或者取值操作捕捉不到,其他的,简直是降维
想tea等常规加密,直接就能看出来,用来trace一下,针不戳
不过还是得动调,不能直接丢弃ida,哎
哎[/md]