本文致谢
本篇文章学到的内容来自且完全来自r0ysue的知识星球,推荐一下(这个男人啥都会,还能陪你在线撩骚)。
打开样本app(文末有下载地址),手机界面显示如下:
将样本app拖入jadx,搜索上面的提示,定位到这里:
判断property的值是否为Russia,写个frida简单Hook下:
Java.use("java.lang.System").getProperty.overload('java.lang.String').implementation = function (str)
{
console.log("Current Property is: ",str,this.getProperty(str));
return "Russia";
}
这样强制返回了"Russia",就可以过掉这个判断,Hook后运行:
提示就在上面代码的下一个提示,可以看到是判断了这个:
!str.equals(getResources().getString(R.string.User))
找到 R.string.User,它在这里:
依葫芦画瓢,让getenv方法强制返回:
RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==
即可,代码:
Java.use("java.lang.System").getenv.overload('java.lang.String').implementation = function (str)
{
console.log("Current getenv is: ",str,this.getenv(str));
return "RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==";
}
这个字符串看起来像base64编码后的字符,解码后,拿到第一个flag:
FLAG{57ERL1NG_4RCH3R}
Hook后,进入了登录界面:
随便输入用户名和密码,看看提示啥:
jdax中搜索红色箭头位置处的字符串,定位到这里:
逻辑也很简单,输入的usename与 R.string.username相比较,在资源文件中找到R.string.username,它的值是:
再看password:,它是在j方法里面进行处理的,一个简单的md5摘要算法,哈希出来的结果与 R.string.password进行比较。
找到R.string.password的值,它是一个30位的字符串:
84e343a0486ff05530df6c705c8bb4
对于这种30位的md5,去掉前6位和后八位,然后再拿到md5加密网站上去解密即可。结果如图:
得到结果:guest,这时,用户名和密码都拿到了,登录后拿到flag:
这是一个聊天记录,随便输入一个字符串,点击SEND按钮,发现毫无反应,猜测需要发送特定的字符串,jadx中尝试搜索 last time,定位到这里:
注意这个 com.tlamb96.kgbmessenger.b.a 方法,发现下面的代码中也有调用:
看到这行代码:
String obj = editText.getText().toString();
以及这个if判断:
if (a(obj.toString()).equals(this.p)) {
Log.d("MessengerActivity", "Successfully asked Boris for the password.");
this.q = obj.toString();
this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.boris, "Only if you ask nicely", j(), true));
this.n.c();
}
输入的字符串经过a方法后与this.p进行比较,而this.p的值是:
"V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003";
a方法是:
private String a(String str) {
char[] charArray = str.toCharArray();
for (int i = 0; i < charArray.length / 2; i++) {
char c = charArray[i];
charArray[i] = (char) (charArray[(charArray.length - i) - 1] ^ '2');
charArray[(charArray.length - i) - 1] = (char) (c ^ 'A');
}
return new String(charArray);
}
这个方法很简单,后半段字符(逆序)与'2'异或,变成新的前半段字符;前半段字符逆序与'A'异或,变成新的后半段字符,中间的那个字符e不做处理。
写成Python还原代码:
s = "V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003"
half_len = len(s) // 2
r = ""
for i in range(half_len):
r += chr(ord(s[i]) ^ ord('2'))
r += 'e'
for i in range(half_len+1,len(s)):
r += chr(ord(s[i]) ^ ord('A'))
print (r[::-1])
得到结果:
Boris, give me the password
输入后有回应了:
还是没拿到flag,原来后面还是b函数处理:
b(obj.toString()).equals(this.r)
输入的字符串经过b方法后与this.r进行比较,而this.r的值是这样的:
"\u0000dslp}oQ\u0000 dks$|M\u0000h +AYQg\u0000P*!M$gQ\u0000";
b方法是这样的:
private String b(String str) {
char[] charArray = str.toCharArray();
for (int i = 0; i < charArray.length; i++) {
charArray[i] = (char) ((charArray[i] >> (i % 8)) ^ charArray[i]);
}
for (int i2 = 0; i2 < charArray.length / 2; i2++) {
char c = charArray[i2];
charArray[i2] = charArray[(charArray.length - i2) - 1];
charArray[(charArray.length - i2) - 1] = c;
}
return new String(charArray);
}
b方法也很简单:先右移,再异或,处理完成后再逆序,总长度未变。
因为i%8的结果会有等于0的情况,而一个数异或本身是等于0的,所以你看到r字符串有很多\u0000,其所在的位置都是8的倍数。写成Python还原后的代码:
r = "\u0000dslp}oQ\u0000 dks$|M\u0000h +AYQg\u0000P*!M$gQ\u0000"[::-1]
s = ""
for i in range(len(r)):
for ch in range(256):
if (ch >> (i%8)) ^ ch == ord(r[i]):
s += chr(ch)
break
print (s)
得到结果:
ay I *P EASE* h ve the assword
注意前后都有空白字符(\u0000),空白位置(非空格,从0开始数8的倍数位置)替换任意字符都可以的.比如输入如下字符串:
May I *PLEASE* have the password?
显示结果:
成功拿到了flag:"FLAG{p455w0rd_P134SE}"。
为了证实空白位置可以填入任意字符,我写了个主动调用的Hook代码:
Java.choose("com.tlamb96.kgbmessenger.MessengerActivity",{
onMatch : function (instance){
console.log("found instance :"+ instance);
instance.q.value = "Boris, give me the password";
instance.s.value = "2ay I *PLEASE* have the password3";
console.log("The flag is: ",instance.i());
},
onComplete : function(){
console.log("Search Completed!");
}
})
保存后的运行:
这里只需要修改instance.s.value的值,注意只能改空白位置的字符,可以随意运行。
本文完,再次感谢肉丝姐带我入门安卓逆向。
样本app百度网盘下载地址:
链接:https://pan.baidu.com/s/1-wHQAke6P6Ff-S3i9JAlYA
提取码:GOOG