来自高维的对抗 - 逆向TinyTool自制

来自高维的对抗 - 逆向TinyTool自制

 

 

 

 

一、序

无论是逆向分析还是漏洞利用,我所理解的攻防博弈无非是二者在既定的某一阶段,以高维的方式进行对抗,并不断地升级维度。比如,逆向工程人员一般会选择在Root的环境下对App进行调试分析,其是以root的高权限对抗受沙盒限制的低权限;在arm64位手机上进行root/越狱时,ret2usr利用技术受到PXN机制的约束,厂商从修改硬件特性的高维度进行对抗,迫使漏洞研究者提高利用技巧。

下文将在Android逆向工程方面,分享鄙人早期从维度攻击的角度所编写的小工具。工具本身可能已经不能适应现在的攻防,“授人以鱼不如授人以渔”,希望能够给各位读者带来一些思路,构建自己的分析利器。

二、正

0x00 自定义Loader

早期Android平台对SO的保护采用畸形文件格式和内容加密的方式来对抗静态分析。随着IDA以及F5插件地不断完善和增多,IDA已经成为了逆向人员的标配工具。正因如此,IDA成为了畸形文件格式的对抗目标。畸形方式从减少文件格式信息到构造促使IDA加载crash的变化正应证了这一点。对此,鄙人研究通过重建文件格式信息的方式来让IDA正常加载。

在完成编写修复重建工具不久之后,鄙人在一次使用IDA的加载bin文件时,猛然意识到畸形文件格式的对抗目标是IDA对ELF文件的加载的默认loader。既然防御的假象和维度仅仅在于默认loader,那么以自定义的loader加载实现高维攻击,理论是毫无敌手的。

那如何来实现IDA自定义loader呢?

  1. 以Segment加载的流程对ELF文件进行解析,获取和重建Section信息(参看上面所说贴子)。
  2. 把文件信息在IDA中进行展示,直接调用对应的IDAPython接口

实现加载bin文件的py代码见文末github链接,直接放置于IDA/loaders目录即可。由于早期少有64位的安卓手机,加载脚本仅支持arm 32位格式,有兴趣读者可以改写实现全平台通用。不同ndk版本所编译文件中与动态加载无关的Section不一定存在,注释相应的重建代码即可。

0x01 Kernel Helper

以APP分析为例,对于加固过的应用通常会对自身的运行环境进行检测。比如: 检测自身调试状态,监控proc文件等。相信各位读者有各种奇淫技巧来绕过,早期鄙人构建hook环境来绕过。从维度的角度,再来分析这种对抗。对于APP或者bin文件而言,其仅运行于受限的环境中,就算exp提权后也只是权限的提升和对内核有一定的访问控制权。对于Android系统而言,逆向人员不仅能够拿到root最高权限,而且还可以修改系统的所有代码。从攻防双方在运行环境的维度来看,“魔”比”道“高了不只三丈,防御方犹如板上鱼肉。而在代码维度,防御方拥有源代码的控制权,攻防处于完全劣势。随着代码混淆和VMP技术的运用,防御方这块鱼肉越来越不好"啃"。

对于基于linux的安卓系统而言,进程的运行环境和结构是由内核来提供和维护的。从修改内核的维度来对抗,能达到一些不错的效果。下文将详述在内核态dump目标进程内存和系统调用监控。

1. 内存DUMP

对内核添加一些自定义功能时,通常可以采用内核驱动来实现。虽然一部分Android手机支持驱动ko文件加载,但内核提供的其他工具则不一定已经编译到内核,在后文中可以看到。nexus系列手机是谷歌官方所支持的,编译刷机都比较方便,推荐使用。

S1. 编译内核

为了让内核支持驱动ko文件的加载,在make memuconfig配置内核选项时,以下勾选:

[*] Enable loadable module support

次级目录所有选项

编译步骤参看谷歌官方提供的内核编译步骤。

S2. 驱动代码

linux系统支持多种驱动设备,这里采用最简单的字符设备来实现。与其他操作系统类似,linux驱动程序也分为入口和出口。在module_init入口中,对字符设备进行初始化,创建/dev/REHelper字符设备。文末代码采用传统的方式对字符设备进行注册,也可直接使用misc的方式。字符设备的操作方式通过注册file_operations回调实现,其中ioctl函数比较灵活,满足实现需求。

