roger 发表于 2020-8-1 19:25:23

举杯邀Frida,对影成三题


  0x00 序言

  在7月建党节之际做了几道作业,回想起学习中国共产党18年革命斗争的历史经验时书本中指出这样一句话:“统一战线、武装斗争、党的建设,是中国共产党在中国革命中战胜敌人的三大法宝”。
  其实,这句话到什么时刻都是具有一定的借鉴意义。那我们开门见山,接下来放的三道题的解题思路就是靠的三大法宝的“变形”。

  0x01 第一题:统一“Frida和Objection”的战线
  要求是使用Frida,或者新建工程跑,但我个人是选择了Frida。


http://pic3.zhimg.com/v2-98d6ac5e1f01108adb7fdb1f534eae2c_r.jpg
  如果成功,flag则会输出出来,看一下流程
  首先str是username 和 password的转字符串的拼接
String str = MainActivity.this.username_et.getText().toString() + MainActivity.this.password_et.getText().toString();  经过VVVVV.VVVV判断为true之后就会告诉我们“恭喜您,成功了!flag is+str”
  那关键的逻辑就在于这个VVVVV.VVVV里面我们去看一下

http://pic4.zhimg.com/v2-d5e04d841fc054be7d56b0f0e8cc46ab_r.jpg
  由于看到VVVV是静态函数,我们选择使用Java.use封装

  function main(){
Java.perform(function() {
Java.use("com.kanxue.pediy1.VVVVV").VVVV.implementation = function(x,y){
var result = this.VVVV(x,y);
console.log("x,y,result:",x,y,result);
return result;
}
})
}
setImmediate(main)
  可以看到成功hook住了我们的输入

http://pic1.zhimg.com/v2-03e923caa9e65cc4512270832adeed0e_r.jpg
  接下来我们不要着急写Frida的代码(为什么?因为反编译的结果不能完全相信),既然统一了战线,那先让我们的战友Objection去探探敌情,开远程比较方便:

  ./fri12820x64 -l 0.0.0.0:8888
objection -N -h 192.168.1.103 -p 8888 -g com.kanxue.pediy1 explore
android hooking watch class_method com.kanxue.pediy1.VVVVV.VVVV --dump-args --dump-backtrace --dump-return

http://pic1.zhimg.com/v2-efe199083aac25f97927b79c4aa36899_r.jpg

  android hooking watch class_method com.kanxue.pediy1.VVVVV.eeeee --dump-args --dump-backtrace --dump-return
  由于再看了一下前提条件,有个输入长度username+password必须长度为5,因此我们规定username:123 password:45

http://pic2.zhimg.com/v2-cfd2e7e668d90583b04f3622516b3bb0_r.jpg
  可以看到VVVV调用了eeeee,此时验证了Jadx反编译的内容确实没错,我们可以放心大胆地使用Frida。
  然后输入是12345,返回值是object object
  由于返回的是object不便于观察,因此我们使用陈总的r0gson,并用frida打印:

  function main(){
Java.perform(function() {
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use("com.r0ysue.gson.Gson");
Java.use("com.kanxue.pediy1.VVVVV").eeeee.implementation = function(x){
var result = this.eeeee(x);
console.log("x,result:",x,gson.$new().toJson(result));
return result;
}
})
}
setImmediate(main)

http://pic2.zhimg.com/v2-ac26bf0494b0dca5e0fdc17dd4e8251f_r.jpg
  噢明白了,那就是需要这个返回值要和”6f452303f18605510aac694b0f5736beebf110bf“的getBytes结果相等才行~
  我们去Android Stutio新建一个工程后尝试一下打印getBytes()的结果

  String str = "6f452303f18605510aac694b0f5736beebf110bf";
