业余时间写的,纯兴趣更新!
运行apk,随便输入,点击确定,弹框错误。
将其拖入jeb中,查看类MainActivity中的onCreate方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public void onCreate(Bundle arg3) {
super
.onCreate(arg3);
this.setContentView(
0x7F040019
);
this.setTitle(
0x7F06001D
);
this.edit_userName
=
"Tenshine"
;
this.edit_sn
=
this.findViewById(
0x7F0C0051
);
this.btn_register
=
this.findViewById(
0x7F0C0052
);
this.btn_register.setOnClickListener(new View$OnClickListener() {
public void onClick(View arg5) {
if
(!MainActivity.this.checkSN(MainActivity.this.edit_userName.trim(), MainActivity.this.edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this,
0x7F06001E
,
0
).show();
}
else
{
Toast.makeText(MainActivity.this,
0x7F06001B
,
0
).show();
MainActivity.this.btn_register.setEnabled(false);
MainActivity.this.setTitle(
0x7F060019
);
}
}
});
}
|
即调用了checkSN方法。参数1为 "Tenshine",参数2为我们输入的字符串。
看下checkSN的实现(具体看代码中的分析)
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
|
private boolean checkSN(String str1, String input_str) {
boolean v7
=
false;
if
(str1 !
=
null) {
try
{
if
(str1.length()
=
=
0
) {
return
v7;
}
if
(input_str
=
=
null) {
return
v7;
}
if
(input_str.length() !
=
22
) {
return
v7;
}
MessageDigest v1
=
MessageDigest.getInstance(
"MD5"
);
v1.reset();
v1.update(str1.getBytes());
/
/
对参数
1
进行md5加密
String v3
=
MainActivity.toHexString(v1.digest(), "");
/
/
并赋值给v3
StringBuilder v5
=
new StringBuilder();
int
v4;
for
(v4
=
0
; v4 < v3.length(); v4
+
=
2
) {
/
/
将v3密文每隔一位进行舍弃
v5.append(v3.charAt(v4));
}
if
(!
"flag{"
+
v5.toString()
+
"}"
.equalsIgnoreCase(input_str)) {
return
v7;
/
/
然后拼接上flag{}再与我们的输入将进行比较。
}
}
catch(NoSuchAlgorithmException v2) {
goto label_40;
}
v7
=
true;
}
return
v7;
label_40:
v2.printStackTrace();
return
v7;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#coding:utf8
import
hashlib
str1
=
b
"Tenshine"
str2
=
hashlib.md5(str1).hexdigest()
print
(str2)
#b9c77224ff234f27ac6badf83b855c76
flag
=
""
for
i
in
range
(
len
(str2)):
if
(i
%
2
=
=
0
):
flag
+
=
str2[i]
#flag="flag{"+flag+"}"
print
(flag)
#bc72f242a6af3857
|
安装apk安装失败
将其拖入jadx发现AndroidManifest.xml清单文件没有内容
应该是AndroidManifest.xml文件被做了手脚。将其提取出来进行修改。
apktool提取失败。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
>apktool d APK逆向
-
2.apk
I: Using Apktool
2.6
.
1
on APK逆向
-
2.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
Exception
in
thread
"main"
brut.androlib.err.RawXmlEncounteredException: Could
not
decode XML
at brut.androlib.res.decoder.XmlPullStreamDecoder.decode(XmlPullStreamDecoder.java:
145
)
at brut.androlib.res.decoder.XmlPullStreamDecoder.decodeManifest(XmlPullStreamDecoder.java:
151
)
at brut.androlib.res.decoder.ResFileDecoder.decodeManifest(ResFileDecoder.java:
159
)
at brut.androlib.res.AndrolibResources.decodeManifestWithResources(AndrolibResources.java:
193
)
at brut.androlib.Androlib.decodeManifestWithResources(Androlib.java:
141
)
at brut.androlib.ApkDecoder.decode(ApkDecoder.java:
109
)
at brut.apktool.Main.cmdDecode(Main.java:
175
)
at brut.apktool.Main.main(Main.java:
79
)
Caused by: java.io.IOException: Expected:
0x001c0001
, got:
0x01001c00
at brut.util.ExtDataInput.skipCheckChunkTypeInt(ExtDataInput.java:
72
)
at brut.androlib.res.decoder.StringBlock.read(StringBlock.java:
50
)
at brut.androlib.res.decoder.AXmlResourceParser.doNext(AXmlResourceParser.java:
814
)
at brut.androlib.res.decoder.AXmlResourceParser.
next
(AXmlResourceParser.java:
98
)
at brut.androlib.res.decoder.AXmlResourceParser.nextToken(AXmlResourceParser.java:
108
)
at org.xmlpull.v1.wrapper.classic.XmlPullParserDelegate.nextToken(XmlPullParserDelegate.java:
105
)
at brut.androlib.res.decoder.XmlPullStreamDecoder.decode(XmlPullStreamDecoder.java:
138
)
...
7
more
|
把apk后缀改成zip,将其拉出来。
找三个正常的清单文件(apk逆向,app1,app2),查看它们文件头哪些是共同的地方。
1
2
3
4
|
第一行:偏移
0x0
-
0x3
0x6
-
0xB
0xE
-
0xF
第二行:偏移
0x1
-
0xB
0xE
-
0xF
第三行:偏移
0x1
-
0xF
第四行:偏移
0x1
-
0x7
0x9
-
0xB
0xD
-
0xF
|
然后再查看下本题
1
2
3
4
|
第一行:偏移
0x0
-
0x3
(一致)
0x6
-
0xB
(不一致
0x8
-
0xB
刚好反转)
0xE
-
0xF
(一致)
第二行:偏移
0x1
-
0xB
(一致)
0xD
-
0xF
(一致)
第三行:偏移
0x1
-
0xF
(不一致
0x2
位置变成
1
了)
第四行:偏移
0x1
-
0x7
(一致)
0x9
-
0xB
(一致)
0xD
-
0xF
(一致)
|
所以这里我们将不一致的地方改为一致,即第一行的偏移0x6-0xB位置和第三行的偏移为偏移0x1-0xF
将其再覆盖掉apk中的清单文件,然后便可以安装成功了.
但安装成功后,却没有显示桌面图标.使用am进行打开Mainactivity界面
1
|
am start com.example.mmsheniq
/
.Mainactivity
|
同时看下logcat
先不点安装,点空白处。
点击注册
这里填过信息后点注册一直报错"请输入正确的身份证"。
本想好好分析下代码,可我懒死了.
修复过AndroidManifest.xml文件后拖入jadx中,再看下清单文件中的内容。发现可疑的字符串,其实就是flag。
1
|
8d6efd232c63b7d2
|
附件是个zip压缩包,解压后
查看FoundItFun.docx,发现只有一句话。
jadx
1
2
3
4
5
6
7
8
9
10
11
12
|
this.btn_register.setOnClickListener(new View.OnClickListener() {
/
/
from
class
: com.droider.crackme0201.MainActivity.
1
@Override
/
/
android.view.View.OnClickListener
public void onClick(View v) {
if
(!MainActivity.this.checkSN(MainActivity.this.edit_userName.getText().toString().trim(), MainActivity.this.edit_sn.getText().toString().trim())) {
Toast.makeText(MainActivity.this, (
int
) R.string.unsuccessed,
0
).show();
return
;
}
Toast.makeText(MainActivity.this, (
int
) R.string.successed,
0
).show();
MainActivity.this.btn_register.setEnabled(false);
MainActivity.this.setTitle(R.string.registered);
}
});
|
水啊水啊,如果checkSN方法返回true,则注册成功。
checkSN
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
|
public boolean checkSN(String userName, String sn) {
try
{
if
(userName.length()
=
=
0
&& sn.length()
=
=
0
) {
/
/
什么都不输入就返回true 哈哈
return
true;
}
if
(userName
=
=
null || userName.length()
=
=
0
) {
return
false;
}
if
(sn
=
=
null || sn.length() !
=
16
) {
return
false;
}
MessageDigest digest
=
MessageDigest.getInstance(
"MD5"
);
digest.reset();
digest.update(userName.getBytes());
/
/
对用户名进行md5加密
byte[] bytes
=
digest.digest();
String hexstr
=
toHexString(bytes, BuildConfig.FLAVOR);
/
/
BuildConfig.FLAVOR为空字符串
StringBuilder sb
=
new StringBuilder();
for
(
int
i
=
0
; i < hexstr.length(); i
+
=
2
) {
sb.append(hexstr.charAt(i));
/
/
隔一位取一位
}
String userSN
=
sb.toString();
return
userSN.equalsIgnoreCase(sn);
/
/
用户名经过上面的加密后就再与输入的注册码sn相等即返回true
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return
false;
}
}
|
什么都不输入就会返回true的,返回successed对应的字符串。
1
|
<string name
=
"successed"
>md5:b3241668ecbeb19921fdac5ac1aafa69<
/
string>
|
对其进行解密得到
1
|
YOU_KNOW_
|
但最终的flag需要在后面再添加上ANDROID即
1
|
YOU_KNOW_ANDROID
|
脑洞,无聊。
jadx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.etFlag
=
(EditText) findViewById(R.
id
.flag_edit);
}
public void onGoClick(View v) {
String sInput
=
this.etFlag.getText().toString();
if
(getSecret(getFlag()).equals(getSecret(encrypt(sInput)))) {
Toast.makeText(this,
"Success"
,
1
).show();
}
else
{
Toast.makeText(this,
"Failed"
,
1
).show();
}
}
|
如果getSecret(getFlag())的返回值和getSecret(encrypt(sInput)))相等就证明Success。
即getFlag()的返回值和encrypt(sInput))返回值相等,就证明Success。
这这两个函数都是在phcm的so库中实现的。
1
2
3
4
5
6
7
|
public native String encrypt(String
str
);
public native String getFlag();
static {
System.loadLibrary(
"phcm"
);
}
|
所以分析下so库中的这两个函数
首先这里是getFlag函数,静态分析太累,动态分析太懒,这里其实不用取分析这个算法,它的返回值是固定的,所以直接用frida去hook这个函数打印出它的返回值就好了,也可以通过向smali代码中插入log去打印。
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
|
int
__fastcall Java_com_ph0en1x_android_1crackme_MainActivity_getFlag(_JNIEnv
*
env)
{
char
*
v1;
/
/
r4
_JNIEnv
*
v2;
/
/
r7
char
*
v3;
/
/
r3
int
v4;
/
/
r0
int
v5;
/
/
r1
char
*
v6;
/
/
r2
const char
*
v7;
/
/
r3
int
v8;
/
/
r0
int
v9;
/
/
r1
int
v10;
/
/
r4
int
v11;
/
/
r0
__int16 v12;
/
/
r3
signed
int
v13;
/
/
r8
signed
int
v14;
/
/
r0
char
*
v15;
/
/
r9
char v16;
/
/
r3
char v17;
/
/
t1
int
v18;
/
/
r1
char s;
/
/
[sp
+
4h
] [bp
-
5Ch
]
char v21[
40
];
/
/
[sp
+
14h
] [bp
-
4Ch
]
char v22;
/
/
[sp
+
40h
] [bp
-
20h
]
v1
=
v21;
v2
=
env;
v3
=
(char
*
)&dword_2770;
do
{
v4
=
*
(_DWORD
*
)v3;
v3
+
=
8
;
v5
=
*
((_DWORD
*
)v3
-
1
);
*
(_DWORD
*
)v1
=
v4;
*
((_DWORD
*
)v1
+
1
)
=
v5;
v1
+
=
8
;
}
while
( v3 !
=
"Hello Ph0en1x"
);
v6
=
&s;
v7
=
"Hello Ph0en1x"
;
do
{
v8
=
*
(_DWORD
*
)v7;
v7
+
=
8
;
v9
=
*
((_DWORD
*
)v7
-
1
);
*
(_DWORD
*
)v6
=
v8;
*
((_DWORD
*
)v6
+
1
)
=
v9;
v10
=
(
int
)(v6
+
8
);
v6
+
=
8
;
}
while
( v7 !
=
"0en1x"
);
v11
=
*
(_DWORD
*
)v7;
v12
=
*
((_WORD
*
)v7
+
2
);
*
(_DWORD
*
)v10
=
v11;
*
(_WORD
*
)(v10
+
4
)
=
v12;
v13
=
strlen(&s);
v14
=
strlen(v21)
-
1
;
v15
=
&v21[v14];
while
( v14 >
0
)
{
v16
=
*
v15
+
1
;
*
v15
=
v16;
v17
=
*
(v15
-
-
-
1
);
v18
=
v14
-
-
%
v13;
v15[
1
]
=
((v16
-
v17) ^
*
(&v22
+
v18
-
60
))
-
1
;
}
v21[
0
]
=
(v21[
0
] ^
0x48
)
-
1
;
return
((
int
(__fastcall
*
)(_JNIEnv
*
, char
*
))v2
-
>functions
-
>NewStringUTF)(v2, v21);
}
|
然后看encrypt函数,将每个字符对应的ascii进行减一。
1
2
3
4
5
6
7
8
9
10
11
12
|
int
__fastcall Java_com_ph0en1x_android_1crackme_MainActivity_encrypt(_JNIEnv
*
a1)
{
_JNIEnv
*
v1;
/
/
r6
const char
*
input_str;
/
/
r4
const char
*
i;
/
/
r5
v1
=
a1;
input_str
=
(const char
*
)((
int
(
*
)(void))a1
-
>functions
-
>GetStringUTFChars)();
for
( i
=
input_str; i
-
input_str < strlen(input_str);
+
+
i )
-
-
*
i;
return
((
int
(__fastcall
*
)(_JNIEnv
*
, const char
*
))v1
-
>functions
-
>NewStringUTF)(v1, input_str);
}
|
所以这里只要首先获得getFlag函数的返回值,然后再将返回值的每个字符对应的ascii进行加一即可。
利用frida去hook native层的getFlag方法的返回值。
run.py
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
|
#coding:utf8
import
sys
import
frida
#process_name = 'infosecadventures.fridademo'
process_name
=
'Android_crackme'
#process_name = 20035
# 发送信息回调函数
def
on_message(message, data):
if
message[
'type'
]
=
=
'send'
:
print
(f
"[*] {message['payload']}"
)
else
:
print
(message)
if
__name__
=
=
'__main__'
:
try
:
device
=
frida.get_usb_device(timeout
=
1000
)
print
(
"* get usb device成功"
)
except
:
device
=
frida.get_remote_device(timeout
=
1000
)
print
(
"* get remote device成功"
)
if
not
device:
print
(
"* 连接到Frida Server失败"
)
else
:
process
=
device.attach(process_name)
#pid = device.spawn(["infosecadventures.fridademo"])
#process = device.attach(pid)
# 加载JS脚本
js
=
open
(
'hook.js'
, encoding
=
'utf-8'
).read()
print
(js)
script
=
process.create_script(js)
script.on(
'message'
, on_message)
script.load()
# 读取返回输入
input
()
script.unload()
|
hook.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Java.perform(function() {
/
/
获取指定类
var
cls
=
Java.use(
'com.ph0en1x.android_crackme.MainActivity'
);
/
/
Hook指定函数
cls
.getFlag.overload().implementation
=
function() {
/
/
进入函数
console.log(
'getFlag-in'
);
/
/
调用原函数
var result
=
this.getFlag();
/
/
打印出参
console.log(
'getFlag-out'
, result);
/
/
返回给原函数的调用
return
result;
}
});
|
成功将返回值给打印出来。
1
|
ek`fz@q2^x
/
t^fn0mF^
6
/
^rb`qanqntfg^E`hq|
|
然后再将返回值的每个字符对应的ascii进行加一便是我们的输入即flag
1
2
3
4
5
6
|
#coding:utf8
flag
=
""
str1
=
"ek`fz@q2^x/t^fn0mF^6/^rb`qanqntfg^E`hq|"
for
i
in
range
(
len
(str1)):
flag
+
=
chr
(
ord
(str1[i])
+
1
)
print
(flag)
#flag{Ar3_y0u_go1nG_70_scarborough_Fair}
|
上面其实是hook的java层,也可以去hook native层,效果是一样的
native_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
|
console.log(
"脚本载入成功"
);
Java.perform(function () {
var getflagAddr
=
Module.findExportByName(
"libphcm.so"
,
"Java_com_ph0en1x_android_1crackme_MainActivity_getFlag"
);
console.log(getflagAddr);
if
(getflagAddr !
=
null) {
Interceptor.attach(getflagAddr, {
onEnter: function (args) {
/
/
args参数数组
console.log(
'encode-Enter'
)
/
/
console.log(args[
0
], Memory.readCString(args[
0
]));
/
/
console.log(args[
1
], Memory.readCString(args[
1
]));
/
/
console.log(args[
2
], Memory.readCString(args[
2
]));
/
/
console.log(args[
3
], Memory.readCString(args[
3
]));
/
/
console.log(args[
4
], Memory.readCString(args[
4
]));
},
onLeave: function (retval) {
/
/
retval函数返回值
console.log(
'encode-Leave'
);
var String_java
=
Java.use(
'java.lang.String'
);
/
/
因为so库中getFlag函数返回的时候是
/
/
return
((
int
(__fastcall
*
)(_JNIEnv
*
, char
*
))v2
-
>functions
-
>NewStringUTF)(v2, v21);
var args_4
=
Java.cast(retval,String_java);
/
/
console.log(args_4);
send(
"getFlag()==>"
+
args_4);
/
/
console.log(retval.toString());
console.log(
'======'
);
}
});
}
})
|
这里我们也尝试下通过改smali代码的方式将这个返回值给打印出来吧。
1
|
invoke
-
static {v1, v0}, Landroid
/
util
/
Log;
-
>d(Ljava
/
lang
/
String;Ljava
/
lang
/
String;)I
|
重新编译签名安装运行。成功将其打印出来。
后面就一样了。
打开后
### 分析
jadx(太好用了吧)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.example.application;
import
android.app.Activity;
import
android.content.IntentFilter;
import
android.os.Bundle;
import
android.widget.TextView;
import
com.example.hellojni.Manifest;
/
*
loaded
from
: classes.dex
*
/
public
class
MainActivity extends Activity {
@Override
/
/
android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
TextView tv
=
new TextView(getApplicationContext());
tv.setText(
"Select the activity you wish to interact with.To-Do: Add buttons to select activity, for now use Send_to_Activity"
);
setContentView(tv);
IntentFilter
filter
=
new IntentFilter();
filter
.addAction(
"com.ctf.INCOMING_INTENT"
);
Send_to_Activity receiver
=
new Send_to_Activity();
registerReceiver(receiver,
filter
, Manifest.permission._MSG, null);
/
/
注册个广播接收者
}
}
|
然后看下这个广播接收者
主要做的就是接收传过来msg值,根据值的不同会跳转到不同的Activity
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
|
package com.example.application;
import
android.content.BroadcastReceiver;
import
android.content.Context;
import
android.content.Intent;
import
android.widget.Toast;
/
*
loaded
from
: classes.dex
*
/
public
class
Send_to_Activity extends BroadcastReceiver {
@Override
/
/
android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String msgText
=
intent.getStringExtra(
"msg"
);
if
(msgText.equalsIgnoreCase(
"ThisIsTheRealOne"
)) {
Intent outIntent
=
new Intent(context, ThisIsTheRealOne.
class
);
context.startActivity(outIntent);
}
else
if
(msgText.equalsIgnoreCase(
"IsThisTheRealOne"
)) {
Intent outIntent2
=
new Intent(context, IsThisTheRealOne.
class
);
context.startActivity(outIntent2);
}
else
if
(msgText.equalsIgnoreCase(
"DefinitelyNotThisOne"
)) {
Intent outIntent3
=
new Intent(context, DefinitelyNotThisOne.
class
);
context.startActivity(outIntent3);
}
else
{
Toast.makeText(context,
"Which Activity do you wish to interact with?"
,
1
).show();
}
}
}
|
ThisIsTheRealOne.class
调用了hello-jni.so中的orThat方法,参数都是固定的,没有用户输入。
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
|
package com.example.application;
import
android.app.Activity;
import
android.content.Intent;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.Button;
import
android.widget.TextView;
import
com.example.hellojni.Manifest;
import
com.example.hellojni.R;
/
*
loaded
from
: classes.dex
*
/
public
class
ThisIsTheRealOne extends Activity {
public native String computeFlag(String
str
, String str2);
public native String definitelyNotThis(String
str
, String str2, String str3);
public native String orThat(String
str
, String str2, String str3);
public native String perhapsThis(String
str
, String str2, String str3);
@Override
/
/
android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
TextView tv
=
new TextView(this);
tv.setText(
"Activity - This Is The Real One"
);
Button button
=
new Button(this);
button.setText(
"Broadcast Intent"
);
setContentView(button);
button.setOnClickListener(new View.OnClickListener() {
/
/
from
class
: com.example.application.ThisIsTheRealOne.
1
@Override
/
/
android.view.View.OnClickListener
public void onClick(View v) {
Intent intent
=
new Intent();
intent.setAction(
"com.ctf.OUTGOING_INTENT"
);
String a
=
ThisIsTheRealOne.this.getResources().getString(R.string.str2)
+
"YSmks"
;
String b
=
Utilities.doBoth(ThisIsTheRealOne.this.getResources().getString(R.string.dev_name));
String c
=
Utilities.doBoth(getClass().getName());
intent.putExtra(
"msg"
, ThisIsTheRealOne.this.orThat(a, b, c));
ThisIsTheRealOne.this.sendBroadcast(intent, Manifest.permission._MSG);
}
});
}
static {
System.loadLibrary(
"hello-jni"
);
}
}
|
IsThisTheRealOne.class
调用了hello-jni.so中的perhapsThis方法,参数也都是固定的,没有用户输入。
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
|
package com.example.application;
import
android.app.Activity;
import
android.content.Intent;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.Button;
import
android.widget.TextView;
import
com.example.hellojni.Manifest;
import
com.example.hellojni.R;
/
*
loaded
from
: classes.dex
*
/
public
class
IsThisTheRealOne extends Activity {
public native String computeFlag(String
str
, String str2);
public native String definitelyNotThis(String
str
, String str2, String str3);
public native String orThat(String
str
, String str2, String str3);
public native String perhapsThis(String
str
, String str2, String str3);
@Override
/
/
android.app.Activity
public void onCreate(Bundle savedInstanceState) {
getApplicationContext();
super
.onCreate(savedInstanceState);
TextView tv
=
new TextView(this);
tv.setText(
"Activity - Is_this_the_real_one"
);
Button button
=
new Button(this);
button.setText(
"Broadcast Intent"
);
setContentView(button);
button.setOnClickListener(new View.OnClickListener() {
/
/
from
class
: com.example.application.IsThisTheRealOne.
1
@Override
/
/
android.view.View.OnClickListener
public void onClick(View v) {
Intent intent
=
new Intent();
intent.setAction(
"com.ctf.OUTGOING_INTENT"
);
String a
=
IsThisTheRealOne.this.getResources().getString(R.string.str3)
+
"\\VlphgQbwvj~HuDgaeTzuSt.@Lex^~"
;
String b
=
Utilities.doBoth(IsThisTheRealOne.this.getResources().getString(R.string.app_name));
String name
=
getClass().getName();
String c
=
Utilities.doBoth(name.substring(
0
, name.length()
-
2
));
intent.putExtra(
"msg"
, IsThisTheRealOne.this.perhapsThis(a, b, c));
IsThisTheRealOne.this.sendBroadcast(intent, Manifest.permission._MSG);
}
});
}
static {
System.loadLibrary(
"hello-jni"
);
}
}
|
DefinitelyNotThisOne.class
调用了hello-jni.so中的definitelyNotThis方法,参数也都是固定的,没有用户输入。
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
|
package com.example.application;
import
android.app.Activity;
import
android.content.Intent;
import
android.os.Bundle;
import
android.view.View;
import
android.widget.Button;
import
android.widget.TextView;
import
com.example.hellojni.Manifest;
import
com.example.hellojni.R;
/
*
loaded
from
: classes.dex
*
/
public
class
DefinitelyNotThisOne extends Activity {
public native String computeFlag(String
str
, String str2);
public native String definitelyNotThis(String
str
, String str2);
public native String orThat(String
str
, String str2, String str3);
public native String perhapsThis(String
str
, String str2, String str3);
@Override
/
/
android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
TextView tv
=
new TextView(this);
tv.setText(
"Activity - Is_this_the_real_one"
);
Button button
=
new Button(this);
button.setText(
"Broadcast Intent"
);
setContentView(button);
button.setOnClickListener(new View.OnClickListener() {
/
/
from
class
: com.example.application.DefinitelyNotThisOne.
1
@Override
/
/
android.view.View.OnClickListener
public void onClick(View v) {
Intent intent
=
new Intent();
intent.setAction(
"com.ctf.OUTGOING_INTENT"
);
DefinitelyNotThisOne.this.getResources().getString(R.string.str1);
String b
=
Utilities.doBoth(DefinitelyNotThisOne.this.getResources().getString(R.string.test));
String c
=
Utilities.doBoth(
"Test"
);
intent.putExtra(
"msg"
, DefinitelyNotThisOne.this.definitelyNotThis(b, c));
DefinitelyNotThisOne.this.sendBroadcast(intent, Manifest.permission._MSG);
}
});
}
static {
System.loadLibrary(
"hello-jni"
);
}
}
|
学了hook后就懒了呢,因为整个程序都没有用户输入,flag肯定是在某个地方进行生成的,猜测是so库中的definitelyNotThis函数返回值或者是orThat函数返回值或者perhapsThis返回值。那我们直接在java层hook这三个函数好了
java_hook.py
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
|
Java.perform(function() {
/
/
/
/
获取指定类
/
/
var
cls
=
Java.use(
'com.ph0en1x.android_crackme.MainActivity'
);
/
/
/
/
Hook指定函数
/
/
cls
.getFlag.overload().implementation
=
function() {
/
/
/
/
进入函数
/
/
console.log(
'getFlag-in'
);
/
/
/
/
调用原函数
/
/
var result
=
this.getFlag();
/
/
/
/
打印出参
/
/
console.log(
'getFlag-out'
, result);
/
/
/
/
返回给原函数的调用
/
/
return
result;
/
/
}
let DefinitelyNotThisOne
=
Java.use(
"com.example.application.DefinitelyNotThisOne"
);
DefinitelyNotThisOne[
"definitelyNotThis"
].implementation
=
function (
str
, str2) {
console.log(
'definitelyNotThis is called'
+
', '
+
'str: '
+
str
+
', '
+
'str2: '
+
str2);
let ret
=
this.definitelyNotThis(
str
, str2);
console.log(
'definitelyNotThis ret value is '
+
ret);
return
ret;
};
let IsThisTheRealOne
=
Java.use(
"com.example.application.IsThisTheRealOne"
);
IsThisTheRealOne[
"perhapsThis"
].implementation
=
function (
str
, str2, str3) {
console.log(
'perhapsThis is called'
+
', '
+
'str: '
+
str
+
', '
+
'str2: '
+
str2
+
', '
+
'str3: '
+
str3);
let ret
=
this.perhapsThis(
str
, str2, str3);
console.log(
'perhapsThis ret value is '
+
ret);
return
ret;
};
let ThisIsTheRealOne
=
Java.use(
"com.example.application.ThisIsTheRealOne"
);
ThisIsTheRealOne[
"orThat"
].implementation
=
function (
str
, str2, str3) {
console.log(
'orThat is called'
+
', '
+
'str: '
+
str
+
', '
+
'str2: '
+
str2
+
', '
+
'str3: '
+
str3);
let ret
=
this.orThat(
str
, str2, str3);
console.log(
'orThat ret value is '
+
ret);
return
ret;
};
});
|
但是现在我们还要解决个问题,我们需要跳转到上面的三个不同Activity中才可以触发hook函数,而看清单中,这三个activity都不是export,也就没办法使用am命令打开。
不过可以将它修改成export后再重新打包,或者将初始Activity指定为上面的其实一个Activity后重新打包。或者按程序逻辑发送不同的广播信息去跳转到不同的Activity中。
1
|
am broadcast
-
n
"com.example.hellojni/com.example.application.Send_to_Activity"
-
a com.ctf.INCOMING_INTENT
-
e
"msg"
ThisIsTheRealOne
|
这里记录下
-n后面是包名/类名
但是这个apk
包名如果写com.example.application 是不行的
写成com.example.hellojni才可以,填清单文件的包名
hook效果
1
2
3
4
|
orThat
is
called,
str
: IIjsWa}iyYSmks, str2: ODBkNTNhZjRmMGZmMWYtMzhhMDIzMmMwYjcwNzlhMTUwMDczOWNlYjhjMhUWYWYeMzYiZDFkMTY?
, str3: MhMhMGJhMTUhOGYWZThlZDQaYWJkYzkWZTktMTQhMjYhOTgiOTZkODgaNWRkZmFiZTciOGNlNDI?
orThat ret value
is
KeepTryingThisIsNotTheActivityYouAreLookingForButHereHaveSomeInternetPoints!
|
继续
1
|
am broadcast
-
n
"com.example.hellojni/com.example.application.Send_to_Activity"
-
a com.ctf.INCOMING_INTENT
-
e
"msg"
DefinitelyNotThisOne
|
效果
1
2
3
4
|
definitelyNotThis
is
called,
str
: YjYwYWZjMjRkMhVhZTQhZDIwZGFkNWJhMGZmZGYiYmQaMmFkMjBiMTEhNDAtMzMzMjdlZmEWNzU?
, str2: MzYwNjMeNjgxNWZkNGQeOTFhOTIhNDkiMDVhNDBkYTAyNWQtYhYxNWYwOTUxMzZiMTlmMzciMjM?
definitelyNotThis ret value
is
Told you so!
|
继续
1
|
am broadcast
-
n
"com.example.hellojni/com.example.application.Send_to_Activity"
-
a com.ctf.INCOMING_INTENT
-
e
"msg"
IsThisTheRealOne
|
效果
1
2
3
4
|
perhapsThis
is
called,
str
: TRytfrgooq|F{i
-
JovFBungFk\VlphgQbwvj~HuDgaeTzuSt.@Lex^~, str2: ZGFkNGIwYzIWYjEzMTUWNjVjNTVlNjZhOGJkNhYtODIyOGEaMTMWNmQaOTVjZjkhMzRjYmUzZGE?
, str3: MzQxZTZmZjAxMmIiMWUzNjUxMmRiYjIxNDUwYTUxMWItZGQzNWUtMzkyOWYyMmQeYjZmMzEaNDQ?
perhapsThis ret value
is
Congratulation!YouFoundTheRightActivityHereYouGo
-
CTF{IDontHaveABadjokeSorry}
|
ok,成功拿到flag.
1
|
CTF{IDontHaveABadjokeSorry}
|
上面是对java层进行的hook,下面是native进行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
28
29
30
31
32
33
34
35
36
37
38
|
function main() {
function getjstring(jstr) {
return
Java.vm.getEnv().getStringUtfChars(jstr, null).readCString();
}
Java.perform(function () {
console.log(
"脚本载入成功"
);
/
/
var so_addr
=
Module.findBaseAddress(
"libhello-jni.so"
);
var perhapsThis_addr
=
Module.findExportByName(
"libhello-jni.so"
,
"Java_com_example_application_IsThisTheRealOne_perhapsThis"
);
console.log(
"perhapsThis_addr"
, perhapsThis_addr);
Interceptor.attach(perhapsThis_addr, {
onEnter: function (args) {
console.log(
"perhapsThis_args:[1]"
, getjstring(args[
2
]),
"\n [2]"
, getjstring(args[
3
]),
"\n [3]"
, getjstring(args[
4
]),
"\n"
);
},
onLeave: function (retval) {
console.log(
"perhapsThis_result:"
, getjstring(retval));
},
});
Interceptor.attach(Module.findExportByName(
"libhello-jni.so"
,
"Java_com_example_application_ThisIsTheRealOne_orThat"
), {
onEnter: function (args) {
console.log(
"orThat_args:[1]"
, getjstring(args[
2
]),
"\n [2]"
, getjstring(args[
3
]),
"\n [3]"
, getjstring(args[
4
]),
"\n"
);
},
onLeave: function (retval) {
console.log(
"orThat_result:"
, getjstring(retval));
},
});
Interceptor.attach(Module.findExportByName(
"libhello-jni.so"
,
"Java_com_example_application_DefinitelyNotThisOne_definitelyNotThis"
), {
onEnter: function (args) {
console.log(
"definitelyNotThis_args:[1]"
, getjstring(args[
2
]),
"\n [2]"
, getjstring(args[
3
]),
"\n"
);
},
onLeave: function (retval) {
console.log(
"definitelyNotThis_result:"
, getjstring(retval));
},
});
});
}
setImmediate(main);
|
更多【攻防世界Mobile通关记录_apk逆向12_RememberOther_Ph0en1x-100_ill-intentions_wp】相关视频教程:www.yxfzedu.com