定义command ID:

#define CMD_BASE 0xC0000000

#define DUMP_MEM (CMD_BASE + 1)

#define SET_PID (CMD_BASE + 2)

构建dump_request参数:

struct dump_request{

pid_t pid; //目标进程

unsigned long addr; //目标进程dump起始地址

ssize_t count; //dump的字节数 char __user *buf; //用户空间存储buf

};

在ioctl中实现分支:

case DUMP_MEM:
target_task = find_task_by_vpid(request->pid); //对于用户态,进程通过进程的pid来标示自身;在内核空间,通过pid找到对应的进程结构task_struct
if(!target_task){
printk(KERN_INFO "find_task_by_vpid(%d) failed\n", request->pid);
ret = -ESRCH;
return ret;
}
request->count = mem_read(target_task->mm, request->buf, request->count, request->addr); //进程的虚拟地址空间同样由内核进程管理,通过mm_struct结构组织

memread其实是对memrw函数的封装,mem_rw能够读写目标进程,简略流程:

static ssize_t mem_rw(struct mm_struct *mm, char __user *buf,
size_t count, unsigned long addr, int write)
{
ssize_t copied;
char *page;

...

page = (char *)__get_free_page(GFP_TEMPORARY); // 获取存储数据的临时页面

...

while (count > 0) {
int this_len = min_t(int, count, PAGE_SIZE);

// 将写入数据从用户空间拷贝到内核空间
if (write && copy_from_user(page, buf, this_len)) {
copied = -EFAULT;
break;
}

// 对目标进程进行读或写操作,具体实现参看内核源码
this_len = access_remote_vm(mm, addr, page, this_len, write);
// 将获取到的目标进程数据从内核拷贝到用户空间
if (!write && copy_to_user(buf, page, this_len)) {
copied = -EFAULT;
break;
}
...
}
...
}

内核驱动部分的dump功能实现,接着只需在用户空间访问驱动程序即可。

// 构造ioctl参数
request.pid = atoi(argv[1]);
request.addr = 0x40000000;
request.buf = buf;
request.count = 1000;

// 打开内核驱动
int fd = open("/dev/REHelper", O_RDWR);
// 发送读取命令
ioctl(fd, DUMP_MEM, &request);
close(fd);

S3. 测试

文末代码中,dump_test为目标进程,dump_host通过内核驱动获取目标进程的数据。insmod和dump_host以root权限运行即可。

2. 系统调用监控

通常情况下,APP通过动态链接库libc.so间接的进行系统调用,直接在用户态hook libc.so的函数即可实现监控。而对于静态编译的bin文件和通过svc汇编指令实现的系统调用,用户态直接hook是不好处理的。道理很简单,系统调用由内核实现,hook也应该在内核。

linux系统的系统调用功能统一存在syscall表中,syscall表通常编译放在内核映像的代码段,修改syscall表需要修改内核页属性,感兴趣的读者可以找到linux rootkit方面的资料。本文对系统调用监控的实现,采用内核从2.6支持的probe功能来实现,选用的最重要原因是:通用性。在不同abi平台通过汇编实现系统调用的读者应该知道,不同abi平台的系统调用功能号并不一定相同,这就意味其在syscall表中的数组索引是不一致的,还需要额外的判定,实现并不优雅。

linux内核提供了kprobe、jprobe和kretprobe三种方式。限于篇幅,仅介绍利用jprobe实现系统调用监控。感兴趣的读者可以参看内核Documentation/kprobes.txt文档以及samples目录下的例子。

S1. 编译选项

为了能够支持probe功能,需在上述开启驱动ko编译选项的基础上勾选kprobe选项。如果没有开启内核驱动选项,是不会有kprobes(new)选项的

General setup --->
[*] Kprobes(New)

S2. 驱动代码

以监控sys_open系统调用为例。首先,在module_init函数中对调用register_jprobes进行注册。注册信息封装在struct jprobe结构中。

static struct jprobe open_probe = {
.entry = jsys_open, //回调函数
.kp = {
.symbol_name = "sys_open", //系统调用名称
},
};

由于系统调用为所有进程提供服务,不加入过滤信息会造成监控信息过多。回调函数的声明和被监控系统调用的声明一致。

asmlinkage int jsys_open(const char *pathname, int flags, mode_t mode){
pid_t current_pid = current_thread_info()->task->tgid;
// 从当前上下文中获取进程的pid

// monitor_pid初始化-1,0为全局监控。
if(!monitor_pid || (current_pid == monitor_pid)){
printk(KERN_INFO "[open] pathname %s, flags: %x, mode: %x\n",
pathname, flags, mode);
}

jprobe_return();
return 0;
}

对monitor_pid的设置通过驱动的ioctl来设置,参数简单直接设置。

case SET_PID:
monitor_pid = (pid_t) arg;

S3. 测试

文末代码bin_wrapper和ptrace_trace均为静态编译,bin_wrapper通过设置监控对ptrace_trace的进行监控。内核prink的打印信息通过cat /proc/kmsg获取,输出类似如下:

<6>[34728.283575] REHelper device open success!
<6>[34728.285504] Set monitor pid: 3851
<6>[34728.287851] [openat] dirfd: -100, pathname /dev/__properties__, flags: a8000, mode: 0
<6>[34728.289348] [openat] dirfd: -100, pathname /proc/stat, flags: 20000, mode: 0
<6>[34728.291325] [openat] dirfd: -100, pathname /proc/self/status, flags: 20000, mode: 0
<6>[34728.292016] [inotify_add_watch]: fd: 4, pathname: /proc/self/mem, mask: 23
<6>[34729.296569] PTRACE_PEEKDATA: [src]pid = 3851 --> [dst]pid = 3852, addr: 40000000, data: be919e38

三、尾

本文介绍了鄙人对攻防的维度思考,以及从维度分析来实现的早期工具的部分介绍。希望能够给各位读者带来一些帮助和思考。限于鄙人水平,难免会有疏漏或者错误之处,敬请各位指出,谢谢。

四、附

https://github.com/ThomasKing2014/ReverseTinytoolDemo

 

 

 

本文来自合作伙伴“阿里聚安全”,发表于2017年02月28日  09:48.

时间: 2024-12-02 03:09:02

来自高维的对抗 - 逆向TinyTool自制的相关文章

云栖Android精华文章合集

云栖Android精彩文章整理自各位技术大咖们关于Android的精彩分享,本文将云栖Android精彩文章整理成为一个合集,以便于大家学习参考.Weex.apk瘦身.开发资源.应用维护.内存管理,一切尽在云栖Android精华文章合集. 云课堂: Android平台页面路由框架ARouter最佳实践 聚能聊: Android_Studio_那些年你常用的神奇快捷键及遇到的糗事儿 文章干货: 安全: APP漏洞扫描器之未使用地址空间随机化 [安全攻防挑战]Androidapp远程控制实战 你必须

【集锦】2016年阿里云在线直播精华合集

每期阿里云的技术分享课程都报名火爆,各路技术大咖进行了对于技术理解的深度分享,但是还是有很多小伙伴错过了现场直播.本文特意为大家整理了阿里云在线技术分享课程的精彩合集,错过了直播的小伙伴们快来补补课吧! 12月28日 阿里沈询:分布式事务原理与实践 分布式数据库之中,一个最重要待解决的问题就是分布式事务应该怎么支持.往往一提到分布式事务,就立刻会联想到性能低,速度慢,然而真的是这样么?有没有一些方式和方法,能够比较好的解决这个问题呢?阿里针对这个场景又是怎么去实践的呢?阿里中间件资深技术专家沈询

云栖大会——阿里聚安全亮出创新技术 颠覆APP传统安全加固

 2016年10月17日 17:32  2175 10月13日,杭州 · 云栖大会如期召开,亮点之一就是将会有众多"黑科技"汇聚,其中由阿里聚安全带来的"全量混淆"和"瘦身"技术开创了APP安全加固的新方向.快速的配置操作使得用户能够无缝平衡业务发展与安全保障. 阿里聚安全是阿里巴巴集团整合自身安全能力对外输出的安全开放平台,细数阿里聚安全的产品发展历程:从强大攻防能力的移动安全组件到业内领先的风险决策引擎,再到基于深度学习技术的内容安全····