byte[] byt = str.getBytes();
for (byte b : byt) {
System.out.println(b);
}
  #结果是:
  那么我们就可以重新写如下脚本爆破“敌方大本营”:

  function firethehome(){
Java.perform(function(){
var VVVVV_Class = Java.use("com.kanxue.pediy1.VVVVV")
console.log("VVVVV_Class:", VVVVV_Class)
VVVVV_Class.eeeee.implementation=function(x){
var result = this.eeeee(x);
console.log("VVVVV.eeeee is hook! x ,result",x,JSON.stringify(result));
return result;
}
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString);
var pSign = Java.use("java.lang.String").$new("6f452303f18605510aac694b0f5736beebf110bf").getBytes();
console.log( ByteString.of(pSign).hex());
// 爆破5位
for(var i = 9999;i<100000;i++){
console.log("i="+i);
var v =Java.use("java.lang.String").$new(String(i));
var vSign = VVVVV_Class.eeeee(v);
console.log("vSign:",ByteString.of(vSign).hex());
if(ByteString.of(vSign).hex() == ByteString.of(pSign).hex()){
console.log("i="+i);
break;
}
}
})
}
setImmediate(firethehome)
  可以发现打印了两次,那么这就是flag


http://pic3.zhimg.com/v2-ea89d4ebb44b8773a432046aab8baa92_r.jpg

http://pic4.zhimg.com/v2-87ac8cf8ef834f76c6d7ca53920b9300_b.jpg
  当然,如果不想要flag,也可以这么玩

  function main(){
Java.perform(function() {
Java.openClassFile("/data/local/tmp/r0gson.dex").load();
const gson = Java.use("com.r0ysue.gson.Gson");
Java.use("com.kanxue.pediy1.VVVVV").eeeee.implementation = function(x){
var result = this.eeeee(x);
console.log("x,result:",x,gson.$new().toJson(result));
var v = result;
v = Java.array('byte',)
console.log(gson.$new().toJson(v));
return v;
}
})
}
setImmediate(main)
  就是无论输入什么都会判断你是对的~


http://pic1.zhimg.com/v2-c2a3e4f37e47b3eb627e8df8b3b272c7_r.jpg

  到了第二题,敌人发现了我们火力凶猛,想到使用动态加载Dex进行了初步的保护,别急,我们有第二法宝。


  0x02 第二题:面对动态加载保护,我们使用枚举ClassLoader来武装“Frida”斗争
  题目要求是“没有要求,做出来即可”。
  那我们回到Jadx,我们可以看MainActivity里面存在着动态加载Dex的操作


http://pic3.zhimg.com/v2-e72b0d502d96dc28bf4f333c4d4f0615_r.jpg
  
str是username和password的字符串拼接,result是最后拿到flag的关键

http://pic3.zhimg.com/v2-f53eca100ef5a0e685f181b71b7b6b90_r.jpg
  result刚开始赋值成false,经过try的处理,可以得知是调用了动态加载的classes.dex中的VVVVV类下的VVVV函数


  function main(){
Java.perform(function(){
Java.enumerateClassLoaders({
onMatch:function(loader){
try{
if(loader.findClass("com.kanxue.pediy1.VVVVV")){
console.log("success found com.kanxue.pediy1.VVVVV!",loader);
Java.classFactory.loader = loader;//替换ClassLoader为DexClassLoader
}
}catch(e){
console.log("found error!",e);
}
},onComplete(){console.log("enum completed!")}
})
Java.use("com.kanxue.pediy1.VVVVV").VVVV("12345");
});
}
function hookDex(){
Java.perform(function(){
Java.choose("com.kanxue.pediy1.MainActivity",{
onMatch:function(instance){
console.log("found instance:",instance);
console.log("invoke loadDexClass!",instance.loadDexClass());
},onComplete(){}
})
Java.choose("dalvik.system.DexClassLoader",{
onMatch:function(loader){
Java.classFactory.loader = loader;
console.log("the Loader:", Java.classFactory.loader)
},onComplete:function(){}
})
var VVVVV_Class = Java.use("com.kanxue.pediy1.VVVVV")
console.log("VVVVV_Class:", VVVVV_Class)
VVVVV_Class.eeeee.implementation=function(x){
var result = this.eeeee(x);
console.log("VVVVV.eeeee is hook!",result);
return result;
}
})
}
#使用两步函数调用法
frida -Ucom.kanxue.pediy1 -l homework2.js
hookDex()
main()  发现已经成功拿到动态加载的DexClassLoader,并替换我们Frida的Java.ClassFactory,最后成功走到VVVVV类中hook住eeeee函数

