!!!锁机软件有一定风险,一定要在模拟器上打开,千万不要用自己的手机安装!!!
这个软件会有关闭usb调试、调大声音、不健康画面等。解压密码:52pojie
拿到样本之后,首先进行静态分析,将软件拖入到jadx和jeb中。从AndroidManifest.xml文件中可以看到软件的包名、申请的权限以及程序的入口。
然后我们跳到MainActivity
类中,也就是程序的入口。代码如下,代码很简单,主要就说注册了一个服务,服务主要的功能就是启动了名为com.shimeng.qq2693533893.MyServiceOne
的服务类。
public class MainActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle bundle) {
LogCatBroadcaster.start(this);
super.onCreate(bundle);
try {
startService(new Intent(this, Class.forName("com.shimeng.qq2693533893.MyServiceOne")));
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
}
}
那么接下来就跳到启动的服务类中去看看,MyServiceOne
代码量就很大了,从一些关键词之类的东西可以分析出来这里就是主要的锁机部分了,看源码可以看出作者的攻击性还是很强的 - -|||。
分析到这其实静态分析就差不多了,接下来就结合Frida和Objection进行动态分析。
首先,因为程序一开始就只是启动了一个服务,那我们就使用objection从services来入手进行分析。
列出程序的所有services,可以看到只有一个,那我们就对这个类进行hook
#查看所有的services
com.shimeng.qq2693533893 on (vivo: 9) [usb] # android hooking list services
com.shimeng.qq2693533893.MyServiceOne
#hook com.shimeng.qq2693533893.MyServiceOne 类
com.shimeng.qq2693533893 on (vivo: 9) [usb] # android hooking watch class com.shimeng.qq2693533893.MyServiceOne --dump-
args --dump-backtrace --dump-return
hook了MyserviceOne
类之后,我们可以看到程序一直在调用access$L1000018
方法,我们来看看这个方法具体是什么。因为在jadx中没有找到这个方法,就使用jeb来进行分析。
这样我们就找到了程序真正做事情的地方了,可以看到这里设置了音量等。
接下来我们继续分析解锁按钮做了什么事情,我们在输入框中随便输入一些东西,然后点击解锁,可以看到objection显示出了调用的一些函数,我们排除掉access$L1000018
,这里的调用是从上往下的调用,可以看到首先调用了颜如玉
这个函数。
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.颜如玉(java.lang.String)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.SHA1(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.getOrderByLexicographic(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.getParamsName(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.lexicographicOrder(java.util.List)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.splitParams(java.util.List, java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.颜如玉(java.lang.String)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.SHA1(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.getOrderByLexicographic(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.getParamsName(java.util.Map)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.lexicographicOrder(java.util.List)
(agent) [867288] Called com.shimeng.qq2693533893.MyServiceOne.splitParams(java.util.List, java.util.Map)
首先我们先静态分析一下源码,找到这个函数,进行交叉引用,可以看到只有com.shimeng.qq2693533893.MyServiceOne$100000002.onClick()
这个函数调用了这个方法,然后那么接下来我们再对这个函数进行hook,看看它的调用堆栈情况。结果如下,和我们静态分析的结果是一样的。
com.shimeng.qq2693533893 on (vivo: 9) [usb] # android hooking watch class_method com.shimeng.qq2693533893.MyServiceOne.
颜如玉 --dump-args --dump-backtrace --dump-return
(agent) Attempting to watch class com.shimeng.qq2693533893.MyServiceOne and method 颜如玉.
(agent) Hooking com.shimeng.qq2693533893.MyServiceOne.颜如玉(java.lang.String)
(agent) Registering job 350829. Type: watch-method for: com.shimeng.qq2693533893.MyServiceOne.颜如玉
com.shimeng.qq2693533893 on (vivo: 9) [usb] # (agent) [350829] Called com.shimeng.qq2693533893.MyServiceOne.颜如玉(java.lang.String)
(agent) [350829] Backtrace:
com.shimeng.qq2693533893.MyServiceOne.颜如玉(Native Method)
com.shimeng.qq2693533893.MyServiceOne$100000002.onClick(MyServiceOne.java:186)
android.view.View.performClick(View.java:6599)
android.view.View.performClickInternal(View.java:6576)
android.view.View.access$3100(View.java:780)
android.view.View$PerformClick.run(View.java:25899)
android.os.Handler.handleCallback(Handler.java:873)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:193)
android.app.ActivityThread.main(ActivityThread.java:6730)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:860)
(agent) [350829] Arguments com.shimeng.qq2693533893.MyServiceOne.颜如玉(9B480C226E220FA9CD301C87D64FB3B9)
(agent) [350829] Return Value: 7A2AADD4840E0AF67E2F86A387E099418DB118E1
我们在jeb中找到这个函数,可以看到这里就是解锁的主体部分了,其中,onclick函数在100000002
类中,100000002
类是由sm
函数调用的。
通过源码可以得出结论,如果想要解锁,就要使得下面这串代码结果为真。
if(颜如玉QQ2693533893.getSaltMD5(MyServiceOne.颜如玉(v2.substring(0, 3))) + v2.substring(3, v2.length()).equals("9DDEB743E935CE399F1DFAF080775366" + v3_1))
我们来分析其中的参数都是从哪里来的,具体见下图,可以看到主要由两部分组成,一部分是我们的输入,一部分是随机值。我们的输入的前三位通过颜如玉
和MD5
函数的处理之后,应该需要等于9DDEB743E935CE399F1DFAF080775366
这个值。颜如玉
这个函数我们前面已经分析过了,主要就是取了一个sha1
,那么由此我们可以推断要求我们输入的前3位应该是固定的。具体的值我们就需要跑一下了。
通过这个函数我们就可以获取前三位的值了,结果是358
。有了前三位,接下来我们就分析后面的部分。
function md5(){
Java.perform(function(){
var javaStr = Java.use('java.lang.String')
for(var i = 100;i<1000;i++){
var i_str = javaStr.$new(String(i));
var MyServiceOne = Java.use('com.shimeng.qq2693533893.MyServiceOne');
var yry = Java.use('com.shimeng.颜如玉.颜如玉QQ2693533893');
var sha1 = MyServiceOne.颜如玉(i_str);
var md5 = yry.getSaltMD5(sha1);
if(md5 == '9DDEB743E935CE399F1DFAF080775366'){
console.log(i);
break;
}
}
})
}
后半部分需要让输入中的第4位到最后一位等于通过将随机数加密后截取0-9位的值,在sm
函数中可以看到,生成的随机数通过颜如玉QQ2693533893.get
函数变成了屏幕上的识别码,所以我们这里还要遍历一下来获取随机数。具体做法如下,得到我们的识别码为5035
。有了这些,我们就可以来生成解密的密钥了
function random(){
Java.perform(function(){
var javaStr = Java.use('java.lang.String')
for(var i = 1000;i<10000;i++){
var i_str = javaStr.$new(String(i));
var yry = Java.use('com.shimeng.颜如玉.颜如玉QQ2693533893');
var code = yry.get(i_str);
if(code == '©嘻∷©'){//识别码随机
console.log(i);
break;
}
}
})
}
生成密钥:
function getKey1(){
Java.perform(function(){
var javaStr = Java.use('java.lang.String')
var i_str = javaStr.$new(String(5035 ^ 1288));
var yry = Java.use('com.shimeng.颜如玉.颜如玉QQ2693533893');
var MyServiceOne = Java.use('com.shimeng.qq2693533893.MyServiceOne');
var code = yry.getSaltMD5(i_str);
var v3 = MyServiceOne.颜如玉(code);
var v3_1 = javaStr.$new(v3).replaceAll("\\D+", "").substring(0, 9);
console.log('358' + v3_1);
})
}
生成密钥,输入,解锁,一气呵。。。???还没完。。还有第二层,mmp
好吧,那就继续吧。有了之前的经验,第二层其实很容易,从第一层可以分析出,第二层是从sm2
函数来进行的。和第一层的识别码如出一辙,只需要把之前的脚本在跑一遍得出识别码的随机数。
这样我们就得到了第二层的密钥。
function getKey2(){
Java.perform(function(){
var javaStr = Java.use('java.lang.String')
var i_str = javaStr.$new(String(2750));//随机数
var yry = null;
var v2 = '';
Java.choose("com.shimeng.颜如玉.颜如玉QQ2693533893",{
onMatch: function(instance){
console.log("find instance",instance);
yry = instance;
},
onComplete: function(){}
})
var code = yry.getTwiceMD5ofString(i_str);
var v3 = yry.hex_sha1(code);
var v3_1 = javaStr.$new(v3).replaceAll("\\D+", "").substring(0, 9);
for(var i=100;i<1000;i++){
var i_str = javaStr.$new(String(i));
code = yry.getTwiceMD5ofString(yry.hex_sha1(i_str));
if(code == '8D4FF507DCDA63C201EB8B99D4170900'){
v2 = i_str;
}
}
console.log(v2 + v3_1);
})
}
生成密钥,输入,解锁,一气呵。。。呵呵???还没完。。还有第三层
好吧,那就继续!小东西!
通过第二层的解锁代码部分,我们可以看到调用的是诗梦
这个函数,这个函数的解锁部分如下:
两个条件满足一个即可
MyServiceOne.this.坐等前往世界的尽头的小船
= 187724addd757b99a9dc1eb570c65f32c33f5941
颜如玉QQ2693533893.hex_sha1(v0.val$fuck.getText().toString()).equals(MyServiceOne.this.坐等前往世界的尽头的小船)
这个其实很简单了,第一种我们直接使用frida查找MyServiceOne
的实例,然后把坐等前往世界的尽头的小船
这个变量赋值即可。第二种我们就需要把坐等前往世界的尽头的小船
这个值赋值为我们指定的值取sha1。
两种解决方案的脚本如下:
function hookGet(){
Java.perform(function(){
var javaStr = Java.use('java.lang.String');
var passwd = javaStr.$new(String(123456));
var passwdSign = Java.use('com.shimeng.颜如玉.颜如玉QQ2693533893').hex_sha1(passwd);
//第一种
Java.choose("com.shimeng.qq2693533893.MyServiceOne",{
onMatch: function(instance){
console.log('find instance',instance);
instance.坐等前往世界的尽头的小船.value = passwdSign;
},
onComplete: function(){
console.log('search completed!')
}
});
//第二种
Java.choose("com.shimeng.qq2693533893.MyServiceOne",{
onMatch: function(instance){
console.log('find instance',instance);
instance.坐等前往世界的尽头的小船.value = '187724addd757b99a9dc1eb570c65f32c33f5941';
},
onComplete: function(){
console.log('search completed!')
}
});
})
}
点击,解锁,一气呵成!!终于没有了!!搞定!!
通过分析这个锁机软件,让初入Frida的我学习到了很多,希望自己能够坚持学习下去。最后,本应该感谢这个软件的作者,不是他写这个软件,我也不能学习到这么多的知识,但是我还是忍不住问候他,CSB,CNMD,祸害人的G东西。