云栖大会 · 聚安全专场议题和嘉宾介绍

 2016年10月12日 10:50  2152 2016杭州·云栖大会将在10月13日-16日,在杭州云栖小镇国际会议中心隆重举办为期4天的云计算狂欢盛宴.大会主题精彩丰富,聚焦探讨云计算.大数据.人工智能.互联网安全.智慧生活.物联网.企业服务等前沿科技与热门话题.各路互联网精英与行业大咖蜂拥齐聚,共同见证这场无与伦比的互联网盛会,一起感受前沿科技成果的精彩! 阿里聚安全作为阿里巴巴集团整合自身安全能力对外输出的安全开放平台,依托国际领先的风险扫描引擎.立体式安全防护技术.庞大的数据库体系和

APP加固新方向--混淆和瘦身

近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户和生产高质量的程序员苦不堪言,APP加固意义愈发重大. 传统加固和脱壳技术的发展经过了三代的发展和升级,时至今日,传统加固面临到挑战,如容易被脱壳,脱壳类教程非常多,通用脱壳机可轻易脱大部分壳. 加固新方向,混淆和瘦身吸引了人们的眼球.代码混淆技术是对抗逆向攻击最有效的方式之一.此外越来越多的新特性正在啃蚀着大型APP的用户体验,APP瘦身减肥也成了亟待

【直播】APP全量混淆和瘦身技术揭秘

[直播]APP全量混淆和瘦身技术揭秘 近些年来移动APP数量呈现爆炸式的增长,黑产也从原来的PC端转移到了移动端,通过逆向手段造成数据泄漏.源码被盗.APP被山寨.破解后注入病毒或广告现象让用户苦不堪言.  为了解决安卓APP容易被逆向的问题,除了对产品进行加固处理,代码混淆技术是对抗逆向攻击最有效的方式之一.本直播会分享阿里聚安全带来的APP全量混淆技术.此外越来越多的新特性正在啃蚀着大型APP的用户体验,APP瘦身减肥也成了亟待解决的问题,如何能在使用安全功能同时瘦身,也将是本期主题所带来的

《DNS与BIND(第5版)》——7.6 保持一切平稳运行

7.6 保持一切平稳运行 维护的一个重要意义在于:能够在真正的问题出现之前,察觉到某些异常.问题发现得越早,就越容易进行修复.正如老话说的:预防为主,治疗为辅. 这一章的内容不全然是关于故障排除的(本书稍后会有一章专门讨论如何排除故障),更偏重于故障出现前的预防.而故障排除是在问题变得复杂后,不得不进行的操作,并且需要根据症状来找出问题. 下面两小节将讨论预防性维护的相关内容:定期查看syslog文件及BIND名称服务器的统计信息,以检查是否存在任何问题.就像给名称服务器做体检一样. 7.6.1

验证码的前世今生(今生篇)

验证码的前世今生(今生篇) 作者:南浔@阿里安全 看完<验证码的前世今生(前世篇)>也许第一感觉就是Winter is coming,互联网的人机对抗到了最黑暗的时刻.柳暗花明又一村,最黑暗的时刻也是光明即将来临的时刻--在传统验证码的末日新的反向图灵测试机制浴火重生. 0×1 验证码的划代标准 在介绍新的反向图灵测试机制前,首先我们对验证码进行划代对比.通过验证码的划代对比我们能更清楚新型验证码的特性. 验证码划代的标准是人机识别过程中基于对人类知识的应用. 第一代:标准验证码 这一代验证码

最简单易懂的GAN(生成对抗网络)教程:从理论到实践(附代码)

  之前 GAN网络是近两年深度学习领域的新秀,火的不行,本文旨在浅显理解传统GAN,分享学习心得.现有GAN网络大多数代码实现使用Python.torch等语言,这里,后面用matlab搭建一个简单的GAN网络,便于理解GAN原理. GAN的鼻祖之作是2014年NIPS一篇文章:Generative Adversarial Net,可以细细品味. ● 分享一个目前各类GAN的一个论文整理集合 ● 再分享一个目前各类GAN的一个代码整理集合   开始 我们知道GAN的思想是是一种二人零和博弈思想