frida0x1
先进行反编译,找到 com.ad2001.frida0x1.MainActivity 方法了
解读一下代码
有一个生成100以内的随机数的方法,还有个一个check方法
内涵一个判断,先hook产生随机数的方法,固定这个i 的值,然后输入正确的i2值
先hook一下 check 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Java .perform (function ( ) { let MainActivity = Java .use ("com.ad2001.frida0x1.MainActivity" ); MainActivity ["get_random" ].implementation = function ( ) { console .log (`MainActivity.get_random is called` ); return 1 ; }; MainActivity ["check" ].implementation = function (i, i2 ) { console .log (`MainActivity.check is called: i=${i} , i2=${i2} ` ); this ["check" ](i, i2); }; });
看输出 i 的值为17,返回1之后还是17,说明这个方法没有执行,需要重启hook,问题不大,现在输入38 (17*2+4)就完成这个了,得到flag
猜测正确,每次重启会随机一个数,把这个数hook了,就固定了密码
再来
frida0x2 反编译 AES加密突脸了
key是 “HILLBILLWILLBINN” .getByte() 这个字节数组
iv值是0字节数组,是个空数组
2 表示解密方法
解密的数据是 “q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=” 进行base64解码之后
难说,直接薄纱了
HILLBILLWILLBINN 用 hex编码一下,iv写32位0,解了
再用JS实现一下
Base64解密之后是原始的数据格式,在网站上使用Raw来接收,在cryptoJS中选择 WordArray 这个类似于字节数组的格式来接收参数,解密成功,得到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const cryptoJS = require ('crypto-js' );var key = cryptoJS.enc .Hex .parse (Buffer .from ("HILLBILLWILLBINN" , "utf8" ).toString ("hex" ))var iv = cryptoJS.enc .Hex .parse ("00000000000000000000000000000000" )var decodeBase64 = Buffer .from ("q7mBQegjhpfIAr0OgfLvH0t/D0Xi0ieG0vd+8ZVW+b4=" , "base64" )var encrypted = cryptoJS.lib .WordArray .create (decodeBase64)var cfg = { iv : iv, mode : cryptoJS.mode .CBC , padding : cryptoJS.pad .Pkcs7 } var decrypted = cryptoJS.AES .decrypt ({ciphertext : encrypted}, key, cfg);console .log (decrypted.toString (cryptoJS.enc .Utf8 ));
使用frida的主动调用来实现,这个才是正题
因为这个方法找不到实现,现在主动调用这个方法
1 2 3 4 5 Java .perform (function ( ) { var a = Java .use ("com.ad2001.frida0x2.MainActivity" ); a.get_flag (4919 ) });
这种主动调用的方式很神奇啊,只有这样可以
我试了其他的方法
这个方法不能够使用 -f 的方式,要使用 -F 直接调用
在程序启动之后再主动调用方法,防止调用完,程序启动重新覆盖显示
1 2 3 4 Java .perform (function ( ) { var MainActivity = Java .use ("com.ad2001.frida0x2.MainActivity" ); MainActivity .get_flag (4919 ); });
1 2 3 4 5 6 7 8 9 10 Java .perform (function ( ) { Java .choose ("com.ad2001.frida0x2.MainActivity" , { onMatch : function (instance ) { var a = 4919 ; instance.get_flag (a); }, onComplete : function ( ) {} }) });
frida0x3 看代码,一个点击方法,判断code是否等于512,但是increase方法没有被调用,说明光点是肯定不行的,hook将数值改成512,然后通过解密方法获取flag
主动更改数值之后,一点就出
1 2 3 4 Java .perform (function ( ) { let Checker = Java .use ("com.ad2001.frida0x3.Checker" ); Checker .code .value = 512 ; })
还可以看源码去网站解密
glass123 进行hex解密
没有添加iv值,更改位ECB模式,base64解码之后的数据类型为Raw
good
就不用JS复现了,需要其他的库,流程和之前的AES解密差不多
frida0x4
主动调用这个方法,这里踩了坑了,我试了两种主动调用的方法均没有输出
第一、Java.use指定已经加载的类,Check.get_flag(1337);
这个形式只能主动调用静态方法,没有实例化的对象需要先实例化对象
第二、使用 Java.choose
时,确保该类在当前上下文中有实例存在。如果当前没有可用的实例,onMatch
函数将不会被调用,从而没有输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Java .perform (function ( ) { var MyClass = Java .use ('com.ad2001.frida0x4.Check' ); var myClassInstance = MyClass .$new(); var result = myClassInstance.get_flag (1337 ); console .log (result.toString ()); })
先创建实例再主动调用,拿下flag
FRIDA{XORED_INSTANCE}
打算用JS复现一下,先解读代码,创建一个byte数组,长度为44
循环数组,对 I]FKNtW@]JKPFA\\[NALJr
这个字符串转字节数组的每一个元素和15(1111)进行异或操作,得到结果再使用hex解码返回字符串
异或,相同为0 ,不同为1
1 2 3 4 5 6 7 8 9 10 11 12 function decodeString (input ) { const hexBytes = new Array (input.length ); for (let i = 0 ; i < input.length ; i++) { hexBytes[i] = String .fromCharCode (input.charCodeAt (i) ^ 15 ); } return hexBytes.join ('' ); } const input = "I]FKNtW@]JKPFA\\[NALJr" const output = decodeString (input);console .log (output);
frida0x5 反编译一下,真难绷
又是一个主动调用的,直接解了算了
JS
跟frida0x2的解密代码一模一样,就改一下key和密文
frida
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java .perform (function ( ) { Java .choose ("com.ad2001.frida0x5.MainActivity" , { onMatch : function (instance ) { var a = 1337 ; instance.flag (a); console .log ("success" ); }, onComplete : function ( ) {} }) })
问题 这个时候我产生了一个问题,为什么同样是主动调用,在apk4中就不能使用 java.choose 要使用 $new(),而apk5中就不能使用 $new() ,而是使用Java.choose 呢
这个和是否在MainActivity类中和是否是静态方法有关,比如apk4,要主动调用的方法是非MainActivity类的非静态方法,这样的方法在Java中要调用还得new一下呢
1 2 Check ch = new Check ();String flag = ch.get_flag(1337 );
那么就不难想象,frida的调用方法了
1 2 3 var Check = Java .use ("com.ad2001.frida0x4.Check" );var ch = Check .$new();var flag = ch.get_flag (1337 );
使用Java.choose方法来调用的话,没有这个实例化对象的,因为这个类就完全是没有调用加载的类,没有任何一个地方调用他了,根本就没有创建,不存在这个实例,既不输出也不执行。
而面对MainActivity类中的非静态方法就不能使用 $new() 的方式了,会导致系统崩溃
因为andriod的生命周期和线程规则,Android组件,如 Activity
的子类,依赖于应用程序上下文进行正确运行。在Frida中,您可能缺少必要的上下文。Android UI组件通常需要具有关联Looper的特定线程。如果涉及UI任务,请确保在具有活动Looper的主线程上执行。活动是较大的Android应用程序生命周期的一部分。创建MainActivity的实例可能需要应用处于特定状态,并且通过Frida管理整个生命周期可能并不直接。
所以说不能直接来 new MainActivity 这个类,因为在Android程序启动的时候,系统就会创建 MainActivity 的实例,这个时候就可以直接使用 Java.choose 来指定Java的实例来进行主动调用了
这种方式一般在非 MainActivity 类的,且有实例化对象的时候调用
这个时候是应该使用 Java.choose 的
1 2 3 4 5 6 7 Java .choose ("" , { onMatch : function (instance ) { var result = instance.get_flag (1337 ); console .log (result); }, onComplete : function ( ) {} })
frida0x6 反编译看方法
这个主动调用需要中西结合,使用 $new 来获取Checker 类的实例化对象,更改num1和num2的值,再用Java.choose 来获取MainActivity类的实例化对象,来主动调用 get_flag 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 Java .perform (function ( ) { var Check = Java .use ('com.ad2001.frida0x6.Checker' ); var Checker = Check .$new(); Checker .num1 .value = 1234 ; Checker .num2 .value = 4321 ; var MainActivity = Java .use ('com.ad2001.frida0x6.MainActivity' ); Java .choose ("com.ad2001.frida0x6.MainActivity" , { onMatch : function (instance ) { instance.get_flag (Checker ); }, onComplete : function ( ) {} }) })
看代码是是一个AES的ECB加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const cryptoJS = require ('crypto-js' );function get_flag ( ) { var key = cryptoJS.enc .Hex .parse (Buffer .from ("MySecureKey12345" , "utf8" ).toString ("hex" )) var decodeBase64 = Buffer .from ("QQzMj/JNaTblEHnIzgJAQkvWJV2oK9G2/UmrCs85fog=" , "base64" ) var encrypted = cryptoJS.lib .WordArray .create (decodeBase64) var cfg = { mode : cryptoJS.mode .ECB , padding : cryptoJS.pad .Pkcs7 } var decrypted = cryptoJS.AES .decrypt ({ciphertext : encrypted}, key, cfg); console .log (decrypted.toString (cryptoJS.enc .Utf8 )); } get_flag ();
frida0x7 这个和上面的区别就是,这里不需要主动调用了,这里初始化了Checker这个类,传入了两个值,然后调用了 flag这个方法,现在需要hook Checker 这个类的构建方法,将传入值改变即可
1 2 3 4 5 6 7 8 Java .perform (function ( ) { var Check = Java .use ('com.ad2001.frida0x7.Checker' ); Check .$init .overload ('int' , 'int' ).implementation = function (a, b ) { var a1 = 513 ; var a2 = 513 ; return this .$init(a1, a2); }; })
出结果
JS解密方法和0x6一模一样
frida0x8 难度飙升了,进so层了,可以看到关键的一个方法放到了SO层,找so文件反编译
找到这个方法
因为的对so层也了解不多,就逐行解读一下,就当加深印象了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 _BOOL8 __fastcall Java_com_ad2001_frida0x8_MainActivity_cmpstr (__int64 a1, __int64 a2, __int64 a3) { int v4; int i; char *s1; char s2[104 ]; unsigned __int64 v9; v9 = __readfsqword('(' ); s1 = (char *)_JNIEnv::GetStringUTFChars(a1, a3, 0LL ); for ( i = 0 ; i < (unsigned __int64)__strlen_chk("GSJEB|OBUJWF`MBOE~" , -1LL ); ++i ) s2[i] = aGsjebObujwfMbo[i] - 1 ; s2[__strlen_chk("GSJEB|OBUJWF`MBOE~" , -1LL )] = 0 ; v4 = strcmp (s1, s2); __android_log_print(3LL , "input " , &unk_6B0, s1); __android_log_print(3LL , "Password" , &unk_6B0, s2); _JNIEnv::ReleaseStringUTFChars(a1, a3, s1); return v4 == 0 ; }
GetStringUTFChars
是JNI中的一个API(可以这样理解),这个 从 Java 字符串中获取 UTF-8 编码的字符数组。,也就是说 s1 存储的是我们输入框输入的信息
for ( i = 0; i < (unsigned __int64)__strlen_chk(“GSJEB|OBUJWF`MBOE~”, -1LL); ++i ) s2[i] = aGsjebObujwfMbo[i] - 1;
这里是利用了一个神秘的方法 aGsjebObujwfMbo 来处理出flag ,将flag赋值给S2
使用 strcmp 方法比较两个字符串,输出两句,然后将s1转换成Java的字符串,然后返回0 ,无论是否正确都返回0 ,这个时候就需要hook strcmp 方法了
沉淀一下,继续hook
hook一下 strcmp 方法,这个是系统方法,通过计算地址值进行hook
1 2 3 4 5 6 7 8 9 Interceptor .attach (Module .findBaseAddress ('libfrida0x8.so' ).add (0x9DD ), { onEnter : function (args ) { console .log ('onEnter: ' , hexdump (args[0 ])) console .log ('onEnter: ' , hexdump (args[1 ])) }, onLeave : function (retval ) { console .log ('onLeave: ' , retval) } });
tab键查看地址
直接打印出来 flag 了
frida0x9
貌似只需要让这个so函数的返回值为1337即可,根本不需要so层的hook,直接Java层,改一下return结果即可
1 2 3 4 5 6 Java .perform (function ( ) { let MainActivity = Java .use ("com.ad2001.a0x9.MainActivity" ); MainActivity ["check_flag" ].implementation = function ( ) { return 1337 ; }; })
静态分析直接梭也行
1 2 3 4 5 6 7 8 9 10 11 12 const CryptoJS = require ("crypto-js" );function AES_encrypt ( ) { let key = CryptoJS .enc .Utf8 .parse ("3000300030003003" ); let data = CryptoJS .enc .Base64 .parse ("hBCKKAqgxVhJMVTQS8JADelBUPUPyDiyO9dLSS3zho0=" ) var decrypted = CryptoJS .AES .decrypt ({ciphertext : data}, key, { mode : CryptoJS .mode .ECB , padding : CryptoJS .pad .NoPadding }).toString (CryptoJS .enc .Utf8 ); console .log (decrypted); return decrypted; }
frida0xA 进so看看
好像也没有什么东西,这个so方法就是将这个东西设置到桌面显示上,返回值就是这个 hello hackers
翻了翻还有一个get_flag 方法,主动调用这个方法
so层的主动调用超纲了,百度一下
1 2 3 4 5 6 7 Java .perform (function ( ) { var addr = Module .findExportByName ("libfrida0xa.so" , "_Z8get_flagii" ) console .log (`addr=${addr} ` ) let get_flag_pointer = new NativePointer (addr) let get_flag = new NativeFunction (get_flag_pointer, 'int' , ['int' , 'int' ]) get_flag (2 ,1 ) })
再看一下代码,是输出成日志的形式,不是直接显示的,还需要查看一下日志
因为使用了 flag 标签,过滤一下
结果如下
也可以根据这个代码直接解密,源代码是将每个字符偏移,将字符转换成ASCCII码的形式进行计算还原
1 2 3 原始字符:F P E > 9 q 8 A > B K - ) 2 0 A - # Y 偏移计算:0,2,4,6,8,...(每个字符的偏移量为 2*i) 最终结果:F+0, P+2, E+4, >+6, 9+8, q+10, 8+12, A+14, >+16, B+18, K+20, -+22, )+24, 2+26, 0+28, A+30, -+32, #+34, Y+36
frida 0xB java层没东西,直接进so。end
没找到,找博客:https://www.cnblogs.com/WXjzc/p/18231528
直接进去没东西,原因是这里有一个永假判断,跳转到其他地址了,感觉比较偏CTF风格了
这里先将 var_24 赋值成 0xDEADBEEF
然后再判断 var_24 是否等于 0x539
不相等跳转到错误分支 loc_171A6 所有啥也没有
在IDA中右键jnz设置成nop,先看一下代码
这个时候就有两种修改方式了,第一种是将 jnz loc_171A6
修改成 jz loc_171A6
jnz 是结果为0(否)跳转指令,jz 是结果为1(是)跳转指令,改成jz就不会出现跳转
或者是将这个指令Nop掉,不进行跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Java .perform (function ( ) { var funcAddr = Module .findExportByName ("libfrida0xb.so" , 'Java_com_ad2001_frida0xb_MainActivity_getFlag' ); var opaddr = funcAddr.add (0x1E ) var writer = new X86Writer (opaddr); Memory .protect (opaddr, 0x10 , 'rwx' ); try { writer.puNop (); writer.flush (); } catch (error) { writer.dispose (); } })
jnz -> jz
jnz的机器码是 0F 85,jz的机器码是 0F 84
需要将这个改为 0F 84
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Java .perform (function ( ) { var funcAddr = Module .findBaseAddress ("libfrida0xb.so" ); var opaddr = funcAddr.add (0x170CE ) console .log (opaddr); var writer = new X86Writer (opaddr); Memory .protect (opaddr, 0x1000 , 'rwx' ); try { writer.putU8 (0x0F ) writer.putU8 (0x84 ) writer.flush (); } catch (error) { console .log ( "Error:" ,error); } finally { writer.dispose (); } })
第二种就是令 var_24 等于 0x539 ,使跳转指令不成立
C7 45 DC EF BE AD DE
后四个机器码是赋值的小端字节序,将这个改成 39 05 00 00,就可以使下方判断成立
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Java .perform (function ( ) { var funcAddr = Module .findBaseAddress ("libfrida0xb.so" ); var opaddr = funcAddr.add (0x170CA ) console .log (opaddr); var writer = new X86Writer (opaddr); Memory .protect (opaddr, 0x1000 , 'rwx' ); try { writer.putU8 (0xEF ) writer.putU8 (0xBE ) writer.putU8 (0xAD ) writer.putU8 (0xDE ) writer.flush (); } catch (error) { console .log ( "Error:" ,error); } finally { writer.dispose (); } })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Java .perform (function ( ) { var funcAddr = Module .findBaseAddress ("libfrida0xb.so" ); var opaddr = funcAddr.add (0x170C3 ) console .log (opaddr); var writer = new X86Writer (opaddr); Memory .protect (opaddr, 0x1000 , 'rwx' ); try { writer.putU8 (0x39 ) writer.putU8 (0x05 ) writer.putU8 (0x00 ) writer.putU8 (0x00 ) writer.flush (); } catch (error) { console .log ( "Error:" ,error); } finally { writer.dispose (); } })
END