【Android CTF】XCTF黑客精神—Frida RPC爆破

  • A+
所属分类:移动安全

作者坛账号:genliese

 

1. 背景

此题比较简单,网上也有各种各样的wp。wp可以分为两类,一类分析算法,另一类是爆破。此题采用的是异或算法,所以分析算法求flag是最快的。而爆破的话,可以重写成等价的代码,如C++、python,或者直接采用主动调用的方式进行爆破,如Frida的主动调用。采用主动调用的好处是不用重写,而我采用Frida RPC进行主动调用的目的是想利用python丰富的库,方便爆破,缺点相对于直接用JS代码进行爆破是太慢了,慢了200多倍。

  • 用到的工具:
    • pixel 3 android 9.0
    • jadx
    • IDA
    • Frida12.8
    • Pycharm
    • VSCode

2. 分析过程

输入注册码,显示如下:

【Android CTF】XCTF黑客精神—Frida RPC爆破

在代码中搜索"您的注册码已保存",相关代码如下:

复制代码 隐藏代码

public class RegActivity extends Activity {
    private Button btn_reg;
    private EditText edit_sn;

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() {
/* class com.gdufs.xman.RegActivity.AnonymousClass1 */

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() {
/* class com.gdufs.xman.RegActivity.AnonymousClass1.AnonymousClass1 */

public void onClick(DialogInterface dialog, int which) {
Process.killProcess(Process.myPid());
}
}).show();
}
});
}
}

然后跳转到saveSN函数所在的类MyApp,代码如下:

复制代码 隐藏代码

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");
}

public void onCreate() {
initSN();
Log.d("com.gdufs.xman m=", String.valueOf(m));
super.onCreate();
}
}

接着分析libmyjni.so文件,在JNI_OnLoad函数中注册了initSNsaveSNwork函数,代码如下:

【Android CTF】XCTF黑客精神—Frida RPC爆破

【Android CTF】XCTF黑客精神—Frida RPC爆破

2.1 分析initSN函数

首先分析initSN函数,其中setValue函数的作用是设置com/gdufs/xman/MyApp类的静态字段m的值,

【Android CTF】XCTF黑客精神—Frida RPC爆破

【Android CTF】XCTF黑客精神—Frida RPC爆破

initSN函数只做了一件事,即如果/sdcard/reg.dat文件的内容是"[email protected]",com/gdufs/xman/MyApp类的静态字段m的值则设置为1,否则设置为0

通过对com/gdufs/xman/MyApp类的静态字段m交叉引用发现,如果m的值为1,则显示已注册,所以我们怀疑输入的注册码通过一系列的计算后得到的值会保存到/sdcard/reg.dat文件中,如果得到的值为"[email protected]",则输入的注册码即为flag

【Android CTF】XCTF黑客精神—Frida RPC爆破

2.2 分析 saveSN函数

saveSN也只干了一件事,把输入的注册码经过异或运算之后存到了/sdcard/reg.dat文件中

【Android CTF】XCTF黑客精神—Frida RPC爆破

【Android CTF】XCTF黑客精神—Frida RPC爆破

我们可以试试,直接把/sdcard/reg.dat文件的内容替换成"[email protected]",会是什么效果,如下:

【Android CTF】XCTF黑客精神—Frida RPC爆破

则我们的flag格式为xman{注册码}

3. 编写脚本

通过上面的分析过程可知,我们可以通过Frida主动调用的方式爆破出flag,首先给出直接用JS代码进行爆破的脚本,再给出RPC爆破的脚本。

3.1 JS代码爆破

复制代码 隐藏代码

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", "[email protected]", "ElR");
const last_character = "D";
const my_dict = "[email protected]#$%^&*()?_"
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}}`);
})
}

setImmediate(Main);

【Android CTF】XCTF黑客精神—Frida RPC爆破

花了大概两分钟爆破出了flag

3.2 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代码
(下面代码可能和markdown有些冲突,导致有些代码的格式乱了,但是不影响阅读和使用)

复制代码 隐藏代码

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", "[email protected]", "ElR"]
goal_three_character_array_length = len(goal_three_character_array)
goal_last_character = "D"
my_dict = "[email protected]#$%^&*()?_"
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("33c")
    try_count = 0
    for try_str in my_dict:
    sys.stdout.write("[{}]Try_str: {}r".format(try_count, "".join(try_str)))
    script.exports.invoke(try_str)
    if result == goal_last_character:
    flag[12] = try_str
    break
    sys.stdout.write("
  • Flag: xman{{{}}} ".format("".join(flag)))
    sys.stdout.flush()
    # 清屏
    print("33c")
    print("

 

  • Flag: xman{{{}}}".format("".join(flag)))
    time_end = time.time()
    cost_time = time_end - time_begin
    print("
    cost time: " + str(cost_time // 60) + "min")

【Android CTF】XCTF黑客精神—Frida RPC爆破

采用RPC爆破的方式花了429分钟才爆破出了flag,花的时间是直接使用JS代码进行爆破的214倍

4. 问题答疑

4.1 设备问题

如果出现各种无法解决的问题,尝试换成跟我一样的设备和系统——pixel 3、android 9.0

4.2 出现global reference table overflow

错误是全局引用表溢出了,具体意思是Java对象的全局引用表溢出了,你在循环里new Java对象就可能会溢出,因为frida对Java对象的引用就是用的全局引用,不知道什么时候才释放,反正循环的时候没有释放

4.3 爆破卡住了

多次调用Java.choose程序就会卡住,好像跟frida版本无关,不知道为什么,所以我直接new一个对象进行主动调用

4.4 为什么233字符没有被爆破出来?

排列使用的字典是没有重复字符的,所以3个字符中不可能同时出现两个相同的字符,下面的代码就会出现这种问题

复制代码 隐藏代码

my_dict = "[email protected]#$%^&*()?_"
permutations(dict, 3)

解决办法是把字典自加多次,代码如下

复制代码 隐藏代码

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)

my_dict = "[email protected]#$%^&*()?_"
possibilities = GeneratePossibilities(my_dict, 3, True)

5. 附件

链接:https://pan.baidu.com/s/1NRPGU_j6CK7bgB363FN33w
提取码:09wb

--

www.52pojie.cn

--

pojie_52

本文始发于微信公众号(吾爱破解论坛):【Android CTF】XCTF黑客精神—Frida RPC爆破

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: