Something u have to know:
实践是检验真理的唯一标准,本次逆向学习使用的AppDemo为Frida官网提供的ctf的示例。
本次实战通过静态插桩篡改法、动态插桩调用法、动态插桩篡改法、汇编代码分析法四个方向获取flag
目录
0x01 环境准备:
该部分内容较多,可以先跳过,需要了回来安装
ID | 工具名 | 链接 | 说明 |
---|---|---|---|
1 | AppDemo | https://github.com/ctfs/write-ups-2015/blob/master/seccon-quals-ctf-2015/binary/reverse-engineering-android-apk-1/rps.apk | 下载失败翻墙下载 |
2 | AndroidKiller | http://www.downza.cn/soft/275382.html | |
3 | J2S2J | https://www.onlinedown.net/download/1209079?module=download | |
4 | Frida | 安装对应版本 | |
5 | Radare2 |
| 下载失败翻墙下载 |
1、AppDemo下载安装
(略)
2、AndroidKiller下载
(略)
若jd-gui不兼容当前java版本,可替换为插件包中原jd-gui
3、J2S2J下载
(略)
转换过程中某dex/jar文件异常,删掉该文件重新转换即可
4、Frida安装
4.1 u need know:
Frida是一款以Python作为载体,Javascript作为在Android中执行代码的Hook框架。
Frida自带的Messages机制与进程交互,以下为Frida的基础模板:
import frida, sys //hook代码,采用javascript编写 jscode = """ //javascript代码!!!!!!!!! """ //自定义回调函数 def on_message(message, data): if message['type'] == 'send': print("[*] {0}".format(message['payload'])) else: print(message) //获取设备并得到应用包对应进程号pid的session实例化对象 process = frida.get_remote_device().attach('应用完整包名') //创建jscode的Script类 script = process.create_script(jscode) //将自定义回调函数带入mseesage script.on('message', on_message) //启动javascript脚本 script.load() sys.stdin.read()
Frida分为frida和finrda-sever两部分,frida-server安装在移动端用于监听来自PC端frida的Messages用来热更
4.2 PC端安装frida需要具备Python开发环境(没有的自己下懒死了),命令行输入:
pip install frida-tools
4.3 移动端安装frida-server需要根据架构信息安装对应版本,使用adb工具查看(此处演示虚模拟器,真机不会的百度,略略略)
模拟器安装目录下自带adb工具,cmd命令行启动
adb shell
getprop ro.product.cpu.abi
看到下面打印的了么,框架为x86,下载对应版本:https://github.com/frida/frida/releases
下载下来后放入移动端,启动监听和frida-server:
adb push xxxxx /data/local/tmp
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
adb shell
cd /data/local/tmp
chmod 755 xxx
./xxx
4.4 以上安装完毕后测试(第一条打印为server服务未启动或其他原因,第二条打印则为成功)
frida-ps -R
0x02 代码分析:
1、APK反编译
代码分析时可以用自带的J2S,也可以使用J2S2J工具,如下图所示对比下来J2S2J工具转换代码更为清晰准确。
2、代码分析
入口MainActivity.smali分析:
//声明了button控件及对应的监听器
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.P = (Button) findViewById(R.id.button);
this.S = (Button) findViewById(R.id.button3);
this.r = (Button) findViewById(R.id.buttonR);
this.P.setOnClickListener(this);
this.r.setOnClickListener(this);
this.S.setOnClickListener(this);
this.flag = 0;
}
//那么就看一下button的点击事件
public void onClick(View v) {
if (this.flag != 1) {
this.flag = 1;
((TextView) findViewById(R.id.textView3)).setText("");
TextView tv = (TextView) findViewById(R.id.textView);
TextView tv2 = (TextView) findViewById(R.id.textView2);
this.m = 0;
this.n = new Random().nextInt(3); //随机数0,1,2
tv2.setText(new String[]{"CPU: Paper", "CPU: Rock", "CPU: Scissors"}[this.n]);
if (v == this.P) {
tv.setText("YOU: Paper");
this.m = 0;
}
if (v == this.r) {
tv.setText("YOU: Rock");
this.m = 1;
}
if (v == this.S) {
tv.setText("YOU: Scissors");
this.m = 2;
}//以上随机得到剪子包袱锤
this.handler.postDelayed(this.showMessageTask, 1000);//输赢判断方法
}
}
//那么就看看输赢判断方法this.showMessageTask
入口的调用($1.smali)找判断方法:
private final Runnable showMessageTask = new Runnable() {
public void run() {
TextView tv3 = (TextView) MainActivity.this.findViewById(R.id.textView3);
MainActivity mainActivity;
if (MainActivity.this.n - MainActivity.this.m == 1) {
mainActivity = MainActivity.this;
mainActivity.cnt++;
tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
} else if (MainActivity.this.m - MainActivity.this.n == 1) {
MainActivity.this.cnt = 0;
tv3.setText("LOSE +0");
} else if (MainActivity.this.m == MainActivity.this.n) {
tv3.setText("DRAW +" + String.valueOf(MainActivity.this.cnt));
} else if (MainActivity.this.m < MainActivity.this.n) {
MainActivity.this.cnt = 0;
tv3.setText("LOSE +0");
} else {
mainActivity = MainActivity.this;
mainActivity.cnt++;
tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt));
}
if (1000 == MainActivity.this.cnt) {
//获胜1000次获取flag
tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}");
}
MainActivity.this.flag = 0;
}
};
4、结论
1、直接通过AndroidKiller篡改,使打印flag成为必然事件;
2、使用Frida hook脚本传递注入,获取calc()返回值并拼接/修改cnt的值为1000 获得flag;
3、使用Radare2分析so文件汇编代码,分析calc()算法获得flag。
注:AndroidKiller为静态二进制插桩,Frida为动态二进制插桩。
0x03 静态插桩篡改法(AndroidKiller):
1、代码修改
我们可以将打印flag的前置条件删除,对应smali中的操作分别是:
1、删除对应条件判断的smali码(如图右红框所示)
2、前置跳转锚点继承给下一段代码(.line 50)
3、因为smali使用的if-ne为不等于,所以需要将该后置跳转对应的前置锚点全部删除(删除所有smali代码中存在的:cond_0)
4、判断条件删除后将条件改为背景(super.this$0.cnt=1000,即v2=1000,即const/16 v2,0x3e8)
注:smali码转换成java代码后会优化,所以判断方式等会稍有不同
2、APK编译
3、获取Flag
触发条件在button的onclic事件,点击即可获得
耶斯莫拉
0x04 动态插桩调用/篡改法(Frida):
方法一:不篡改程序,调用calc()方法拼接得到flag
1、移动端运行frida-server
没看懂的参考第二章Frida安装最后几步
2、Javascript代码编写
Java.perform(function () {
//定义变量MainActivity指定使用的类
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
//指定类下的onClick方法,为其添加自定义方法
MainActivity.onClick.implementation = function () {
//调用calc()方法,获取返回值
var calcData = this.calc();
//发送给PC端打印calc()方法返回值
send("calc() return:"+calcData);
var result = (1000+calcData)*107;
//发送给PC端打印flag
send("SECCON{"+result.toString()+"}");
}
});
3、Javascript代码套入python模板
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function () {
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
MainActivity.onClick.implementation = function () {
var calcData = this.calc();
send("calc() return:"+calcData);
var result = (1000+calcData)*107;
send("SECCON{"+result.toString()+"}");
}
});
"""
process = frida.get_remote_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()
4、运行查看flag
方法二:篡改cnt的值为1000 获得flag
5、同上直接上Python代码
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function () {
var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity');
MainActivity.onClick.implementation = function (v) {
this.onClick(v);
this.n.value = 0;
this.m.value = 2;
this.cnt.value = 999;
}
});
"""
process = frida.get_remote_device().attach('com.example.seccon2015.rock_paper_scissors')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()
6、运行查看flag
耶斯莫拉
0x05 汇编代码分析(Radare2):
1、使用radare2对so文件分析(或者ida pro等工具)
2、拼接获取flag
"SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107)+"}"
MainActivity.this.cnt=1000 MainActivity.this.calc()=7
SECCON {107749}
耶斯莫拉