0x01 前言
KGB Messenger是一个类似闯关的APP,有很多关卡,通过的方法有很多种,这里我们主要用frida进行闯关。中间有很多关于算法的实现和反推演的过程,其实对于新手还是很有挑战的,当然了对于我来说也是很有挑战的,接下分享下通关的思路和方法。
0x02 第一关:This app can only run on Russian devices.
打开APP,映入眼前的就是 This app can only run on Russian devices.的提示报错,如果不解决这个,后面将无法进行,我们将APP拖入jadx进行分析。
搜索到目标代码,我们分析发现 System.getProperty("user.home") = Russia 即可满足条件进入下一关。
System.getProperty(String name)方法用于得到系统的属性.System是在lang包中的一个类,这个类中存在大
量和系统打交道的实用方法,而且一般都是类方法,.getProperty(String key)就是其中一个比较常用的方
法,用于返回系统参数文件中这个方法指定键所代表的值。
1
2
3
4
5
|
var system
=
Java.use(
"java.lang.System"
);
system.getProperty.overload(
'java.lang.String'
).implementation
=
function (
str
) {
var re
=
this.getProperty(
str
);
return
"Russia"
;
}
|
0x03 第二关:Must be on the user whitelist.
根据上面的图片,这一关的关键是System.getenv("USER") = getResources().getString(R.string.User)即可通过下一关。通过上一关的分析我知道getenv的类是java.lang.String用样的方法hook修改返回值即可。那返回值是什么呢?从getResources大概可以猜测是资源文件,再跳转到R.string.User。
得到 User = 0x7f0d0000 应该是标记内存地址指向了某个元素,相互映射的关系,我们再搜索 0x7f0d0000
果然再资源文件里面,我再进行搜索。发现 对应的user字符串: RkxBR3s1N0VSTDFOR180UkNIM1J9Cg==
1
2
3
4
5
6
7
|
var system
=
Java.use(
"java.lang.System"
);
system.getenv.overload(
'java.lang.String'
).implementation
=
function (
str
) {
console.log(
"system.getenv : "
,
str
)
var re
=
this.getenv(
str
);
console.log(
"system.getenv.re : "
, re)
return
"RkxBR3s1N0VSTDFOR180UkNIM1J9Cg=="
;
}
|
0x04 第三关:User not recognized.
来到这一关是一个登录界面,随便输入账号密码,提示:User not recognized.,看来登录是有文章的,我们查看代码分析.
通过第二关的分析,我们很容容易找到了R.string.username = codenameduchess
1
|
adb shell
input
text
'codenameduchess'
|
0x05 第四关:Incorrect password.
随便输入密码 会提示 Incorrect password.,查看上面的图片我们可以知道,j()这函数是关键,满足true这个条件,即可跳入到下一关。
让结果强行改true.
1
2
3
4
5
|
var LoginActivity
=
Java.use(
"com.tlamb96.kgbmessenger.LoginActivity"
);
LoginActivity[
"j"
].implementation
=
function () {
var ret
=
this.j();
return
true;
};
|
0x06 第五关:V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003
来到这一关,我们进入到一个消息聊天界面,发送消息是没有反馈的,看代码:
this.o.add(new com.tlamb96.kgbmessenger.b.a(R.string.user, obj, j(), false));这个是发送消息的模板,true是对方发送的,flase代表我发发送的消息。当然了这段代码和解密无关,但是地了解它的发送逻辑,排除掉无关的代码。a(obj.toString()).equals(this.p)是闯关的关键。 p="V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003";输入的值经过a方法,返回值等于p值即可进入下一关。
a()
1
2
3
4
5
6
7
8
9
|
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);
}
|
这个a方法是一个算法,只要我们反推出这个算法,就知道应该输入什么值,遇到这样的问题,我觉得我们应该先还原算法,不要硬反推,应该这样很烧脑,很浪费时间,正向还原比反向推导要简单的多,我们安装他的逻辑进行编写即可。
再写算法之前,我们先了解下python的异或^运算。
1
2
3
|
chr
()将数字(
10
进制)转化为中文
ord
() 将中文转化为数字(
10
进制)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def
a(str_m):
charArray2
=
list
(str_m)
for
ii
in
range
(
0
,
int
(
len
(charArray2)
/
2
)):
cc
=
charArray2[ii]
print
(
"ii "
, ii, cc,
" charArray2[ii] :"
,
len
(charArray2)
-
ii
-
1
, charArray2[
len
(charArray2)
-
ii
-
1
],
chr
(
ord
(charArray2[
len
(charArray2)
-
ii
-
1
]) ^
ord
(
'2'
)),
"charArray2[len(charArray2) - ii - 1] :"
,
len
(charArray2)
-
ii
-
1
,
chr
(
ord
(cc) ^
ord
(
'A'
)))
charArray2[ii]
=
chr
(
ord
(charArray2[
len
(charArray2)
-
ii
-
1
]) ^
ord
(
'2'
))
charArray2[
len
(charArray2)
-
ii
-
1
]
=
chr
(
ord
(cc) ^
ord
(
'A'
))
charArrayStr2
=
"".join(charArray2)
print
(
"charArrayStr2:"
, charArrayStr2)
return
charArrayStr2
if
__name__
=
=
'__main__'
:
str_m
=
"abcdef"
|
"abcdef" 经过加密得到 #TWV"# ,当然了得必须验证下你得算法是否正确,万一写错误,不是陷入到了死胡同?所有hook a方法,输入 abcdef 看看打印的结果是不是TWV"#
1
2
3
4
5
6
7
8
9
10
11
|
var MessengerActivity
=
Java.use(
"com.tlamb96.kgbmessenger.MessengerActivity"
);
MessengerActivity[
"a"
].implementation
=
function (
str
) {
console.log(
'a is called'
+
', '
+
'str: '
+
str
);
var ret
=
this.a(
str
);
console.log(
'a ret value is '
+
ret);
return
ret;
};
结果:
a
is
called,
str
: abcdef
a ret value
is
TWV"
|
得到了正向的加密,反推就简单多了,因为写了一遍,再反推和没写直接推演是不一样的。在这里我们要注意下 ^ 这个值的还原。记住这一点:当然也不一定对,欢迎批评指正,互相学习。
1
2
|
求y : x ^
'值'
=
y
求x : y ^
'值'
=
x
|
反推算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def
a_jie(str_m):
str_m_fanzhuan
=
str_m[::
-
1
]
charArray2
=
list
(str_m_fanzhuan)
for
ii
in
range
(
0
,
int
(
len
(charArray2)
/
2
)):
cc
=
charArray2[ii]
charArray2[ii]
=
chr
(
ord
(charArray2[
len
(charArray2)
-
ii
-
1
]) ^
ord
(
'2'
))
charArray2[
len
(charArray2)
-
ii
-
1
]
=
chr
(
ord
(cc) ^
ord
(
'A'
))
charArrayStr2
=
"".join(charArray2)
print
(
"charArrayStr2:"
, charArrayStr2)
str_m_fanzhuan
=
charArrayStr2[::
-
1
]
print
(str_m_fanzhuan)
return
str_m_fanzhuan
|
我们输入 TWV"# 得到值 :abcedf 完全正确。这个时候我们再输入p值:
1
2
3
4
5
|
ss
=
"V@]EAASB\u0012WZF\u0012e,a$7(&am2(3.\u0003"
re
=
a_jie(ss)
返回结果: Boris, give me the password
|
输入 : Boris, give me the password 即可进入到下一关。
0x07 第六关:\u0000dslp}oQ\u0000 dks
∣M0˘000h+AYQg0˘000P∗!MgQ\u0000
这一关的思路和第五关一样,他们的风控手段也是雷同的。主要是 b(String str) = r = "\u0000dslp}oQ\u0000 dks
∣M0˘000h+AYQg0˘000P∗!MgQ\u0000";
b() 源码:
1
2
3
4
5
6
7
8
9
10
11
12
|
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方法python正向还原:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def
b(str_m):
charArray
=
list
(str_m)
charArray2
=
list
(str_m)
for
i
in
range
(
len
(charArray)):
charArray[i]
=
chr
((
ord
(charArray[i]) >> (i
%
8
)) ^
ord
(charArray[i]))
print
(i,charArray2[i],charArray[i])
print
(charArray)
for
i2
in
range
(
int
(
len
(charArray)
/
2
)):
c
=
charArray[i2]
charArray[i2]
=
charArray[(
len
(charArray)
-
i2)
-
1
]
charArray[(
len
(charArray)
-
i2)
-
1
]
=
c
print
(i2,c, charArray[i2],
len
(charArray)
-
i2
-
1
,charArray[(
len
(charArray)
-
i2)
-
1
])
print
(charArray)
charArrayStr2
=
"".join(charArray)
return
charArrayStr2
|
我们发现以下代码是主要的加密位置,后面只是实现了一个倒序,只是他实现的过程比较复杂,如果用python 实现这个逻辑只要一行代码就行。废话不多说,只要实现以下代码的反推逻辑即可破解。
1
2
3
4
|
char[] charArray
=
str
.toCharArray();
for
(
int
i
=
0
; i < charArray.length; i
+
+
) {
charArray[i]
=
(char) ((charArray[i] >> (i
%
8
)) ^ charArray[i]);
}
|
反推后的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def
b_jie(str_m):
str_m
=
"\000dslp}oQ\000 dks$|M\000h +AYQg\000P*!M$gQ\000"
charArray
=
list
(
str
(str_m))
print
(str_m)
charArray.reverse()
print
(charArray)
for
i
in
range
(
len
(charArray)):
if
i
%
8
=
=
0
:
print
(
"_"
, end
=
"")
continue
for
ch
in
string.printable:
final_ch
=
chr
((
ord
(ch) >> (i
%
8
)) ^
ord
(ch))
if
final_ch
=
=
charArray[i]:
print
(ch, end
=
"")
结果:
[
'\x00'
,
'Q'
,
'g'
,
'$'
,
'M'
,
'!'
,
'*'
,
'P'
,
'\x00'
,
'g'
,
'Q'
,
'Y'
,
'A'
,
'+'
,
' '
,
'h'
,
'\x00'
,
'M'
,
'|'
,
'$'
,
's'
,
'k'
,
'd'
,
' '
,
'\x00'
,
'Q'
,
'o'
,
'}'
,
'p'
,
'l'
,
's'
,
'd'
,
'\x00'
]
_ay I
*
P_EASE
*
h_ve the _assword_
|
得到 : 补全单词:May I PLEASE have the password
总体来说hook的逻辑还是简单的,代码相信大家都能看得懂,唯一的难度就是a和b方法的加密算法的还原。
更多【Frida 实战 KGB Messenger】相关视频教程:www.yxfzedu.com