前言
业余时间写的,纯兴趣更新!
apk逆向
题目信息
运行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 | import hashlib
str1 = b "Tenshine"
str2 = hashlib.md5(str1).hexdigest()
print (str2)
flag = ""
for i in range ( len (str2)):
if (i % 2 = = 0 ):
flag + = str2[i]
print (flag)
|
APK逆向-2
题目信息
安装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。
RememberOther
题目信息
附件是个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>
|
对其进行解密得到
MD5免费在线解密破解_MD5在线加密-SOMD5
解题
但最终的flag需要在后面再添加上ANDROID即
脑洞,无聊。
Ph0en1x-100
题目信息
分析
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 | import sys
import frida
process_name = 'Android_crackme'
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)
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;
}
});
|
成功将返回值给打印出来。
然后再将返回值的每个字符对应的ascii进行加一便是我们的输入即flag
1 2 3 4 5 6 | flag = ""
for i in range ( len (str1)):
flag + = chr ( ord (str1[i]) + 1 )
print (flag)
|
上面其实是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
|
重新编译签名安装运行。成功将其打印出来。
后面就一样了。
ill-intentions
题目信息
打开后
### 分析
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) + "\\[email protected]^~" ;
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中。
(32条消息) 使用am命令发送带参数的广播和服务_sunxiaolin2016的博客-CSDN博客
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\[email protected]^~, 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);
|
[2022冬季班]《安卓高级研修班(网课)》月薪两万班招生中~