移动应用的静态动态分析

一、环境准备

1. 安装Android Studio

首先,安装最新版本的 Android Studio,它将提供模拟器和相关的开发工具。

2. 安装apktool

apktool 是一款用于反编译和重新编译APK文件的实用工具。可以通过以下方式安装:

1
brew install apktool

3. 配置adb环境

在Android Studio中已包含 android-platform-tools,但需要配置环境变量以便在终端中使用 adb 命令。

编辑 ~/.zshrc(或 ~/.bashrc)文件,添加以下内容:

1
export PATH=$PATH:~/Library/Android/sdk/platform-tools/

保存后,运行 source ~/.zshrc(或 source ~/.bashrc)使配置生效。

二、检查APK的证书信息

使用 jarsigner 工具可以快速查看APK的签名证书信息。执行以下命令:

1
jarsigner -verify -verbose -certs ./app2.apk | grep Signed

示例输出:

1
- Signed by "CN=Cristian Bidea, OU=Mobile, O=king.com, L=Bucharest, ST=Romania, C=RO"

参考资料:使用 jarsigner 检查APK签名

三、反编译和重新编译APK

1. 反编译APK

使用 apktool 反编译APK文件:

1
apktool d ./app1.apk -o app1_decompiled

这将把 app1.apk 反编译到 app1_decompiled 目录中,可以在其中查看和修改资源和代码。

2. 修改代码

在反编译后的目录中,根据需要修改源代码或资源文件。

3. 重新编译APK

修改完成后,使用以下命令重新编译APK:

1
apktool b app1_decompiled -o app1_new.apk

四、签名和安装修改后的APK

1. 生成签名密钥

首先,创建存放密钥的目录:

1
mkdir -p ./keys

使用 keytool 生成新的签名密钥:

1
keytool -genkey -v -keystore ./keys/my_keystore.keystore -alias my_alias -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 10000

在生成过程中,需要输入密钥库密码并填写签名信息,例如姓名、组织单位、组织、城市、州/省和国家代码。

2. 签名APK

使用 jarsigner 对新APK进行签名:

1
jarsigner -keystore ./keys/my_keystore.keystore -sigalg SHA256withRSA -digestalg SHA-256 ./app1_new.apk my_alias

3. 处理签名不匹配问题

如果在安装时遇到以下错误:

1
Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package signatures do not match newer version; ignoring!]

这是由于签名不匹配导致的。需要先卸载已安装的旧版本APK:

1
adb uninstall com.example.app1

然后重新安装新签名的APK:

1
adb install app1_new.apk

五、使用mitmproxy进行流量分析

1. mitmproxy介绍

mitmproxy 是一个开源的交互式 HTTPS 代理工具,主要用于拦截、查看、修改和重放 HTTP 和 HTTPS 流量。它提供了命令行界面(mitmproxy)、无交互模式(mitmdump)以及 Web 界面(mitmweb),满足不同用户的使用需求。开发者和测试人员常用它来调试网络请求、分析流量和模拟网络环境。

​ •mitmproxy:主要定位于网络流量的调试和分析工具,适合需要深度定制和脚本化操作的开发者和测试人员。

​ •Burp Suite:是一款综合性的 Web 安全测试工具,专为渗透测试人员设计,提供了从被动分析到主动攻击的一系列功能。

2. 安装mitmproxy

1
brew install mitmproxy

3. 配置证书

在安卓模拟器中,打开浏览器访问 mitm.it,下载适用于安卓的证书。

在模拟器的设置中,安装下载的CA证书,以便mitmproxy可以拦截和分析HTTPS网络流量。

六、使用Frida进行代码注入

1. 可执行程序注入示例

(1)编写并编译C程序

创建一个名为 exercise.c 的文件,内容如下:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// exercise.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#define MESSAGE_NUM 5
char* encryptString(char*,char*,size_t);
char* getKey(int);
void decryptAllMessage();
int checkPassword();
int getMessage(char*);
void printAllEncryptedMessages();

struct messages{
char* key;
char* encryptedString;
size_t len;
}message[MESSAGE_NUM];


int main()
{
srand(time(NULL));
char readString[255];
for(int i=0;i<MESSAGE_NUM;++i) {
memset(readString,0,255);
printf("Enter Message:\n");
int len = getMessage(readString);
char* key = getKey(len);
char* encryptedString = encryptString(readString,key,len);
message[i].key=key;
message[i].encryptedString = encryptedString;
message[i].len = len;
}

printf("Enter Password\n");
if(checkPassword())
{
decryptAllMessage();
}
else
printAllEncryptedMessages();

for(int i=0;i<MESSAGE_NUM;++i)
{
free(message[i].encryptedString);
free(message[i].key);
}
}

int getMessage(char* message)
{
return read(0,message,100);
}

int checkPassword()
{
char password[255];
size_t passwordLen = read(0,password,100);
return (passwordLen != 0) && (!memcmp(password,"password",8));

}

char* getKey(int len)
{
char* key = malloc(len);
for(int i=0;i<len;++i)
{
key[i]=rand();
}
return key;
}

char* encryptString(char* val,char* key, size_t len)
{
char* encryptedMessage = malloc(len);

for(int i=0;i<len;++i)
{
encryptedMessage[i] = val[i]^key[i];
}
return encryptedMessage;
}

void printAllEncryptedMessages()
{
for(int i=0;i<MESSAGE_NUM;++i)
{
printf("message %d:",i);
for(int j=0;j<message[i].len;++j)
{
printf("%c",message[i].encryptedString[j]);
}
printf("\n");
}

}

void decryptAllMessage()
{
for(int i=0;i<MESSAGE_NUM;++i)
{
printf("message %d:",i);
for(int j=0;j<message[i].len;++j)
{
printf("%c",message[i].encryptedString[j]^message[i].key[j]);
}
}
}

代码解释:

•接收多个用户输入的字符串,并使用随机生成的密钥进行简单的加密(例如,逐字符异或加密)。

•询问用户输入密码(硬编码为 “password”)。

•如果密码正确,解密并打印原始字符串。

编译程序:

1
gcc exercise.c -o exercise

(2)编写Frida脚本

创建一个名为 hook_rand.js 的脚本:

1
2
3
4
5
6
7
8
9
10
// hook_rand.js
Interceptor.attach(Module.findExportByName(null, 'rand'), {
onEnter: function(args) {
// 不需要处理进入函数时的参数
},
onLeave: function(retval) {
// 将返回值设置为0
retval.replace(0);
}
});

(3)运行和注入

在终端A中,运行编译好的程序:

1
./exercise

在终端B中,找到程序的PID并注入Frida脚本:

1
2
ps aux | grep exercise
frida -p <PID> -l hook_rand.js

回到终端A,继续操作,会发现由于rand函数被Hook,程序行为发生了改变。输入任何密码(即使是错误的密码),观察程序行为。

由于rand函数的返回值被固定为0,加密过程实际上没有改变字符串内容。因此,即使输入错误的密码,程序认为密码正确,并解密并打印原始字符串。

2. 对安卓APP进行注入

(1)准备工作

  • 安装Android Pie (API 28) ARM版本:确保模拟器为ARM架构,以避免兼容性问题。

  • 启动模拟器并获取root权限

    1
    2
    3
    adb devices
    adb -s emulator-5554 shell
    su

(2)安装Frida服务器

  • 下载Frida服务器Frida Releases

  • 解压unxz frida-server-16.5.2-android-arm64.xz

  • 推送到模拟器并设置权限

    1
    2
    3
    4
    5
    adb root
    adb push frida-server-16.1.1-android-arm64 /data/local/tmp/frida-server
    adb shell
    su
    chmod 755 /data/local/tmp/frida-server
  • 启动Frida服务器

    1
    /data/local/tmp/frida-server &

(3)安装目标APK

1
adb install target_app.apk

(4)编写Frida脚本

创建一个名为 hook_fun.js 的脚本:

1
2
3
4
5
6
7
8
9
10
11
// hook_fun.js
Java.perform(function () {
var MyActivity = Java.use("com.example.target.my_activity");
MyActivity.fun.overload('int', 'int').implementation = function (x, y) {
console.log("[*] Original parameters: x = " + x + ", y = " + y);
var newX = 2;
var newY = 5;
console.log("[*] Modified parameters: x = " + newX + ", y = " + newY);
this.fun(newX, newY);
};
});

(5)注入脚本

1
2
adb shell ps | grep target_app
frida -U -p <PID> -l hook_fun.js

查看日志:

1
adb logcat

通过以上步骤,将原本APP执行的add 50 and 30 修改为了sum 2 and 5,在日志信息中可以看到执行结果的变化。

七、总结

本篇博客详细介绍了如何对安卓应用进行静态动态分析,从环境配置到实际操作,包括反编译APK、修改和重新编译、签名、安装,以及使用mitmproxy和Frida进行高级分析。