http://pic1.zhimg.com/v2-88821f2c5c99b43df9ef5cac1215b185_r.jpg
  OK,敌方的小尾巴已经被我们抓住,开始爆破,主要修改hookDex()

  function hookDex(){
Java.perform(function(){
Java.choose("com.kanxue.pediy1.MainActivity",{
onMatch:function(instance){
console.log("found instance:",instance);
console.log("invoke loadDexClass!",instance.loadDexClass());
},onComplete(){}
})
Java.choose("dalvik.system.DexClassLoader",{
onMatch:function(loader){
Java.classFactory.loader = loader;
console.log("the Loader:", Java.classFactory.loader)
},onComplete:function(){}
})
var VVVVV_Class = Java.use("com.kanxue.pediy1.VVVVV")
console.log("VVVVV_Class:", VVVVV_Class)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString);
var pSign = Java.use("java.lang.String").$new("7c133979c8fc45943815792c0288300687cf0a16").getBytes();
console.log( ByteString.of(pSign).hex());
for(var i = 9999;i<100000;i++){
console.log("i="+i);
var v =Java.use("java.lang.String").$new(String(i));
var vSign = VVVVV_Class.eeeee(v);
console.log("vSign:",ByteString.of(vSign).hex());
if(ByteString.of(vSign).hex() == ByteString.of(pSign).hex()){
console.log("i="+i);
break;
}
}
})
}

http://pic2.zhimg.com/v2-afce7599d4d125fe4c4e4c18c58b474f_r.jpg
  OK,回过头去验证一下:


http://picb.zhimg.com/v2-7f90300f3e4005a40d037bb219fbf75b_r.jpg
  竟然没有成功,那么,已经在java层分析没有大问题的前提下,我们要考虑去Native层看看有没有蹊跷。
  目标是libnative-lib.so中的StringFromJNI函数【因为动态加载dex的时候调用VVVV方法时用到了stringFromJNI的返回值做了参数】

http://pic1.zhimg.com/v2-34184f1162aa60829cc5361aa8a2a7b0_r.jpg
  去IDA中查看:

http://picb.zhimg.com/v2-eda65c2914372706cd42850b01c48d13_r.jpg
  一看,果然是这里,也就是最后传出来的参数也就是要当作VVVV参数的input【上图中的result】是+1后的结果。
  因此我们逆向时需要减一,因此flag猜想是66999-1=66998

http://pic2.zhimg.com/v2-caa7230d3caa909ddaa3079c9c1e6925_r.jpg

  到了第三题,敌方已然是强弩之末,使出了最后的“杀手锏”保护措施——杀Frida进程。
  莫慌,我们也有最后的第三法宝。


  0x03 第三题:面对反Frida保护屡试不爽的kill进程,采用Native Hook来强化建设我们的"Frida"

http://pic4.zhimg.com/v2-d8aa56913698428cb5985905edb0d744_r.jpg
  发现了检测frida的循环函数,跟进去查看,


http://pic4.zhimg.com/v2-bb0c69323ce461fe61c778561e8924ad_r.jpg
  因为strcmp此时若等于0(也就是第一个参数也为REJECT),会调用kill函数杀死进程

http://pic2.zhimg.com/v2-579ce5910394c6f42c0d15fcbb7eb745_r.jpg
  有攻就有防,既然在so中出现了strcmp这么可爱的经典Native函数,关键它还是影响kill()的判断条件,我们就加以利用,四两拨千斤。
  那我们可以写如下脚本:

  function hookstrcmp(){
Java.perform(function() {
console.log("I am a Hook function");
var strcmp = Module.findExportByName("libc.so","strcmp");//这里发现无论“libnative-lib.so”还是“libc.so”都是一样的地址
console.log("find strcmp:",strcmp);
Interceptor.attach(strcmp, {
onEnter: function (args) {
//hook住后打印strcmp的第一个参数和第二个参数的内容
if(ptr(args).readCString().indexOf("REJECT")>=0){
console.log("[*] strcmp (" + ptr(args).readCString() + "," + ptr(args).readCString()+")");
this.isREJECT = true;
}

},onLeave:function(retval){
if(this.isREJECT){
console.log("the REJECT's result :",retval);
}
}
});
})
}
  而且这里设计的初衷是不影响其他正常的strcmp操作,因此我使用this来设置一个可以从传参到返回值都能用的一个标志(this.isREJECT)。

http://pic4.zhimg.com/v2-a0c6ebed6d8ac278086cd037e7e1b308_r.jpg
  发现打印的都是第二个参数带REJECT的
  好了,接下来为了不让这一系列REJECT为第二参数的strcmp判断为0,我们需要replace这个函数的返回值,打算让它一直返回0x1

  function hookstrcmp(){
Java.perform(function() {
console.log("I am a Hook function");
var strcmp = Module.findExportByName("libc.so","strcmp");//这里发现无论“libnative-lib.so”还是“libc.so”都是一样的地址
console.log("find strcmp:",strcmp);
Interceptor.attach(strcmp, {
onEnter: function (args) {
//hook住后打印strcmp的第一个参数和第二个参数的内容
if(ptr(args).readCString().indexOf("REJECT")>=0){
console.log("[*] strcmp (" + ptr(args).readCString() + "," + ptr(args).readCString()+")");
this.isREJECT = true;
}
},onLeave:function(retval){
if(this.isREJECT){
retval.replace(0x1);
console.log("the REJECT's result :",retval);
}
}
});
})
}

http://pic2.zhimg.com/v2-dc7092b95aa52a9f22202001c6b52a5e_r.jpg
  这时候可以发现即使判断到接收信息是REJECT,我们的程序也不退出了~
  Fire!!!

  function hookDex(){
Java.perform(function(){
Java.choose("com.kanxue.pediy1.MainActivity",{
onMatch:function(instance){
console.log("found instance:",instance);
console.log("invoke loadDexClass!",instance.loadDexClass());
},onComplete(){}
})
Java.choose("dalvik.system.DexClassLoader",{
onMatch:function(loader){
Java.classFactory.loader = loader;
console.log("the Loader:", Java.classFactory.loader)
},onComplete:function(){}
})
var VVVVV_Class = Java.use("com.kanxue.pediy1.VVVVV")
console.log("VVVVV_Class:", VVVVV_Class)
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
console.log(ByteString);
var pSign = Java.use("java.lang.String").$new("971b82e071392d8293e57b39fc5056c731517d4e").getBytes();
console.log( ByteString.of(pSign).hex());
//爆破
for(var i = 9999;i<100000;i++){
console.log("i="+i);
var v =Java.use("java.lang.String").$new(String(i));
var vSign = VVVVV_Class.eeeee(v);
console.log("vSign:",ByteString.of(vSign).hex());
if(ByteString.of(vSign).hex() == ByteString.of(pSign).hex()){
console.log("i="+i);
break;
}
}
})
}
function hookstrcmp(){
Java.perform(function() {
console.log("I am a Hook function");
var strcmp = Module.findExportByName("libc.so","strcmp");//这里发现无论“libnative-lib.so”还是“libc.so”都是一样的地址
console.log("find strcmp:",strcmp);
//Hook strcmp
Interceptor.attach(strcmp, {
onEnter: function (args) {
//hook住后打印strcmp的第一个参数和第二个参数的内容
if(ptr(args).readCString().indexOf("REJECT")>=0){
console.log("[*] strcmp (" + ptr(args).readCString() + "," + ptr(args).readCString()+")");
this.isREJECT = true;
}
},onLeave:function(retval){
if(this.isREJECT){
retval.replace(0x1);
console.log("the REJECT's result :",retval);
}
}
});
})
}
setImmediate(hookstrcmp);

http://pic2.zhimg.com/v2-044e5759c7cbfb4e6308c0d9bb009c8c_r.jpg
  可以发现在99999处i出现了两次,通过再次比较pSign发现两者一致,所以答案是99999

http://pic3.zhimg.com/v2-bf25562cf1807c2424324b36e0900179_r.jpg

  最后测试的时候发现还是和第二题一样做了+1的操作,那我们还是99999-1=99998
  flag即为99998

http://pic4.zhimg.com/v2-e26d5a573dd525069e005d0e02d70597_r.jpg

  0x04 结语
  党的三大法宝在现今仍有很强的教育意义,其实关键就看我们如何看待前人总结的宝贵经验。
  当然这是题外话,论坛中还有很多大佬写了不同解法,建议一同食用,取长补短,共同进步~
  祝7月顺利!
**** Hidden Message *****

页: [1]
查看完整版本: 举杯邀Frida,对影成三题