文章目录
- 一、黑客精神
- 二、使用步骤
- 1.运行app
- 2.jadx反编译apk
- 3.IDA打开so文件
- 4.函数分析
- 5.脚本爆破
- 总结
一、黑客精神
题目链接:https://adworld.xctf.org.cn/task/task_list?type=mobile&number=6&grade=0&page=1
二、使用步骤
1.运行app
2.jadx反编译apk
代码语言:javascript复制import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
/* loaded from: classes.dex */
public class MainActivity extends Activity {
private static String workString;
private Button btn1;
@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
String str2;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("com.gdufs.xman m=", "Xman");
MyApp myApp = (MyApp) getApplication();
int m = MyApp.m;
if (m == 0) {
str2 = "未注册";
} else if (m == 1) {
str2 = "已注册";
} else {
str2 = "已混乱";
}
setTitle("Xman" str2);
this.btn1 = (Button) findViewById(R.id.button1);
this.btn1.setOnClickListener(new View.OnClickListener() { // from class: com.gdufs.xman.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
MyApp myApp2 = (MyApp) 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();
}
});
}
public void doRegister() {
new AlertDialog.Builder(this).setTitle("注册").setMessage("Flag就在前方!").setPositiveButton("注册", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.3
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(BuildConfig.APPLICATION_ID, "com.gdufs.xman.RegActivity"));
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
}
}).setNegativeButton("不玩了", new DialogInterface.OnClickListener() { // from class: com.gdufs.xman.MainActivity.2
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Process.killProcess(Process.myPid());
}
}).show();
}
public void work(String str) {
workString = str;
}
@Override // android.app.Activity
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
}
RegActivity 界面
代码语言:javascript复制package com.gdufs.xman;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Process;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
/* loaded from: classes.dex */
public class RegActivity extends Activity {
private Button btn_reg;
private EditText edit_sn;
@Override // android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reg);
this.btn_reg = (Button) findViewById(R.id.button1);
this.edit_sn = (EditText) findViewById(R.id.editText1);
this.btn_reg.setOnClickListener(new View.OnClickListener() { // from class: com.gdufs.xman.RegActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
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() { // from class: com.gdufs.xman.RegActivity.1.1
@Override // android.content.DialogInterface.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Process.killProcess(Process.myPid());
}
}).show();
}
});
}
}
myApp函数
代码语言:javascript复制package com.gdufs.xman;
import android.app.Application;
import android.util.Log;
/* loaded from: classes.dex */
public class MyApp extends Application {
public static int m = 0;
public native void initSN();
public native void saveSN(String str);
public native void work();
static {
System.loadLibrary("myjni");
}
@Override // android.app.Application
public void onCreate() {
initSN();
Log.d("com.gdufs.xman m=", String.valueOf(m));
super.onCreate();
}
}
3.IDA打开so文件
接着分析libmyjni.so文件,在JNI_OnLoad函数中注册了initSN、saveSN和work函数,代码如下:
代码语言:javascript复制jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
if ( !(*vm)->GetEnv(vm, (void **)&g_env, 65542) )
{
j___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) )
{
j___android_log_print(2, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() ok");
return 65542;
}
j___android_log_print(6, "com.gdufs.xman", "RegisterNatives() --> nativeMethod() failed");
}
return -1;
}
双击off_5004查找注册函数
发现
- initSN()
- saveSN(String str)
- work()
4.函数分析
initSN
首先分析initSN函数,其中setValue函数的作用是设置com/gdufs/xman/MyApp类的静态字段m的值
代码语言:javascript复制int __fastcall n1(int a1)
{
int v2; // r0
int v3; // r4
int v4; // r0
int v5; // r7
const char *v6; // r5
int v8; // r0
int v9; // r1
v2 = j_fopen("/sdcard/reg.dat", "r ");
v3 = v2;
if ( !v2 )
{
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v2, 0, 2);
v5 = j_ftell(v3);
v6 = (const char *)j_malloc(v5 1);
if ( !v6 )
{
j_fclose(v3);
v4 = a1;
return setValue(v4, 0);
}
j_fseek(v3, 0, 0);
j_fread(v6, v5, 1, v3);
v6[v5] = 0;
if ( !j_strcmp(v6, "EoPAoY62@ElRD") )
{
v8 = a1;
v9 = 1;
}
else
{
v8 = a1;
v9 = 0;
}
setValue(v8, v9);
return j_fclose(v3);
}
initSN会读取/sdcard/reg.dat文件(这个文件默认没有),如果文件内容和字符串 “EoPAoY62@ElRD” 匹配,那么myaap.m值为1,代表已经注册。我们尝试手动修改这个 文件的值
saveSN
代码语言:javascript复制int __fastcall n2(int a1, int a2, int a3)
{
int v5; // r7
const char *v7; // r6
const char *v8; // r5
int v9; // r4
int v10; // r0
char v11; // r2
signed int v12; // [sp 8h] [bp-38h]
char v13[20]; // [sp 10h] [bp-30h] BYREF
v5 = j_fopen("/sdcard/reg.dat", "w ");
if ( !v5 )
return j___android_log_print(3, "com.gdufs.xman", byte_2E5A);
strcpy(v13, "W3_arE_whO_we_ARE");
v7 = (const char *)(*(int (__fastcall **)(int, int, _DWORD))(*(_DWORD *)a1 676))(a1, a3, 0);
v8 = v7;
v12 = j_strlen(v7);
v9 = 2016;
while ( 1 )
{
v10 = v8 - v7;
if ( v8 - v7 >= v12 )
break;
if ( v10 % 3 == 1 )
{
v9 = (v9 5) % 16;
v11 = v13[v9 1];
}
else if ( v10 % 3 == 2 )
{
v9 = (v9 7) % 15;
v11 = v13[v9 2];
}
else
{
v9 = (v9 3) % 13;
v11 = v13[v9 3];
}
*v8 ^= v11;
}
j_fputs(v7, v5);
return j_fclose(v5);
}
saveSN也只干了一件事,把输入的注册码经过异或运算之后存到了/sdcard/reg.dat文件中
直接把/sdcard/reg.dat文件的内容替换成EoPAoY62@ElRD
运行app
我们的flag格式为xman{注册码}
5.脚本爆破
js版本爆破
代码爆破
代码语言:javascript复制var fputs_str = null;
function Hook() {
Java.perform(function () {
const imports = Module.enumerateImportsSync("libmyjni.so");
const imports_len = imports.length;
var fputs_addr = null;
for (var i = 0; i < imports_len; i ) {
if (imports[i].name == "fputs") {
fputs_addr = imports[i].address;
break;
}
}
if (fputs_addr != null) {
Interceptor.attach(fputs_addr, {
onEnter: function (args) {
fputs_str = args[0].readCString();
},
onLeave: function (retval) {
}
})
}
})
}
function Invoke(try_str) {
Java.perform(function () {
Java.choose("com.gdufs.xman.MyApp", {
onMatch: function (instance) {
instance.saveSN(try_str);
},
onComplete: function () {
}
})
})
}
function Main() {
Hook();
Java.perform(function () {
const three_character_array = new Array("EoP", "AoY", "62@", "ElR");
const last_character = "D";
const my_dict = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()?_"
const myapp_class_obj = Java.use("com.gdufs.xman.MyApp");
const three_character_array_len = three_character_array.length;
const my_dict_len = my_dict.length
const myapp = Java.use("com.gdufs.xman.MyApp").$new();
var flag = "";
for (var i = 0; i < three_character_array_len; i ) {
var found = false;
for (var j = 0; j < my_dict_len; j ) {
if (found == true) {
break;
}
for (var k = 0; k < my_dict_len; k ) {
if (found == true) {
break;
}
for (var m = 0; m < my_dict_len; m ) {
const try_str = my_dict[j] my_dict[k] my_dict[m];
console.log(`try_str: ${try_str}`);
myapp.saveSN(try_str);
if (three_character_array[i] == fputs_str) {
flag = try_str;
console.log(`found: ${try_str}`);
found = true;
break;
}
}
}
}
}
for (var i = 0; i < my_dict_len; i ) {
const try_str = my_dict[i];
console.log(`try_str: ${try_str}`);
Invoke(try_str);
if (last_character == fputs_str) {
flag = try_str;
console.log(`found: ${try_str}`);
break;
}
}
console.log(`flag: xman{${flag}}`);
})
}
setTimeout(Main, 500);
rpc爆破
代码语言:javascript复制var myapp = null;
function Hook() {
Java.perform(function () {
myapp = Java.use("com.gdufs.xman.MyApp").$new();
const imports = Module.enumerateImportsSync("libmyjni.so");
const imports_len = imports.length;
var fputs_addr = null;
for (var i = 0; i < imports_len; i ) {
if (imports[i].name == "fputs") {
fputs_addr = imports[i].address;
break;
}
}
if (fputs_addr != null) {
Interceptor.attach(fputs_addr, {
onEnter: function (args) {
send(args[0].readCString());
},
onLeave: function (retval) {
}
})
}
})
}
function Invoke(try_str) {
Java.perform(function () {
myapp.saveSN(try_str);
})
}
rpc.exports = {
hook: Hook,
invoke: Invoke
}
python爆破
代码语言:javascript复制from itertools import permutations
import sys
import time
import frida
result = ""
received = False
def MessageHandler(message, data):
if message["type"] == "send":
global result
global received
result = message["payload"]
received = True
else:
print(message)
def GeneratePossibilities(dict, count, repetitive=False):
"""
:param dict: Dictionary
:param count: Number of characters per group
:param repetitive: Whether there are duplicate characters in each group
:return: return permutations(dict, count)
"""
if repetitive and (count > 1):
src_dict = dict
for i in range(count - 1):
dict = src_dict
return permutations(dict, count)
device = frida.get_device_manager().add_remote_device("192.168.1.5:8888")
pid = device.spawn("com.gdufs.xman")
session = device.attach(pid)
with open("index.js") as file_descriptor:
script = session.create_script(file_descriptor.read(), runtime="v8")
script.on("message", MessageHandler)
script.load()
time.sleep(1)
device.resume(pid)
goal_three_character_array = ["EoP", "AoY", "62@", "ElR"]
goal_three_character_array_length = len(goal_three_character_array)
goal_last_character = "D"
my_dict = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()?_"
flag = ['*'] * 13
possibilities = GeneratePossibilities(my_dict, 3, True)
time.sleep(1)
time_begin = time.time()
script.exports.hook()
first_part_times = 0
try_count = 0
for p in possibilities:
character1, character2, character3 = p
try_str = character1 character2 character3
try_count = 1
sys.stdout.write("[{}]Try_str: {}r".format(try_count, "".join(try_str)))
sys.stdout.flush()
received = False
script.exports.invoke(try_str)
while not received:
pass
for i in range(goal_three_character_array_length):
if result == goal_three_character_array[i]:
if flag[i * 3] == "*":
first_part_times = 1
flag[i * 3: i * 3 3] = character1, character2, character3
break
sys.stdout.write("
Flag: xman{{{}}} ".format("".join(flag)))
sys.stdout.flush()
if first_part_times == 4:
break
# 清屏
print("