其他
巧解一道CTF Android题
本文为看雪论坛优秀文章
看雪论坛作者ID:白云精灵
1:jeb
2:ida
3:Pycharm
4:idea
5:010editor
6:frida
用到的办法是XOR解密。无须还原代码,穷举爆破。
原理:经过XOR异或加密的字符串都可以再次异或进行解密获得key。
我们获取一下最顶层activity。最顶层activity是com.gdufs.xman/.RegActivity。
package com.gdufs.xman;
import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Process;
import android.view.View.OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class RegActivity extends Activity {
private Button btn_reg;
private EditText edit_sn;
@Override // android.app.Activity
public void onCreate(Bundle arg3) {
super.onCreate(arg3);
this.setContentView(0x7F04001B); // layout:activity_reg
this.btn_reg = (Button)this.findViewById(0x7F0B0054); // id:button1
this.edit_sn = (EditText)this.findViewById(0x7F0B0055); // id:editText1
this.btn_reg.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View arg5) {
String sn = RegActivity.this.edit_sn.getText().toString().trim();
if(sn == null || sn.length() == 0) {
Toast.makeText(RegActivity.this, "您的输入为空", 0).show();
return;
}
((MyApp)RegActivity.this.getApplication()).saveSN(sn);
new AlertDialog.Builder(RegActivity.this).setTitle("回复").setMessage("您的注册码已保存").setPositiveButton("好吧", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface arg2, int arg3) {
Process.killProcess(Process.myPid());
}
}).show();
}
});
}
}
String sn = RegActivity.this.edit_sn.getText().toString().trim();
((MyApp)RegActivity.this.getApplication()).saveSN(sn);
package com.gdufs.xman;
import android.app.Application;
import android.util.Log;
public class MyApp extends Application {
public static int m;
static {
MyApp.m = 0;
System.loadLibrary("myjni");
}
public native void initSN() {
}
@Override // android.app.Application
public void onCreate() {
this.initSN();
Log.d("com.gdufs.xman m=", String.valueOf(MyApp.m));
super.onCreate();
}
public native void saveSN(String arg1) {
}
public native void work() {
}
}
jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
if ( !(*vm)->GetEnv(vm, (void **)&g_env, 65542) )
{
_android_log_print(2, "com.gdufs.xman", "JNI_OnLoad()");
native_class = (*(int (__fastcall **)(int, const char *))(*(_DWORD *)g_env + 24))(g_env, "com/gdufs/xman/MyApp");
if ( !(*(int (__fastcall **)(int, int, char **, int))(*(_DWORD *)g_env + 860))(g_env, native_class, off_5004, 3) )
{
_android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
return 65542;
}
_android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
}
return -1;
}
可以看到动态注册的函数。
var RevealNativeMethods = function() {
var pSize = Process.pointerSize;
var env = Java.vm.getEnv();
var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
var jclassAddress2NameMap = {};
function getNativeAddress(idx) {
return env.handle.readPointer().add(idx * pSize).readPointer();
}
// intercepting FindClass to populate Map<address, jclass>
Interceptor.attach(getNativeAddress(FindClassIndex), {
onEnter: function(args) {
jclassAddress2NameMap[args[0]] = args[1].readCString();
}
});
// RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977
Interceptor.attach(getNativeAddress(RegisterNatives), {
onEnter: function(args) {
for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) {
/*
https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
*/
var structSize = pSize * 3; // = sizeof(JNINativeMethod)
var methodsPtr = ptr(args[2]);
var signature = methodsPtr.add(i * structSize + pSize).readPointer();
var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr
var jClass = jclassAddress2NameMap[args[0]].split('/');
var methodName = methodsPtr.add(i * structSize).readPointer().readCString();
var str_name_so = "libmyjni.so"; //需要hook的so名
var n_addr_so = Module.findBaseAddress(str_name_so); //加载到内存后 函数地址 = so地址 + 函数偏移
console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({
module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol
package: jClass.slice(0, -1).join('.'),
class: jClass[jClass.length - 1],
method: methodName, // methodsPtr.readPointer().readCString(), // char* name
signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java
address: (fnPtr-n_addr_so).toString(16)
}), '\x1b[39;49;00m');
}
}
});
}
Java.perform(RevealNativeMethods);
[Redmi K20 Pro Premium Edition::com.gdufs.xman ]-> {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"initSN","signature":"()V","address":"13b1"}
{"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"saveSN","signature":"(Ljava/lang/String;)V","address":"11f9"}
{"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"work","signature":"()V","address":"14cd"}
int __fastcall n2(_DWORD *a1, int a2, int a3)
{
FILE *v5; // r7
_DWORD *v7; // r4
const char *v8; // r3
int v9; // r0
int v10; // r1
_WORD *v11; // r5
_DWORD *v12; // r0
int v13; // r4
int v14; // r3
signed int v15; // r6
const char *v16; // r9
char *v17; // r5
signed int v18; // r10
char v19; // r2
char v20; // r3
_BYTE v21[56]; // [sp+0h] [bp-38h] BYREF
v5 = fopen("/sdcard/reg.dat", "w+");
if ( !v5 )
return j___android_log_print(3, "com.gdufs.xman", byte_2DCA);
v7 = v21;
v8 = "W3_arE_whO_we_ARE";
do
{
v9 = *(_DWORD *)v8;
v8 += 8;
v10 = *((_DWORD *)v8 - 1);
*v7 = v9;
v7[1] = v10;
v11 = v7 + 2;
v7 += 2;
}
while ( v8 != "E" );
v12 = a1;
v13 = 2016;
*v11 = *(_WORD *)v8;
v14 = *a1;
v15 = 0;
v16 = (const char *)(*(int (__fastcall **)(_DWORD *, int, _DWORD))(v14 + 676))(v12, a3, 0);
v17 = (char *)v16;
v18 = strlen(v16);
while ( v15 < v18 )
{
if ( v15 % 3 == 1 )
{
v13 = (v13 + 5) % 16;
v19 = v21[v13 + 1];
}
else if ( v15 % 3 == 2 )
{
v13 = (v13 + 7) % 15;
v19 = v21[v13 + 2];
}
else
{
v13 = (v13 + 3) % 13;
v19 = v21[v13 + 3];
}
v20 = *v17;
++v15;
*v17++ = v20 ^ v19;
}
fputs(v16, v5);
return j_fclose(v5);
}
如下代码在sd卡目录创建了一个文件叫reg.dat。
v5 = fopen("/sdcard/reg.dat", "w+");
fputs(v16, v5);
*v17++ = v20 ^ v19;
拖入010editor,可以看到我们输入的是13个1,异或出13个数据。
new AlertDialog.Builder(RegActivity.this).setTitle("回复").setMessage("您的注册码已保存").setPositiveButton("好吧", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface arg2, int arg3) {
Process.killProcess(Process.myPid());
}
}).show();
<?xml version="1.0" encoding="UTF-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.gdufs.xman" platformBuildVersionCode="23" platformBuildVersionName="6.0-2704002" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/aaron" android:label="@string/app_name" android:name="com.gdufs.xman.MyApp" android:theme="@style/AppTheme">
<activity android:label="@string/app_name" android:name="com.gdufs.xman.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:label="@string/title_activity_reg" android:name="com.gdufs.xman.RegActivity"/>
</application>
</manifest>
package com.gdufs.xman;
import android.app.Activity;
import android.app.AlertDialog.Builder;
import android.content.ComponentName;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.Menu;
import android.view.View.OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity {
private Button btn1;
private static String workString;
public void doRegister() {
new AlertDialog.Builder(this).setTitle("注册").setMessage("Flag就在前方!").setPositiveButton("注册", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.gdufs.xman", "com.gdufs.xman.RegActivity"));
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
}
}).setNegativeButton("不玩了", new DialogInterface.OnClickListener() {
@Override // android.content.DialogInterface$OnClickListener
public void onClick(DialogInterface dialog, int which) {
Process.killProcess(Process.myPid());
}
}).show();
}
@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
String str2;
super.onCreate(savedInstanceState);
this.setContentView(0x7F04001A); // layout:activity_main
Log.d("com.gdufs.xman m=", "Xman");
this.getApplication();
int m = MyApp.m;
if(m == 0) {
str2 = "未注册";
}
else {
str2 = m == 1 ? "已注册" : "已混乱";
}
this.setTitle("Xman" + str2);
this.btn1 = (Button)this.findViewById(0x7F0B0054); // id:button1
this.btn1.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View v) {
MainActivity.this.getApplication();
if(MyApp.m == 0) {
MainActivity.this.doRegister();
return;
}
((MyApp)MainActivity.this.getApplication()).work();
Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
}
});
}
@Override // android.app.Activity
public boolean onCreateOptionsMenu(Menu menu) {
this.getMenuInflater().inflate(0x7F0D0000, menu); // menu:menu_main
return 1;
}
public void work(String str) {
MainActivity.workString = str;
}
}
if(m == 0) {
str2 = "未注册";
}
else {
str2 = m == 1 ? "已注册" : "已混乱";
}
if(MyApp.m == 0) {
MainActivity.this.doRegister();
return;
}
((MyApp)MainActivity.this.getApplication()).work();
Toast.makeText(MainActivity.this.getApplicationContext(), MainActivity.workString, 0).show();
public native void work() {
}
[Redmi K20 Pro Premium Edition::com.gdufs.xman ]-> {"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"initSN","signature":"()V","address":"13b1"}
{"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"saveSN","signature":"(Ljava/lang/String;)V","address":"11f9"}
{"module":"libmyjni.so","package":"com.gdufs.xman","class":"MyApp","method":"work","signature":"()V","address":"14cd"}
int __fastcall n3(int a1)
{
int Value; // r0
int v3; // r0
void *v4; // r1
bool v5; // zf
n1(a1);
Value = getValue(a1);
if ( Value )
{
v5 = Value == 1;
v3 = a1;
if ( v5 )
v4 = &unk_2E6B;
else
v4 = &unk_2E95;
}
else
{
v3 = a1;
v4 = &unk_2E5B;
}
return callWork(v3, v4);
}
int __fastcall n1(int a1)
{
FILE *v2; // r0
FILE *v3; // r4
int v4; // r0
int v5; // r7
void *v6; // r5
int v8; // r0
int v9; // r1
v2 = fopen("/sdcard/reg.dat", "r+");
v3 = v2;
if ( !v2 )
{
v4 = a1;
return setValue(v4, 0);
}
fseek(v2, 0, 2);
v5 = ftell(v3);
v6 = malloc(v5 + 1);
if ( !v6 )
{
fclose(v3);
v4 = a1;
return setValue(v4, 0);
}
fseek(v3, 0, 0);
fread(v6, v5, 1u, v3);
*((_BYTE *)v6 + v5) = 0;
if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") )
{
v8 = a1;
v9 = 1;
}
else
{
v8 = a1;
v9 = 0;
}
setValue(v8, v9);
return j_fclose(v3);
}
strcmp函数比较返回值如果相同返回0,所以需要取反。
if ( !strcmp((const char *)v6, "EoPAoY62@ElRD") )
{
v8 = a1;
v9 = 1;
}
else
{
v8 = a1;
v9 = 0;
}
int __fastcall setValue(_JNIEnv *a1, int a2)
{
jclass v4; // r5
jfieldID v5; // r0
v4 = a1->functions->FindClass(a1, "com/gdufs/xman/MyApp");
v5 = a1->functions->GetStaticFieldID(a1, v4, "m", "I");
return ((int (__fastcall *)(_JNIEnv *, jclass, jfieldID, int))a1->functions->SetStaticIntField)(a1, v4, v5, a2);
}
package com.gdufs.xman;
import android.app.Application;
import android.util.Log;
public class MyApp extends Application {
public static int m;
static {
MyApp.m = 0;
System.loadLibrary("myjni");
}
public native void initSN() {
}
@Override // android.app.Application
public void onCreate() {
this.initSN();
Log.d("com.gdufs.xman m=", String.valueOf(MyApp.m));
super.onCreate();
}
public native void saveSN(String arg1) {
}
public native void work() {
}
}
为13位的,也就是说需要输入13位注册码,才能异或出这个真码。
!strcmp((const char *)v6, "EoPAoY62@ElRD")
31 31 31 31 31 31 31 31 31 31 31 31 31
46 6E 50 46 6E 50 46 6E 50 46 6E 50 46
public static void Xor(){
int xorData[]={0x46,0x6E,0x50,0x46,0x6E,0x50,0x46,0x6E,0x50,0x46,0X6E,0X50,0X46};
int xorDataMy[]={0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31};
System.out.print("[");
for (int i = 0; i < xorData.length; i++) {
// System.out.print("0x"+Integer.toHexString (xorData[i]^xorDataMy[i]));
// if(i<xorData.length-1){
// System.out.print(",");
// }
System.out.print(xorData[i]^xorDataMy[i]);
if(i<xorData.length-1){
System.out.print(",");
}
}
System.out.print("]");
}
[119,95,97,119,95,97,119,95,97,119,95,97,119]
45 6F 50 41 6F 59 36 32 40 45 6C 52 44
我们打印一下需要异或的真码数据。
public static void Xor1(){
int xorData[]={0x45,0x6f,0x50,0x41,0x6f,0x59,0x36,0x32,0x40,0x45,0x6c,0x52,0x44};
System.out.print("[");
for (int i = 0; i < xorData.length; i++) {
// System.out.print("0x"+Integer.toHexString (xorData[i]^xorDataMy[i]));
// if(i<xorData.length-1){
// System.out.print(",");
// }
System.out.print(xorData[i]);
if(i<xorData.length-1){
System.out.print(",");
}
}
System.out.print("]");
}
import binascii
xorkey =[119,95,97,119,95,97,119,95,97,119,95,97,119]
realkey=[69,111,80,65,111,89,54,50,64,69,108,82,68]
def XorDecy(data, l):
ret = []
for i in range(l):
ret.append(data[i] ^ xorkey[i])
s = ''
for i in ret:
s += chr(i)
print(s)
return ret
XorDecy(realkey,len(realkey))
得到flag为:xman{201608Am!2333}
https://starrysp.lanzoum.com/iLwj108r0v5c
看雪ID:白云精灵
https://bbs.pediy.com/user-home-814281.htm
# 往期推荐
2.android so文件攻防实战-libDexHelper.so反混淆
球分享
球点赞
球在看
点击“阅读原文”,了解更多!