- 序文
- 1.iosフッキングサーチのソースコード解析
- 2. ApiResolver はメモリ内のすべてのシンボルを検索します
- 3. すべてのクラス/すべてのメソッド/すべてのオーバーロードの列挙検索
- 4.すべてのクラス/すべてのメソッド/すべてのオーバーロードをフックする
- 5. 解析パラメータ/コールスタック/戻り値を出力(変更)
- 6. メモリ内のすべてのモジュール/シンボル/アドレスを列挙します。
- 7. Brainless Automation Hook アプリケーション パッケージのすべての機能
- 8. オブジェクトメモリローミングによるすべてのオブジェクトの検索
- 9.ObjC.choose はすべてのクラス出力プロパティを列挙します
- 10. オブジェクトメソッドを積極的に呼び出してアルゴリズムの実行結果を取得する
- 11 リモート バッチ オフライン用の RPC ブラック ボックス呼び出しアルゴリズムを構成する
序文
ついに Frida の部分を学びました。この記事では、自動フック フレームワークである Objection のソース コードから開始し、Frida の組み込み ApiResolver スクレーパーを使用して、すべてのクラス/すべてのメソッド/すべてを含むメモリ内のすべてのシンボルを列挙する方法を学びます。ヘビーロードを実行し、それをフックしてパラメータ呼び出しスタックの戻り値を出力し、関数ロジックを変更する目的を達成するためにパラメータと戻り値を変更します。
一度に 1 つの関数をフックすることが最終的な目標ではありません。バッチ オートメーションで一度に数百の関数をフックするのは本当に素晴らしいことです。この記事では、App のすべてのクラスを検索してそれらをすべてフックし、 Frida は、メモリの特性をトラバースして、ObjC オブジェクトを検索し、メソッドを直接呼び出し、アルゴリズムの実行結果を取得し、最後にリモート バッチ オフライン呼び出し用に RPC を構成します。これにより、チーム メンバーがアルゴリズムの実行結果を取得できるようになります。アルゴリズム実装の詳細は不明。 目的。
添付されたAPP、コードなどは私のプロジェクトにあり、自分で入手できます。
https://github.com/r0ysue/AndroidSecurityStudy
1.iosフッキングサーチのソースコード解析
ここでは、github に直接アクセスして異議のソース コードをダウンロードし、ios 用のフックがどのように記述されているかを確認します。
反対意見の ios 関連のフック コードは、agent–>src–>ios–>hooking.ts ファイルにあり、ここでは主にその検索と監視がどのように実装されているかを確認します。
const objcEnumerate = (pattern: string): ApiResolverMatch[] => {
return new ApiResolver('objc').enumerateMatches(pattern);
};
export const search = (patternOrClass: string): ApiResolverMatch[] => {
// if we didnt get a pattern, make one assuming its meant to be a class
if (!patternOrClass.includes('['))
patternOrClass = `*[*${patternOrClass}* *]`;
return objcEnumerate(patternOrClass);
};
ここでは、まず検索ソース コードを確認します。渡したパラメーターが patternOrclass パラメーターとして使用されていることがわかります。まず、対応する形式であるかどうかを確認し、そうでない場合は完了してから、ApiResolverMatch を使用して、ファイル内のシンボルを検索します。フックするメソッドを見つけるためのメモリ アドレス、iosのフック、androidのso層のフックは両方ともアドレスを直接フックしてから、watchメソッドを確認します
export const watch = (patternOrClass: string, dargs: boolean = false, dbt: boolean = false,
dret: boolean = false, watchParents: boolean = false): void => {
// Add the job
// We init a new job here as the child watch* calls will be grouped in a single job.
// mostly commandline fluff
const job: IJob = {
identifier: jobs.identifier(),
invocations: [],
type: `ios-watch for: ${patternOrClass}`,
};
jobs.add(job);
const isPattern = patternOrClass.includes('[');
// if we have a patterm we'll loop the methods, hook and push a listener to the job
if (isPattern === true) {
const matches = objcEnumerate(patternOrClass);
matches.forEach((match: ApiResolverMatch) => {
watchMethod(match.name, job, dargs, dbt, dret);
});
return;
}
watchClass(patternOrClass, job, dargs, dbt, dret, watchParents);
};
watch パラメータを使用すると、ジョブリストに追加され、同時に watchClass メソッドが呼び出されます。
const watchClass = (clazz: string, job: IJob, dargs: boolean = false, dbt: boolean = false,
dret: boolean = false, parents: boolean = false): void => {
const target = ObjC.classes[clazz];
if (!target) {
send(`${c.red(`Error!`)} Unable to find class ${c.redBright(clazz)}!`);
return;
}
// with parents as true, include methods from a parent class,
// otherwise simply hook the target class' own methods
(parents ? target.$methods : target.$ownMethods).forEach((method) => {
// filter and make sure we have a type and name. Looks like some methods can
// have '' as name... am expecting something like "- isJailBroken"
const fullMethodName = `${method[0]}[${clazz} ${method.substring(2)}]`;
watchMethod(fullMethodName, job, dargs, dbt, dret);
});
};
watchClass メソッドでは、最初にパラメータ clazz をパラメータとして渡し、API ObjC.classes[clazz] を使用してクラス オブジェクトを取得し、次にクラス メソッドをトラバースしてメソッド フックのために watchMethod メソッドを呼び出していることがわかります。
const watchMethod = (selector: string, job: IJob, dargs: boolean, dbt: boolean,
dret: boolean): void => {
const resolver = new ApiResolver("objc");
let matchedMethod = {
address: undefined,
name: undefined,
};
// handle the resolvers error it may throw if the selector format is off.
try {
// select the first match
const resolved = resolver.enumerateMatches(selector);
if (resolved.length <= 0) {
send(`${c.red(`Error:`)} No matches for selector ${c.redBright(`${selector}`)}. ` +
`Double check the name, or try "ios hooking list class_methods" first.`);
return;
}
// not sure if this will ever be the case... but lets log it
// anyways
if (resolved.length > 1) {
send(`${c.yellow(`Warning:`)} More than one result for selector ${c.redBright(`${selector}`)}!`);
}
matchedMethod = resolved[0];
} catch (error) {
send(
`${c.red(`Error:`)} Unable to find address for selector ${c.redBright(`${selector}`)}! ` +
`The error was:\n` + c.red((error as Error).message),
);
return;
}
// Attach to the discovered match
// TODO: loop correctly when globbing
send(`Found selector at ${c.green(matchedMethod.address.toString())} as ${c.green(matchedMethod.name)}`);
const watchInvocation: InvocationListener = Interceptor.attach(matchedMethod.address, {
// tslint:disable-next-line:object-literal-shorthand
onEnter: function (args) {
// how many arguments do we have in this selector?
const argumentCount: number = (selector.match(/:/g) || []).length;
const receiver = new ObjC.Object(args[0]);
send(
c.blackBright(`[${job.identifier}] `) +
`Called: ${c.green(`${selector}`)} ${c.blue(`${argumentCount}`)} arguments` +
`(Kind: ${c.cyan(receiver.$kind)}) (Super: ${c.cyan(receiver.$superClass.$className)})`,
);
// if we should include a backtrace to here, do that.
if (dbt) {1
send(
c.blackBright(`[${job.identifier}] `) +
`${c.green(`${selector}`)} Backtrace:\n\t` +
Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n\t"),
);
}
if (dargs && argumentCount > 0) {
const methodSplit = ObjC.selectorAsString(args[1]).split(":").filter((val) => val);
const r = methodSplit.map((argName, position) => {
// As this is an ObjectiveC method, the arguments are as follows:
// 0. 'self'
// 1. The selector (object.name:)
// 2. The first arg
//
// For this reason do we shift it by 2 positions to get an 'instance' for
// the argument value.
const t = new ObjC.Object(args[position + 2]);
return `${argName}: ${c.greenBright(`${t}`)}`;
});
send(c.blackBright(`[${job.identifier}] `) +
`Argument dump: [${c.green(receiver.$className)} ${r.join(" ")}]`);
}
},
onLeave: (retval) => {
// do nothing if we are not expected to dump return values
if (!dret) { return; }
send(c.blackBright(`[${job.identifier}] `) + `Return Value: ${c.red(retval.toString())}`);
},
});
job.invocations.push(watchInvocation);
};
コードを観察すると、ここでは frida の API Interceptor.attach がフックされ、受信パラメータ dargs と dbt に従ってパラメータとコールスタックを出力するかどうかを判断するために使用されていることがわかります。
2. ApiResolver はメモリ内のすべてのシンボルを検索します
まずは公式WebサイトにアクセスしてAPIの紹介をご覧ください。
API がモジュール module と objc をサポートしており、戻り値が API 名とアドレスを含むオブジェクト配列であることがわかります。次に、この API を使用してフック コードを記述します。
補足: ここで、ObjC フック コードが書かれたときに Java.perform() が含まれていないことがわかります。これは、Frida がインジェクトするときに、インジェクトされたプログラムの実行環境を自動的に作成するためです。OC の Runtime Java の jvm ですが、 jvm は独立しているため、「ライブラリ」は libart.so であるため、frida が挿入された後、この art.so をフックし、その組み込み API を Java フックに使用します。そのため、frida が挿入したものは独自の jvm 仮想マシン プロセスを作成します。しかし、アプリケーションのクラスまたはメソッドを操作したいのですが、彼のjvmに入力されます。
setImmediate(() => {
const resolver = new ApiResolver('objc');
const matches = resolver.enumerateMatches('*[ViewController *]');
matches.forEach((match)=>{
console.log(JSON.stringify(match))
})
})
3. すべてのクラス/すべてのメソッド/すべてのオーバーロードの列挙検索
実際、ApiResolver を使用してすべてのクラス、すべてのメソッド、オーバーロードを検索することはできます。列挙に一致する文字列を変更するだけで済みます。
setImmediate(() => {
const resolver = new ApiResolver('objc');
const matches = resolver.enumerateMatches('*[* *]');
matches.forEach((match)=>{
console.log(JSON.stringify(match))
})
})
ここでは、すべての API と属性の名前とアドレスが列挙され、出力されていることがわかります。
4.すべてのクラス/すべてのメソッド/すべてのオーバーロードをフックする
まず、objection を使用して次の ViewController クラスをフックします。
frida-ps -U 查看应用名
objection -g UnCrackable1 explore objection 注入应用进程
ios hooking watch class ViewController hook ViewController类
ios hooking list class_methods ViewController 列出所有方法签名
--include-parents是否包含父类方法
ios hooking watch method "*[ViewController buttonClick:]" --dump-args --dump-backtrace --dump-return
hook 相应方法
この時点で、オブジェクト内のコードの特定の場所を確認します。フックが見つかったときに出力されるプロンプト文字列をコピーできます。オブジェクト内を検索して実装方法を確認するためのセレクターです。
検索により、プロンプト文字列がメソッド watchMethod に出力されることがわかりました。特定のフック実装ロジックも上で説明しました。実際には、watchClass–>watchMethod であり、Interceptor.attach API を使用して watchMethod のメソッド アドレスをフックします。公式サイトに行ってみましょう このAPIの簡単な使い方を見てみましょう
const resolver = new ApiResolver('objc');
const matches = resolver.enumerateMatches('*[* isEqualToString:*]');
matches.forEach((match) => {
console.log(JSON.stringify(match))
Interceptor.attach(match.address,{
onEnter:function(args){
},onLeave:function(ret){
}
})
})
コードを見ると、ApiResolver を使用して文字を列挙し、JSON.stringify(match) を使用して便利なメソッドを文字列に変換して出力していることがわかります。ここでの変換の理由は、オブジェクトがこのオブジェクトにはメソッド名とアドレス属性が含まれており、Interceptor.attach の match.address をフックに使用します。onEnter は元のメソッドの実行前、onLeave はメソッドの実行後、args はメソッドの実行ですパラメータ、ret は戻り値です。自分で名前を付けることができます。
5. 解析パラメータ/コールスタック/戻り値を出力(変更)
①目的を達成するために戻り値を変更する
ここでデモのソース コードを見ると、入力文字列と非表示の文字列を比較して結果を取得していることがわかります。
したがって、 isEqualToString メソッドの戻り値を直接フックして、正しく返されるようにします。
setImmediate(() => {
console.log("hello world!r0ysue! objc =>", ObjC.available)
const resolver = new ApiResolver('objc');
const matches = resolver.enumerateMatches('*[* isEqualToString:*]');
matches.forEach((match) => {
console.log(JSON.stringify(match))
Interceptor.attach(match.address,{
onEnter:function(args){
this.change = false;
if(receiver.toString().indexOf("aaaabbbb") >= 0){
this.change = true;
console.log("need change");
}
},onLeave:function(ret){
console.log("ret=>",ret)
if(this.change){
ret.replace(new NativePointer(0x1))
}
}
})
})
})
ここでは、コード内で戻り値を true に直接変更して、戻り結果が正しくなるようにしています。
(1) ret は参照オブジェクトです。その値を変更したい場合は、上に示したように公式の replace メソッドを使用する必要があります。
(2) Interceptor.attachのパラメータと戻り値は両方ともポインタなので、数値型であってもptr()を使ってポインタ型の代入に変換する必要があります
②目的を達成するためにパラメータを変更する
setImmediate(() => {
// console.log("hello world!r0ysue! objc =>", ObjC.available)
const resolver = new ApiResolver('objc');
const matches = resolver.enumerateMatches('*[* isEqualToString:*]');
matches.forEach((match) => {
console.log(JSON.stringify(match))
Interceptor.attach(match.address,{
onEnter:function(args){
const receiver = new ObjC.Object(args[0])
console.log("receiver is =>",receiver.$className, " =>",receiver.toString());
if(receiver.toString().indexOf("aaabbb") >= 0){
const { NSString } = ObjC.classes;
var newString = NSString.stringWithString_("aaabbb");
args[2] = newString;
}
},onLeave:function(ret){
}
})
})
デモのケースを分析し、それを true にするために isEqualToString メソッドのパラメーターを変更する必要があります。ここで注意すべき点が 2 つあります。
(1) 上記の ObjC 基本文法で、OC 言語のメッセージ メカニズムが objc_msgSend (レシーバー、セレクター、arg1、arg2、...) に変換されて呼び出されると述べました。つまり、最初のパラメーターが呼び出されます。呼び出されるメソッドは呼び出し元そのものであり、2 番目のパラメーターはメソッドの名前であるセレクターであり、3 番目以降のパラメーターは実際のパラメーターです。ここでは、 const レシーバー = new ObjC を通じて呼び出し元を取得したことがわかります。 .Object(args[0]) 自体を呼び出して OC オブジェクトに変換します。OC オブジェクトに変換した後、公式 Web サイトに示されているように、frida の API を呼び出すことができます。
一連の API を使用して、このオブジェクトに基づいてクラス名、メソッドなどを取得できます。
(2) パラメータにポインタを割り当てたいので、新しい NSString を作成して割り当てる これも公式サイトにある実装です
③コールスタックを表示する
コールスタックの出力には、引き続き公式 Web サイトが提供する API を直接使用します。
console.log('CCCryptorCreate called from:\n' +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
ここではまだいくつかの解析 API が使用されています。
jsonStringfy: js オブジェクトを js の文字列に変換する
tostring : オブジェクトを文字列に変換します
objc.object: OC オブジェクトに変換
④ 置換機能
上記の変更されたパラメーターの戻り値は、関数を実行したいという事実に基づいていますが、場合によってはメソッドの実行を望まない場合もあります。その場合は、次のメソッドを使用してその実行を直接置き換えることができます。
setImmediate(() => {
const ViewController = ObjC.classes.NSString;
const buttonClick = ViewController['- buttonClick:'];
const oldImpl = buttonClick.implementation;
buttonClick.implementation = ObjC.implement(buttonClick,(handle, selector,args)=>{
console.log("handle selector args =>",handle,selector,args)
console.log(Thread.backtrace(this.AudioContext,Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n\t'))
oldImpl(handle,selector,args);
})
})
上記のコードは、あらかじめoldImplにメソッドのアドレスを保存し、ObjC.implementを使用してメソッドを置き換えていることがわかります。内部の元のメソッドを実行するかどうかは自分で実装できます
6. メモリ内のすべてのモジュール/シンボル/アドレスを列挙します。
frida-ps -Ua 查看运行的应用
objection -g UnCrackable1 explore objection注入应用进程
memory list modules 列举内存中加载的模块
memory list exports UnCrackable\ Level\ 1 列出模块中的导出符号
memory list exports Foundation
異議の実装方法を見てみましょう。列挙モジュールと列挙モジュール内のエクスポートされたシンボル。コードは、agent->generic->memory.ts にあります。
export const listModules = (): Module[] => {
return Process.enumerateModules();
};
export const listExports = (name: string): ModuleExportDetails[] | null => {
const mod: Module[] = Process.enumerateModules().filter((m) => m.name === name);
if (mod.length <= 0) {
return null;
}
return mod[0].enumerateExports();
};
実装ロジックは実際には非常に単純であることがわかります。つまり、frida の API Process.enumerateModules() を使用してモジュール オブジェクトの配列を取得し、モジュール オブジェクトに従って enumerateExports() を呼び出してエクスポートされたシンボルを取得します。 Android の c に関連するシンボル アドレスの列挙は、実際には次のようになります。同様に、コードを自分で実装します。
function listExports(name) {
//①可以在枚举模块是保存模块的name属性作为参数然后作为参数给Module.enumerateExports(name)
// var exporttArr = Module.enumerateExports(name);
//②也可以在枚举模块是保存模块对象当作参数,然后用模块对象调用name.enumerateExports()
var exporttArr = name.enumerateExports();
// var exporttArr = name.enumerateSymbols();
console.log("枚举模块==》",name.name)
for(var i = 0;i<exporttArr.length;i++){
console.log("exports: ",exporttArr[i].name,"----address:",exporttArr[i].address)
}
}
function listModules(){
var modules = Process.enumerateModules()
for(var i = 0;i<modules.length;i++){
// console.log("moulename =>",modules[i].name,"--address:",JSON.stringify(modules[i].base))
console.log("=======================================")
console.log("模块名称:",JSON.stringify(modules[i].name));
console.log("模块地址:",JSON.stringify(modules[i].base));
console.log("大小:",JSON.stringify(modules[i].size));
console.log("文件系统路径",JSON.stringify(modules[i].path));
}
return modules[0]
}
function main(){
var modulename = listModules();
console.log(modulename)
listExports(modulename)
}
setImmediate(main)
7. Brainless Automation Hook アプリケーション パッケージのすべての機能
フック アプリケーション パッケージ内のすべての関数については、インターネット上で関連する事例を検索して、その記述方法を確認できます。
function get_timestamp()
{
var today = new Date();
var timestamp = today.getFullYear() + '-' + (today.getMonth()+1) + '-' + today.getDate() + ' ' + today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds() + ":" + today.getMilliseconds();
return timestamp;
}
function hook_class_method(class_name, method_name)
{
var hook = eval('ObjC.classes.'+class_name+'["'+method_name+'"]');
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
console.log("[*] [" + get_timestamp() + " ] Detected call to: " + class_name + " -> " + method_name);
}
});
}
function run_hook_all_methods_of_classes_app_only()
{
console.log("[*] Started: Hook all methods of all app only classes");
var free = new NativeFunction(Module.findExportByName(null, 'free'), 'void', ['pointer'])
var copyClassNamesForImage = new NativeFunction(Module.findExportByName(null, 'objc_copyClassNamesForImage'), 'pointer', ['pointer', 'pointer'])
var p = Memory.alloc(Process.pointerSize)
Memory.writeUInt(p, 0)
var path = ObjC.classes.NSBundle.mainBundle().executablePath().UTF8String()
var pPath = Memory.allocUtf8String(path)
var pClasses = copyClassNamesForImage(pPath, p)
var count = Memory.readUInt(p)
var classesArray = new Array(count)
for (var i = 0; i < count; i++)
{
var pClassName = Memory.readPointer(pClasses.add(i * Process.pointerSize))
classesArray[i] = Memory.readUtf8String(pClassName)
var className = classesArray[i]
if (ObjC.classes.hasOwnProperty(className))
{
//console.log("[+] Class: " + className);
//var methods = ObjC.classes[className].$methods;
var methods = ObjC.classes[className].$ownMethods;
for (var j = 0; j < methods.length; j++)
{
try
{
var className2 = className;
var funcName2 = methods[j];
//console.log("[-] Method: " + methods[j]);
hook_class_method(className2, funcName2);
//console.log("[*] [" + get_timestamp() + "] Hooking successful: " + className2 + " -> " + funcName2);
}
catch(err)
{
console.log("[*] [" + get_timestamp() + "] Hooking Error: " + err.message);
}
}
}
}
free(pClasses)
console.log("[*] Completed: Hook all methods of all app only classes");
}
function hook_all_methods_of_classes_app_only()
{
setImmediate(run_hook_all_methods_of_classes_app_only)
}
hook_all_methods_of_classes_app_only()
スクリプトの主な実装ロジックはおおよそ次のとおりであることがわかります。
① run_hook_all_methods_of_classes_app_only メソッドですべてのクラスを取得します 具体的な取得方法は、Objective-C ランタイムのプライベート API である copyClassNamesForImage を使用して、現在のアプリに定義されているすべてのクラス名を取得し、メモリ配列に保存します。
② 次に、クラスのメソッドを走査し、hasOwnProperty の js API を使用して、クラスが現在のアプリに属しているかどうかを判断し、属している場合はクラス内のメソッドを走査します。
③各メソッドの実際のフックにはhook_class_methodメソッドを使用します。
8. オブジェクトメモリローミングによるすべてのオブジェクトの検索
まず、objection を使用して ViewController オブジェクトを検索します。
ios heap search instances ViewController
次に、異議申し立てコードに移動して、その特定の実装を確認します。場所は、agent->src->ios->heap.ts です。
const enumerateInstances = (clazz: string): ObjC.Object[] => {
if (!ObjC.classes.hasOwnProperty(clazz)) {
c.log(`Unknown Objective-C class: ${c.redBright(clazz)}`);
return [];
}
const specifier: ObjC.DetailedChooseSpecifier = {
class: ObjC.classes[clazz],
subclasses: true, // don't skip subclasses
};
return ObjC.chooseSync(specifier);
};
export const getInstances = (clazz: string): IHeapObject[] => {
c.log(`${c.blackBright(`Enumerating live instances of`)} ${c.greenBright(clazz)}...`);
return enumerateInstances(clazz).map((instance): IHeapObject => {
try {
return {
className: instance.$className,
handle: instance.handle.toString(),
ivars: instance.$ivars,
kind: instance.$kind,
methods: instance.$ownMethods,
superClass: instance.$superClass.$className,
};
} catch (err) {
c.log(`Warning: ${c.yellowBright((err as Error).message)}`);
}
});
};
最終的には、frida が提供する API である ObjC.chooseSync が実際に呼び出され、オブジェクトを検索していることがわかります。
ここで ObjC.chooseSync が使用される理由は、ObjC.choose
これは非同期 API ですがObjC.chooseSync
同期 API であるためです。多数のオブジェクトを迅速に列挙する必要があり、一部のオブジェクトが欠落する可能性があることを許容できる場合は、これを使用できますObjC.choose
。ただし、すべてのオブジェクトが列挙されていることを保証する必要があり、速度が遅くても許容される場合は、呼び出しの方がObjC.chooseSync
適しています。
9.ObjC.choose はすべてのクラス出力プロパティを列挙します
ObjC.choose を使用して実際のインスタンスを列挙すると、このインスタンスのプロパティを直接出力できます。Frida 公式 Web サイトでもこれらのメソッドが提供されています
実装コードは次のとおりです。
setImmediate(() => {
if(ObjC.available){
const specifier = {
class: ObjC.classes['ViewController'],
subclasses: true,
};
ObjC.choose(specifier,{
onMatch:function(ins){
console.log("found ins =>",ins)
console.log("ivars =>",ins.$ivars["_theLabel"].toString())
console.log("methods =>",ins.$ownMethods)
},onComplete(){
console.log("Search Completed")
}
})
}
})
オブジェクト ins を列挙した後、その $ivars を直接呼び出して属性オブジェクトを取得し、toString() を使用してそれを文字列に変換して出力できることがわかります。
10. オブジェクトメソッドを積極的に呼び出してアルゴリズムの実行結果を取得する
積極的に異議を申し立てる方法を見てみましょう
ios heap search instances ViewController
ios heap print methods 0x15dd08220
ios heap execute 0x15dd08220 theLabel --return-string
これらのメソッドはすべてオブジェクトであることがわかりますが、デモでは do_it メソッドは c で実装された関数であり、オブジェクトはありません。このときの呼び出しコードは次のとおりです。
function callcmethod(name){
var doit = new NativeFunction(Module.findExportByName(null, name),'pointer', [])
console.log("doit result => ",doit().readCString())
}
function main(){
// var modulename = listModules();
// console.log(modulename)
// listExports(modulename)
callcmethod('do_it')
}
setImmediate(main)
具体的には、次のような手順で説明できます。
①以前に作成した列挙モジュールとモジュール内の文字リストを呼び出して、文字をエクスポートするメソッドを表示します。
② Module.findExportByName(null, name) を呼び出して 2 番目のパラメータとして文字をエクスポートします。最初のパラメータは関数アドレスを取得するモジュール名です
③ frida の API NativeFunction() を呼び出します。API は、関数アドレス、ネイティブ関数の戻り値の型、ネイティブ関数のパラメーターの型配列の 3 つのパラメーターを受け取り、js 関数オブジェクトを生成します。
④上記で生成したjs関数オブジェクトを実行すると元の関数が呼び出されます
11 リモート バッチ オフライン用の RPC ブラック ボックス呼び出しアルゴリズムを構成する
jsコードは次のとおりです。
function callSecretFunc(){
return new NativeFunction(Module.findExportByName(null, 'do_it'),'pointer', [])().readCString()
}
rpc.exports={
callSecretFunction:callSecretFunc
}
手動フック挿入プロセス呼び出しテスト
実行が成功したことは、エクスポートが成功したことを示します
pyコードは次のとおりです。
import time
import frida
def my_message_handler(message, payload):
print(message)
print(payload)
device = frida.get_usb_device()
#frida.get_usb_device() 相当于手工注入式的 -U参数 通过USB获取设备
#device.get_frontmost_application() 相当于-F 获取当前页面进程
#创建一个session对象准备注入
session = device.attach(device.get_frontmost_application().pid)
#读取js文件并通过session开始注入
with open("ios2.js") as f :
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
command = ""
while 1 ==1 :
command = input("Enter Command:")
if command == "1":
break
elif command == "2":
print(script.exports.callSecretFunctionon())
次のように:
import time
import frida
def my_message_handler(message, payload):
print(message)
print(payload)
device = frida.get_usb_device()
#frida.get_usb_device() 相当于手工注入式的 -U参数 通过USB获取设备
#device.get_frontmost_application() 相当于-F 获取当前页面进程
#创建一个session对象准备注入
session = device.attach(device.get_frontmost_application().pid)
#读取js文件并通过session开始注入
with open("ios2.js") as f :
script = session.create_script(f.read())
script.on("message", my_message_handler)
script.load()
command = ""
while 1 ==1 :
command = input("Enter Command:")
if command == "1":
break
elif command == "2":
print(script.exports.callSecretFunctionon())
【外部リンク画像転送...(img-wIWHhJwN-1690441675043)】
以上がこの記事の全内容ですが、次の記事では引き続き frida が r0tracer に ios トレース機能を追加する方法について学びます。