-
-
[原创] Frida hook Java/Native与init_array 自吐最终方案
-
2021-5-10 14:39 8758
-
1. 前言
题目来自看雪2020春季班10月份第一题和第三题。这两个题目本身难度不高,但是确实考察对源码的理解,同时也不失为一个frida
的案例。
2. HTTPUrlConnection 信息自吐
2.1 题目描述
在HttpURLConnection
的开发过程中,设置参数时会用到以下方法。分析并hook
上这些方法,写出一两个参数的自吐。
1 2 3 4 5 6 7 8 | / / URLConnection httpUrlConnection.setDoOutput(true); httpUrlConnection.setDoInput(true); HttpUrlConnection.setUseCaches(false); httpUrlConnection.setRequestProperty( "Content-type" , "application/x-java-serialized-object" ); httpUrlConnection.connect(); / / HttpURLConnection httpUrlConnection.setRequestMethod( "POST" ); |
2.2 分析
具体在测试时可以自己写一个简单的demo
并在APP中显示调用如下函数
测试app功能正常后,用Objection
直接把以下两个类全都hook
上:java.net.URLConnection
以及java.net.HttpURLConnection
结果如图发现反复调用这几个函数,其他函数都没有调用:
怀疑底层是否真实使用这些类实现的,找到对应源码,发现其实HttpURLConnection
类也是一个抽象类,就是说在Java
中实际上是其Impl
类去实现:
使用WallBreaker
去搜索HttpURLConnection
会发现确实有一个类com.android.okhttp.internal.huc.HttpURLConnectionImpl
去源码里搜了搜发现,确实这个类实现了这个函数
晕死,结果这个类也是okHttp做的底层???先hook一下这个类
会发现这个实现类确实实现了之前没有hook到的三个函数
那么最终脚本就直接写就是了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | function hook_java(){ Java.perform(function(){ / / httpUrlConnection.setDoOutput(true); / / httpUrlConnection.setDoInput(true); / / HttpUrlConnection.setUseCaches(false); var URLConnection = Java.use( "java.net.URLConnection" ) URLConnection.setDoOutput.implementation = function(isOutput){ console.log( "URLConnection.setDoOutput : " ,isOutput); return this.setDoOutput(isOutput); } URLConnection.setDoInput.implementation = function(isInput){ console.log( "URLConnection.setDoInput : " ,isInput); return this.setDoInput(isInput); } URLConnection.setUseCaches.implementation = function(isUseCaches){ console.log( "URLConnection.setUseCaches : " ,isUseCaches); return this.setUseCaches(isUseCaches); } / / com.android.okhttp.internal.huc.HttpURLConnectionImpl var HttpURLConnectionImpl = Java.use( "com.android.okhttp.internal.huc.HttpURLConnectionImpl" ); HttpURLConnectionImpl.setRequestProperty.implementation = function(name,value){ console.log( "HttpURLConnectionImpl.setRequestProperty => " ,name, ": " ,value); return this.setRequestProperty(name,value); } HttpURLConnectionImpl.setRequestMethod.implementation = function( type ){ console.log( "HttpURLConnectionImpl.setRequestMethod : " , type ); return this.setRequestMethod( type ); } HttpURLConnectionImpl.connect.implementation = function(){ console.log( "HttpURLConnectionImpl.connect" ); return this.connect(); } }); } function main(){ hook_java(); } setImmediate(main); |
最终效果如下
3. so构造函数自吐
3.1 题目描述
在课时⑨中给出的init array的自吐 hook_linker,32位版本解决了,请分析64位版本,尝试给出解决方案并解决。
3.2 分析
以8.1为例,查看源码后会发现
dlopen
调用过程中最终是
目录/bionic/linker/linker_soinfo.cpp
1 2 3 4 | soinfo::call_constructors() call_function( "DT_INIT" , init_func_, get_realpath()); call_array( "DT_INIT_ARRAY" , init_array_, init_array_count_, false, get_realpath()); - - - - - - >循环调用了 call_function( "function" , functions[i], realpath); |
这个函数是模版函数,把linker拷到IDA上一看,会发现这个函数是唯一的,且参数是4个,同时会发现64位的linker中是没有call_function
这个函数的,这个函数被优化成了代码片段,插进call_constructors
函数和call_array
函数中了。
最终模仿这个函数直接写一个js版本就行了关键的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | function hook_init_array() { / / console.log( "hook_constructor" ,Process.pointerSize); if (Process.pointerSize = = 4 ) { var linker = Process.findModuleByName( "linker" ); } else if (Process.pointerSize = = 8 ) { var linker = Process.findModuleByName( "linker64" ); } var addr_call_array = null; if (linker) { var symbols = linker.enumerateSymbols(); for (var i = 0 ; i < symbols.length; i + + ) { var name = symbols[i].name; if (name.indexOf( "call_array" ) > = 0 ) { addr_call_array = symbols[i].address; } } } if (addr_call_array) { Interceptor.attach(addr_call_array, { onEnter: function (args) { this. type = ptr(args[ 0 ]).readCString(); / / console.log(this. type ,args[ 1 ],args[ 2 ],args[ 3 ]) if (this. type = = "DT_INIT_ARRAY" ) { this.count = args[ 2 ]; / / this.addrArray = new Array(this.count); this.path = ptr(args[ 3 ]).readCString(); var strs = new Array(); / / 定义一数组 strs = this.path.split( "/" ); / / 字符分割 this.filename = strs.pop(); if (this.count > 0 ){ console.log( "path : " , this.path); console.log( "filename : " , this.filename); } for (var i = 0 ; i < this.count; i + + ) { console.log( "offset : init_array[" + i + "] = " , ptr(args[ 1 ]).add(Process.pointerSize * i).readPointer().sub(Module.findBaseAddress(this.filename))); / / 插入hook init_array代码 } } }, onLeave: function (retval) { } }); } } |
但是这样只是hook
了so
中init_array
节中函数,还存在.init_proc
的构造函数并未hook
,和32位一样本来是继续去hook
call_function
函数,在脱出/system/lib64/libart.so
后,发现call_function
这个symbol
无法找到,观察下图发现这个函数被inline
了。
但是仔细观察.init_proc
和.init_array
函数调用前后,都会有一个log的判断,直接去hook
这个_dl_async_safe_format_log
函数吧
但是首先得_dl_g_ld_debug_verbosity
这个值大于等于2这个函数才会执行,那么先使用frida
去这个变量的地址,然后修改这个变量的值使其达到_dl_async_safe_format_log
函数会执行的条件即可。
最终frida
关键代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | function hook_constructor() { if (Process.pointerSize = = 4 ) { var linker = Process.findModuleByName( "linker" ); } else { var linker = Process.findModuleByName( "linker64" ); } var addr_call_function = null; var addr_g_ld_debug_verbosity = null; var addr_async_safe_format_log = null; if (linker) { / / console.log( "found linker" ); var symbols = linker.enumerateSymbols(); for (var i = 0 ; i < symbols.length; i + + ) { var name = symbols[i].name; if (name.indexOf( "call_function" ) > = 0 ){ addr_call_function = symbols[i].address; / / console.log( "call_function" ,JSON.stringify(symbols[i])); } else if (name.indexOf( "g_ld_debug_verbosity" ) > = 0 ){ addr_g_ld_debug_verbosity = symbols[i].address; ptr(addr_g_ld_debug_verbosity).writeInt( 2 ); } else if (name.indexOf( "async_safe_format_log" ) > = 0 && name.indexOf( 'va_list' ) < 0 ){ / / console.log( "async_safe_format_log" ,JSON.stringify(symbols[i])); addr_async_safe_format_log = symbols[i].address; } } } if (addr_async_safe_format_log){ Interceptor.attach(addr_async_safe_format_log,{ onEnter: function(args){ this.log_level = args[ 0 ]; this.tag = ptr(args[ 1 ]).readCString() this.fmt = ptr(args[ 2 ]).readCString() if (this.fmt.indexOf( "c-tor" ) > = 0 && this.fmt.indexOf( 'Done' ) < 0 ){ this.function_type = ptr(args[ 3 ]).readCString(), / / func_type this.so_path = ptr(args[ 5 ]).readCString(); var strs = new Array(); / / 定义一数组 strs = this.so_path.split( "/" ); / / 字符分割 this.so_name = strs.pop(); this.func_offset = ptr(args[ 4 ]).sub(Module.findBaseAddress(this.so_name)) console.log( "func_type:" , this.function_type, '\nso_name:' ,this.so_name, '\nso_path:' ,this.so_path, '\nfunc_offset:' ,this.func_offset ); / / hook代码在这加 } }, onLeave: function(retval){ } }) } } |
最终效果如下,
64位:
32位效果:
同样的完整代码已经上传github
,看这里
4. 后记
一切答案都在源码中。
看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~