demo_01
输入完用户名和密码之后发现窗口提示login faild:
直接在jadx中搜索字符:
然后定位到关键代码:
逻辑非常清晰:
输入用户名和密码
1 2 | final EditText editText = (EditText) findViewById(R. id .username);
final EditText editText2 = (EditText) findViewById(R. id .password);
|
点击之后获取两个文本框中的值
1 2 3 | public void onClick(View view) {
String obj = editText.getText().toString();
String obj2 = editText2.getText().toString();
|
只有LoginActivity.a(obj, obj)=(obj2)时才能跳向正确的分支
1 2 3 | else if (LoginActivity.a(obj, obj).equals(obj2)) {
LoginActivity.this.startActivity(new Intent(LoginActivity.this.mContext, FridaActivity1. class ));
LoginActivity.this.finishActivity( 0 );
|
然后交叉引用定位到了这个函数:
所以接下来我们就要hook这个函数得到他的返回值,然后将这个函数的返回值输入到第二个文本框中才可以:
首先用frida看看每个程序的pid,因为注入脚本的时候有的frida版本的问题需要使用程序的pid进行注入:
然后用objection看看内存中是否真正的存在这个activity:
之前我的一篇文章中的有个activity需要手动跳转到android intent launch_activity XXXXXX
然后在看看这个activity中的函数在内存中的情况:
发现程序确实调用了这个函数:
(agent) [442509] Called com.example.androiddemo.Activity.LoginActivity.a(java.lang.String, java.lang.String)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function hook_java(){
Java.perform(function(){
Java.use( "com.example.androiddemo.Activity.LoginActivity" ).a.overload( 'java.lang.String' , 'java.lang.String' ).implementation = function(arg1,arg2){
console.log( "hook,start!" )
var result = this.a(arg1,arg2);
console.log( "arg1,arg2,result:" ,arg1,arg2,result);
}
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
然后在命令行窗口中就可以发现函数的返回值了:
所以这个函数如果输入aaa123返回值就应该是:
ed5091524bdcb5bf75012e7562cf99d4f7078da00af8c70210196c835c27239f
点击登录按钮成功跳向了第一关:
点击登录按钮提示check faild
那么接下来继续在jadx中搜索这个字符串:
然后定位到了这里:
所以我们就要hook这个a函数让他返回这个字符串
1 | R4jSLLLLLLLLLLOrLE7 / 5B + Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a + P65NK44NNROl0wNOLLLL =
|
用objection看一下,发现调用了这几个函数:
我们发现这个函数在内存中是真实存在的,所以需要我们hook的只有这一个函数:
com.example.androiddemo.Activity.FridaActivity1.a([B)
然后写js脚本就行了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function hook_java(){
Java.perform(function(){
Java.use( "com.example.androiddemo.Activity.FridaActivity1" ).a.implementation = function(arg1){
/ / 这里有个函数重载,在刚刚的objection中也可以看出来
console.log( "hook,start!" )
var result = "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=" ;
console.log( "arg1:" ,arg1);
return result;
}
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
成功过关
然后继续下一关,应该是这里:
首先看看都有什么函数
我们再用objection看看调用了哪些函数:
发现这两个函数并没有被调用,所以我们要hook这两个函数让他们主动执行:
1 2 | private static void com.example.androiddemo.Activity.FridaActivity2.setStatic_bool_var()
private void com.example.androiddemo.Activity.FridaActivity2.setBool_var()
|
按照程序的执行流程我们可以知道:
我们要让onCheck() 函数中的if判断的返回值为假,才能跳向正确的分支,所以我们要让两个变量的值都为true,这里有两种思路,一种是hook两个变量的初始值,修改函数的初始值,还有一种方法是我们主动调用这俩个函数来修改变量的值
我们先来实现一种简单的方法,直接修改变量的值:
对了之前忘了说了,我们可以再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 | function hook_java(){
Java.perform(function(){
/ / 这里需要注意一下,静态的成员变量可以直接修改
/ / private static boolean static_bool_var = false;
Java.use( "com.example.androiddemo.Activity.FridaActivity2" ).static_bool_var.value = true;
/ / 动态的成员变量需要使用主动调用的方法
/ / private boolean bool_var = false;
Java.choose( "com.example.androiddemo.Activity.FridaActivity2" ,{
onMatch:function(instence){
console.log( "found instence:" ,instence);
instence.bool_var.value = true;
},onComplete:function(){console.log( "instence completed!" )}
})
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
然后在来实现第二种方法,hook这两个函数,让他们主动执行一下,因为从刚刚的objection的分析中我们可以发现,那两个函数并没被调用,所以我们要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 | function hook_java(){
Java.perform(function(){
/ / private static void setStatic_bool_var()
/ / 这个函数是静态的函数,所以可以直接hook调用
Java.use( "com.example.androiddemo.Activity.FridaActivity2" ).setStatic_bool_var();
/ / private void setBool_var()
/ / 这个函数不是static修饰的,所以要通过主动调用的方式
Java.choose( "com.example.androiddemo.Activity.FridaActivity2" ,{
onMatch:function(instance){
console.log( "found instence" ,instance);
instance.setBool_var() / / 调用函数,执行函数
},onComplete:function(){console.log( "instence,completed!" );}
})
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
通过这两种方法我们都可以实现通关
然后我们进入第三关:
objection看一下执行了什么函数
发现修改成员变量的值就可以了:
把这三个成员变量的值都修改为true,程序就会执行success分支
当让我们也需要注意一下动态成员变量的修改方法:
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 | function hook_java(){
Java.perform(function(){
/ / private static boolean static_bool_var = false;
/ / 静态的成员变量可以直接修改
Java.use( "com.example.androiddemo.Activity.FridaActivity3" ).static_bool_var.value = true;
/ / private boolean bool_var = false;
/ / private boolean same_name_bool_var = false;
/ / 动态的成员变量需要主动调用
Java.choose( "com.example.androiddemo.Activity.FridaActivity3" ,{
onMatch:function(instence){
console.log( "found instence:" ,instence);
instence.bool_var.value = true; / / 修改成员变量的值
/ / 这里需要注意一下,因为具有同名的成员变量和成员函数,所以修改成员变量的值的时候需要在前面加一个_
instence._same_name_bool_var.value = true; / / 修改成员变量的值
},onComplete:function(){console.log( "instence completed!" )}
})
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
成功进入第四关:
,由于我们不知道到底调用了哪些方法,这里我们还是先用objection看一下函数的执行:(到这一关了,告诉大家一个这个程序的bug):
可以直接用objection在不同的activity中来回跳
好了我们回来继续看看函数的调用:
然后接下来我们只需要让每一个check函数都返回true就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function hook_java(){
Java.perform(function(){
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check1.implementation = function(){ return true};
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check2.implementation = function(){ return true};
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check3.implementation = function(){ return true};
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check4.implementation = function(){ return true};
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check5.implementation = function(){ return true};
Java.use( "com.example.androiddemo.Activity.FridaActivity4$InnerClasses" ).check6.implementation = function(){ return true};
})
}
function main(){
hook_java() / / 在main函数中调用hook_java函数
}
setImmediate(main);
|
然后进入第五关:
先用objection看看调用了什么函数
这里我们需要分析一下代码:
判断getDynamicDexCheck()这个函数的返回值是否为空
然后发现他是动态加载一个dex文件:
然后这个check函数在这里声名:
我们如果直接hook这个check函数是不会成功的,所以我们要先找到这个dex文件:
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 | function fifth(){
Java.perform(function(){
Java.choose( "com.example.androiddemo.Activity.FridaActivity5" ,{
onMatch:function(instance){
/ / 用classname来查看
console.log( "found instence getDynamicDexCheck():" ,instance.getDynamicDexCheck().$className);
},onComplete:function(){console.log( "search complete!" );}
})
/ / 用枚举法看看在那个类里面
Java.enumerateClassLoaders({
onMatch:function(loader){
try {
if (loader.findClass( "com.example.androiddemo.Dynamic.DynamicCheck" )){
console.log( "Success found loader:" ,loader);
/ / 将默认的classloader替换成loader
Java.classFactory.loader = loader;
}
} catch (error) {
console.log( "found,error!" + error);
}
},onComplete:function(){console.log( "enum complete!" )}
})
Java.use( "com.example.androiddemo.Dynamic.DynamicCheck" ).check.implementation = function(){ return true;}
})
}
|
最后进入第六关:
这里我们采取枚举所有类的方法来hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function func6() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle) {
if (name.indexOf( "com.example.androiddemo.Activity.Frida6" ) > = 0 ) {
console.log(name);
var frida6 = Java.use(name);
frida6.check.implementation = function () {
console.log( "frida 6 check:" , this);
return true;
};
}
}, onComplete: function () {
}
})
});
}
|
demo_02
安装好程序之后我们首先看一下程序的提示,
提示设备只能运行在俄罗斯的设备上,然后我们用objection看一下程序的activity:
然后竟然惊喜的发现程序可以在不同的activity中切换:
......这并不是我们想要做的,还是从一开始的不能在俄罗斯的设备上开始分析吧:
通过搜索字符串,我们定位到了这里:
所以我们就要hook这个函数的返回值System.getProperty()为Russian
可以去看看system在哪个包下面:
接下来就可以写js脚本了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function main(){
Java.perform(function(){
var System = Java.use( "java.lang.System" );
console.log(System);
System.getProperty.overload( 'java.lang.String' ).implementation = function (key) {
var result = this.getProperty(key);
result = "Russia" ;
console.log( "System.getProperty:" , key, result);
return result;
};
})
}
setImmediate(main)
|
frida -U --no-pause -f com.tlamb96.spetsnazmessenger -l E:\homework_python\frida_env\shizhan_02.js
这里要注意一下,这里要使用spwan的方式注入,因为是在app启动的过程中注入的脚本
接下来手机提示这样的界面,
继续字符串搜索看看
根据程序的逻辑,这两个变量成员得相等:
str.equals(getResources().getString(R.string.User)))
然后向上找找str:
String str = System.getenv("USER");
所以接下来我们就要hook这个getenv函数了
然后找到(R.string.User)这个的值作为函数的返回值
<string name="User">RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==</string>
这里注意一下,在写js脚本的时候之前写的js脚本不要删除,因为这两个都需要hook:
js脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | function main(){
Java.perform(function(){
var System = Java.use( "java.lang.System" );
console.log(System);
System.getProperty.overload( 'java.lang.String' ).implementation = function (key) {
var result = this.getProperty(key);
result = "Russia" ;
console.log( "System.getProperty:" , key, result);
return result;
};
System.getenv.overload( 'java.lang.String' ).implementation = function (key) {
var result = this.getenv(key);
result = "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==" ;
console.log( "getenv :" ,key,result);
return result;
};
})
}
setImmediate(main)
|
成功进入这个界面:
随意输入一下,程序是这样提示的:
然后我们继续去jadx中看看字符串,定位到了这里:
然后开始分析这个程序:
1 | if (this.n ! = null && this.o ! = null && !this.n.isEmpty() && !this.o.isEmpty()) {
|
这一行代码的意思是我们输入的用户名和密码都不能为空
1 | if (!this.n.equals(getResources().getString(R.string.username))) {
|
这个的意思是输入的用户名要和资源文件中的相等
<string name="username">codenameduchess</string>
</resources>
然后进入这个分支:
然后看看j函数是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 | private boolean j() {
byte[] digest = this.m.digest(this.o.getBytes());
/ / 这里是用户输入的密码经过m函数(跟进分析之后发现m函数是md5函数)
String str = "";
for (byte b : digest) {
str = str + String. format ( "%x" , Byte.valueOf(b));
}
/ / 转换成 hex 字符串
return str .equals(getResources().getString(R.string.password));
/ / 最后这个 str 要和资源文件中的password相等才可以
/ / <string name = "password" > 84e343a0486ff05530df6c705c8bb4 < / string>
}
|
然后直接获得这个密码的明文就可以了
那么我们将用户名:codenameduchess
密码:guest输入文本框之后就可以了:
然后成功进入下一个activity
然后随意输入之后发现没有什么提示,只能看代码了:
我们发现这里有程序界面中的字符串
我们可以猜测,接下来的操作肯定是要输入字符串,所以我们可以hook这个com.tlamb96.kgbmessenger.b.a函数,看看他的函数调用栈,可能就会有发现:
根据刚刚界面的参数我们可以知道,我们接下来是要hook这个构造函数的:
public a(int i, String str, String str2, boolean z)
1 2 3 4 5 6 7 | var a = Java.use( "com.tlamb96.kgbmessenger.b.a" );
a.$init.implementation = function(i, str ,str2,z){
this.$init(i, str ,str2,z);
console.log( "a,$init" ,i, str ,str2,z);
console.log(Java.use( "android.util.Log" ).getStackTraceString(Java.use( "java.lang.Throwable" ).$new()));
}
})
|
调用栈取自r0ysue大佬的知识星球
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
发现是由这个函数调用来的:
com.tlamb96.kgbmessenger.MessengerActivity.onSendMessage
从gadx中看看:
在127行发现确实是由这个函数调用的
然后我们发现程序的逻辑就是让这个a函数的返回值为
然后我们用objection看一下,程序在执行过程中确实调用了这个函数:
com.tlamb96.kgbmessenger.MessengerActivity.a
这个a函数不是静态
所以我一开始想用主动调用的方法来着,但是通过主动调用hook这个函数之后,即使没有输入他也会hook成功,所以达不到我们想要的效果:
1 2 3 4 5 6 7 8 9 | Java.choose( "com.tlamb96.kgbmessenger.MessengerActivity" ,{
onMatch:function(instance){
console.log( "found instance" ,instance);
instance.a = function( str ){
result = "[email protected]]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003" ;
return result;
}
},onComplete:function(){console.log( "search,completed!" )}
})
|
发现这样是不可以的,所以还是采用java.use的方法来hook这个函数
1 2 3 4 5 6 7 8 | Java.use( "com.tlamb96.kgbmessenger.MessengerActivity" ).a.implementation = function(x){
var result = this.a(x);
console.log( "a:x,result:" ,x,result);
if (x = = "kanxue" ){
result = Java.use( "java.lang.String" ).$new( "[email protected]]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003" );
}
return result;
}
|
如果我们输入kanxue的话就会注入脚本
程序就会正确运行
这里我们采用了类似于爆破的方法,但是这样程序到最后是不会得到flag的
我们在程序的末尾发现了flag是由i和j函数产生的
然后这个i函数里面的两个参数
char[] charArray = this.q.substring(19).toCharArray();
char[] charArray2 = this.s.substring(7, 13).toCharArray();
q和s是由我们的程序的输入框中的值得到的
所以我们还是要按照解密顺序一步步的来,不能直接返回两个字符串的值
这里我们用android stdio将java算法变成dex文件来解密
build\intermediates\javac\debug\classes\com\example\myapplication这个目录下是编译得class文件,然后将.class文件转换成.jar文件在转换成.dex文件,然后将dex文件放在data/local/tmp文件下,然后给他777权限,最后在js脚本中加载dex文件
在js脚本刚开始的地方
然后输入执行后的结果
继续去jadx中看看
所以接下来就要分析这个b函数了
如果还是按照之前的方法就不好弄了,所以直接编写py脚本解密就可以了:
输入程序的运行结果之后得到flag:
当程序执行到这一步的时候,如果点击好吧,就会退出程序:
然后再jadx中定位到关键代码
我们可以发现
sn是我输入的字符串,然后转成字节,转成string类型
然后这一行代码中传入了:
1 | ((MyApp) RegActivity.this.getApplication()).saveSN(sn);
|
saveSN这个关键函数
我们跟进去分析一下,他是native层的函数
我们再从程序的入口点分析分析:
我们刚刚分析的注册的函数是从这里跳过来的:
如果我们再doRegister() 函数中点击注册按钮,就会执行这里
1 | intent.setComponent(new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity" ));
|
然后我们再向上回溯:
在这个oncreate函数中的onclick方法中:
根据MyApp.m 的值判断是否需要注册
然后我们分析一下这个MyAPP方法,发现他的逻辑也在so层:
所以我们要分析的就是这几个so层的方法了,在ida中打开看看:
我们在export中并没有发现savesn方法:
所以它采用的是动态注册的方法,直接去jni中看看:
首先要导入jni。h头文件,不要忘了这一点:
这里展示一下我修复之后的jni函数:
然后我们发现确实注册了这几个函数:
我们首先来分析第一个函数"initSN"
也就是那个n1函数:
首先打开这个文件/sdcard/reg.dat,我们会发现里面是一些奇怪的字符:
然后将文件中的内容存贮在v6中:
如果v6的值为"[email protected]",那么将v8的值设置为1,否则将v8的值设置为0
然后我们看一下n3函数:
然后跟进 v2 = getValue(a1);这里看看:
发现这里将m赋值,也就是刚刚给v8赋的值在这里传递给了m
最后我们来看一下savesn函数:
这里我展示一下修复之后的代码:
先打开这个文件中的内容/sdcard/reg.dat
然后将我们输入的字符串str传入v7:
v7 = (*env)->GetStringUTFChars(env, str, 0);
然后通过解密这个函数,就能得到flag
最后得到的flag为:xman{201608Am!2333}
先附上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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | var fputs_str = null;
function Hook() {
Java.perform(function () {
const imports = Module.enumerateImportsSync( "libmyjni.so" );
const imports_len = imports.length;
var fputs_addr = null;
for (var i = 0 ; i < imports_len; i + + ) {
if (imports[i].name = = "fputs" ) {
fputs_addr = imports[i].address;
break ;
}
}
if (fputs_addr ! = null) {
Interceptor.attach(fputs_addr, {
onEnter: function (args) {
fputs_str = args[ 0 ].readCString();
},
onLeave: function (retval) {
}
})
}
})
}
function Invoke(temp) {
Java.perform(function () {
Java.choose( "com.gdufs.xman.MyApp" , {
onMatch: function (instance) {
instance.saveSN(temp);
},
onComplete: function () {
}
})
})
}
function attack() {
Hook();
Java.perform(function () {
const end = "D" ;
const _array_len = _array.length;
const secret_len = secret.length
const myapp = Java.use( "com.gdufs.xman.MyApp" ).$new();
var flag = "";
for (var i = 0 ; i < _array_len; i + + ) {
var flag = false;
for (var j = 0 ; j < secret_len; j + + ) {
if (flag = = true) {
break ;
}
for (var k = 0 ; k < secret_len; k + + ) {
if (flag = = true) {
break ;
}
for (var m = 0 ; m < secret_len; m + + ) {
const temp = secret[j] + secret[k] + secret[m];
console.log(`temp: ${temp}`);
myapp.saveSN(temp);
if (_array[i] = = fputs_str) {
flag + = temp;
console.log(`flag: ${temp}`);
flag = true;
break ;
}
}
}
}
}
for (var i = 0 ; i < secret_len; i + + ) {
const temp = secret[i];
console.log(`temp: ${temp}`);
Invoke(temp);
if (end = = fputs_str) {
flag + = temp;
console.log(`flag: ${temp}`);
break ;
}
}
console.log(`flag: xman{${flag}}`);
})
}
|
这道题的逻辑倒是不难,接下来我们试一试frida:
首先我们就要hook掉那个java层的killprocess的函数,一执行就退出,太恶心了:
我们会发现这个函数在这个包里面:
直接展示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 | function hook_java(){
Java.perform(function(){
var MyApp = Java.use( "com.gdufs.xman.MyApp" );
MyApp.saveSN.implementation = function( str ){
console.log( "MyApp.saveSN.str:" , str );
this.saveSN( str ); / / 调用一下函数
}
/ / hook掉killProcess函数,不让他执行
var Process = Java.use( "android.os.Process" );
Process.killProcess.implementation = function(pid){
console.log( "Process.killProcess not implement!" ,pid);
}
console.log( "hook completed!" );
})
}
|
然后我们尝试着hook一下so层的用户层的函数,试试能不能找到:
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 | function hook_native(){
/ / 找到模块的地址
var base_myjni = Module.findBaseAddress( "libmyjni.so" );
/ / 如果以spwan的模式启动,那么就要判断base_myjni的值是否为 0 ,一开始会不加载这个so文件
if (base_myjni){
console.log( "base_myjni" ,base_myjni); / / base_myjni 0xbfd0e000
/ / 找到要hook的函数 这个函数得是export函数
/ / 参数: so所在路径,要hook的函数的名字
var n2 = Module.findExportByName( "libmyjni.so" , "n2" );
/ / thumb格式的函数,hook的时候在静态分析的地址上面加 1
/ / ida地址: 0x000011F8 实际地址:n2: 0xbfd0f1f9
/ / 相差一个模块的地址
console.log( "hook_native()__ n2:" ,n2);
/ / n2 - base_myjni = 偏移 + 1 ;
/ / 11f9 = 偏移 + 1 ; 偏移 = 11f8 也就是ida中的地址
/ / 开始hook函数
Interceptor.attach(n2,{
onEnter:function(args){
/ / args是一个数组
console.log( "hook_native()__ n2 onEnter:" ,args[ 0 ],args[ 1 ],args[ 2 ]);
},onLeave:function(retval){
}
});
}
}
|
我们可以发现,成功的hook到了函数:
然后我们来hook一下这个系统层的函数,看看是不是我们输入的值:
详细过程在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 | / / hook这个函数GetStringUTFChars
/ / 这个函数GetStringUTFChars在libart里面
function hook_libart(){
/ / 枚举所有的文件来找到
/ / 首先找到so
var module_libart = Process.findModuleByName( "libart.so" );
/ / 看看有哪些符号
var symbols = module_libart.enumerateSymbols();
/ / 找到函数的名字和地址
var add_GetStringUTFChars = null;
for (var i = 0 ;i < symbols.length;i + + ){
var name = symbols[i].name;
if (name.indexOf( "art" ) > = 0 ){
if ( (name.indexOf( "JNI" ) > = 0 ) && (name.indexOf( "CheckJNI" ) = = - 1 ) ){
if (name.indexOf( "GetStringUTFChars" ) > = 0 ){
console.log( "hook_libart()__ name:" ,name);
/ / 找到函数的名字
add_GetStringUTFChars = symbols[i].address;
/ / 找到函数的地址
}
}
}
}
/ / 开始hook函数
if (add_GetStringUTFChars){
Interceptor.attach(add_GetStringUTFChars,{
onEnter:function(args){
console.log( "onEnter find add_GetStringUTFChars************************************\r\n" );
},onLeave:function(retval){
/ / 从ida中看出返回值为const char * 类型
console.log( "onLeave GetStringUTFChars_native_retval************************************\r\n:" ,ptr(retval).readCString());
}
})
}
|
输入注册码为kanxue
确实可以hook到这个函数,并且返回值也是和我们预料的一样:
还可以hook这个系统层的函数来查看程序时候被注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | / / 通过hook strcmp函数来查看m的值,来查看程序是否被注册
function hook_libc() {
/ / hook libc的函数
var strcmp = Module.findExportByName( "libc.so" , "strcmp" );
console.log( "strcmp:" , strcmp);
Interceptor.attach(strcmp, {
onEnter: function (args) {
var str_2 = ptr(args[ 1 ]).readCString();
console.log( "strcmp:" , ptr(args[ 0 ]).readCString(),
ptr(args[ 1 ]).readCString());
}
}, onLeave: function (retval) {
}
});
}
|
然后我们在尝试一下利用frida向程序中写值:
1 2 3 4 5 6 7 8 9 | / / 用firda向文件中写内容
function write_reg_dat() {
/ / frida 的api来写文件
var file = new File ( "/sdcard/reg.dat" , "w" );
file .flush();
file .close();
}
|
这样就能达到爆破这道题目的效果
[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~
最后于 5天前
被以和爲貴编辑
,原因: