Android逆向零基础到入坟

一、JNI开发

Java native interface:是一种java和c\c++交互的通道;这个是java的一种语言特性,与android无关

1、创建项目

  • 普通项目:Empty Activity(Java)

  • jni项目:Native C++(Java + C)

通项目也可以手动配置成jni项目,但是比较

Project选择Native C++

其他选项无所谓,next就行。

2、编码实例

项目创建完毕后,可以在MainActivity.java中看到有一个native修饰stringFromJNI函数。

并且可以在左边的项目结构中看到有cpp文件夹,展开后可以看到有一个native-lib.cpp文件,双进进入可以看到有一个函数实现。

默认会对函数名折叠,单击灰色的函数名即可看到完整的函数名。

jni类型的函数名为:Java_包名_1static_类名_函数名,其中头文件jni.h为必须引入。回到MainActivity.java中自己添加两个函数。

首先可以看到报错了,鼠标放上去。

根据提示点击Create JNI function for add会自动生成对应函数体。

编写下列代码。

  • add函数直接正常编写即可。

  • getName由于是c语言实现的函数,自身并没有java的String类,无法直接返回,但可以通过调用env→NewStringUTF(const char*)函数进行转换后返回。

回到MainActivity中调用,并输出查看。

3、JNI注册

创建Native C++项目后会发现多了一个CMakeList.txt,打开查看内容。

  • cmake_minimum_required:cmake的最低版本。

  • project:项目名。

  • add_library:欲添加的c库。在默认配置中含义为添加名为${CMAKE_PROJECT_NAME}srcnative-lib.cpp这个库(编译产出为so文件,为linux动态链接库文件。)

  • target_link_libraries:要链接的库,默认配置添加了${CMAKE_PROJECT_NAME}

一旦工程编译,则会生成对应so文件在build/intermadiates/cxx/Debug/{xxxxx}/obj/{arch}/下,例如项目默认的so为libnative_static.so

3.1 静态注册

手动创建一个enc.javaenc.cpp文件。

  • enc.java:用来声明native函数。

  • enc.cpp:用来定义native函数实现。

java代码如下:

System.loadLibrary("enc");表示要加载这个库文件。然后按照相同的方法创建对象体,并实现代码。

由于此时并没有对CMakeList.txt修改,因此会出现很多报错。按照下列方式进行修改。

最后Build→Refresh Links C++ Projects刷新一下cmake即可。入口函数调用。

3.2 动态注册

动态注册只是c/c++代码实现不同,其他设置还是一样。但需要一个注册格式,生成步骤如下:

1、获取enc类名路径。

2、终端跳转到项目java路径下。

3、输入javah path

会生成一个.h文件,打开即可看到格式。

编写如下代码:

二、进程内存读写

CLion+NDK环境,具体搭建见官方链接:https://developer.android.google.cn/ndk/guides/ndk-build?hl=zh-cn

这里贴一下项目结构和mk文件。

打开说明一下最后一行,官方给的说明有很多个。这里因为是生成可执行文件,因此这里是$(BUILD_EXCUTEABLE)

2.1 ptrace

man文档:https://man7.org/linux/man-pages/man2/ptrace.2.html

实际上就是个调试命令,可以对进程附加、读写等操作。

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
#include <sys/types.h>
#include <sys/ptrace.h>
#include <stdint.h>
#include <stdio.h>

int main()
{
int pid = 17579;
uintptr_t addr = 0x7693F64EB4;

// 附加进程
long ret = ptrace(PT_ATTACH,pid,NULL,NULL);
printf("PTRACE_ATTACK:%d\n",ret);

// 附加失败结束进程
if(ret !=0) {
printf("attach error\n");
return 0;
}

// 读数据
ret = ptrace(PT_READ_D,pid,addr,NULL);
printf("PT_READ_D:%d\n",ret);

// 写数据
int data=999;
ret = ptrace(PT_WRITE_D,pid,addr,data);

// 读数据
ret = ptrace(PT_READ_D,pid,addr,NULL);
printf("PT_READ_D:%d\n",ret);

// 取消附加
ret = ptrace(PT_DETACH,pid,NULL,NULL);
printf("PTRACE_DETACH:%d\n",ret);

}

由于开发环境在mac上,因此宏可能和linux上不太一样,linux(PTRACE_)mac(PT_)

2.2 proc读写

linux中执行的进程被管理在/proc中,每个进程以目录的形式管理,目录名为进程id。

随便进入一个进程中。

其他目录不做解释,mem代表为进程内存,因此可以通过linux的io命令打开以为文件形式读写。

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
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>

int main()
{
// 打开进程内存(mem)页
int fd = open("/proc/17579/mem",O_RDWR);
uintptr_t addr = 0x7693F64EB4;

int res = 0;
// 文件指针指向地址
lseek(fd,addr,SEEK_SET);
// 读取内容(内存)
read(fd,&res,4);
printf("read:%d\n",res);

// 重新调节指针指向地址
lseek(fd,addr,SEEK_SET);
// 写入内容
int buf = 999;
write(fd,buf,4);

// 重新读取
lseek(fd,addr,SEEK_SET);
read(fd,&res,4);
printf("read%d\n",res);

}

2.3 process_vm

man手册:https://man7.org/linux/man-pages/man2/process_vm_readv.2.html

说白了就是两个进程间测内存数据传输,然后这里提一下官方最后一句:两个数据直接相互传输,不会经过内核空间。指的是不会将数据从一个进程的用户空间复制到内核空间,再从内核空间复制到目标进程的用户空间。这样提高了效率,而且三个内存读写中,这个方法也是效率最高的。

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
#include <stdio.h>
#include <unistd.h>
#include <sys/uio.h> // process_vm_函数头文件
#include <unistd.h>
#include <sys/syscall.h>
int main(void)
{
//ptrace
//proc
//process_vm syscall
struct iovec local[1];
struct iovec remote[1];
int buffer[40];
int pid=30044;

local[0].iov_base=buffer;
local[0].iov_len=160;
remote[0].iov_base=(void *)0xBCF5D83C;
remote[0].iov_len=160;

long int readl=syscall(__NR_process_vm_readv,pid,local,1,remote,1,0);
printf("syscall readlen:%ld\n",readl);

for(int i=0;i<40;i++){
printf("%d\t",buffer[i]);
if((i+1)%4==0){
printf("\n");
}
}

buffer[0]=999;
long int writel=syscall(__NR_process_vm_writev,pid,local,1,remote,1,0);
printf("syscall writel:%ld\n",writel);

return 0;
}

这里可以直接调用process_vm_readvprocess_vm_writev,但是部分环境上不会有这个函数,所以也可以syscall

三、Charles抓包

首先说明,一定要去官方下载正版,盗版一定别用,踩坑了,呜呜呜呜!!!!!!

官方链接:https://www.charlesproxy.com/

在线注册机:https://zzzmode.com/mytools/charles/

charles下载后开启,第一个设置是把电脑代理关闭(默认是开启的),不关闭,电脑无法上网。

关闭方式:ProxymacOSA Proxy。macOS是我电脑系统,这个根据你系统来显示。

然后是关闭启动时自动开启:ProxyProxy Settings,取消勾选Enable macOS proxy on launch


3.1 设置代理端口(可默认)

ProxyProxy Settings

设置完毕后实际上已经可以抓包了,但是只能抓http的包。下一步设置ssl证书后就可以抓https的包了。

3.2 设置ssl

ProxySSL Proxy Settings

点击Add后host和port全部填*,代表对所有的https请求捕获,这个后续可以自己优化,比如只对指定host的https进行抓包。

3.3 Android安装证书

这一步,千万,一定,必须,用的是官方的Charles,不然证书有问题(安装成功了依然没法抓https包)。问就是踩坑了。

HelpSSL Proxying,然后这里可以选择Save Charle Root Certificate,也可以选择第四个install charles root ..... remote browser(这个具体操作就不说了)。

将导出的证书用adb拉进手机后先不忙安装证书,需要安装一个模块。

在Android 8.0设备上root设备只需要将上面生成的xxx.0文件直接拷贝到/system/etc/security/cacerts目录下即可导入成功。但是在再高版本的系统上,即使是获取了root权限,也难以修改系统分区文件属性来进行读写。经常会出现一下错误提示:

- Read-only file system

- mount: '/system' not in /proc/mounts

- 等等。

Move_Certificates-v1.9.zip

使用KernelSU或者面具安装这个模块,这个模块主要作用就是把用户证书自动移动到系统证书中(安装证书后,需要重启手机生效)。

首先安装证书后,重启手机,然后到系统级证书查看。

可以看到最后一个证书为Charles,没有这一步操作前,这个证书可以在用户级中看到,现在移动过来后就看不到了。

3.4 手机设置代理

首先获取charles的ip,HelpLocal IP Address

这里有两个方法。

3.4.1 手动设置网络代理

ip填写上边获取到的内容,端口填第一步设置的HTTP Proxy端口。点击确定后即可抓包。

3.4.2 socksDroid

手机安装这个apk,打开后

Server IP依旧填写上边获取到的IP,Server Port则填写第一步中SOCKS Proxy中设置的端口。然后点击右上角的开关,开启即可。

开启成功时,右上角状态栏会有一个钥匙状态图标,表示生效。

3.5 抓包

上边设置完毕后,直接可以抓包了。