Android hook system API —— 修改系统相机preview data
来源:互联网 发布:互穿网络防腐涂料 编辑:程序博客网 时间:2024/04/29 21:23
前一段时间想到一个问题:Android平台上能否通过一个app来修改系统相机所拍摄到的图像数据?如果可以的话,那基于此能做些什么?
首先,直觉告诉我这种修改是可行的。其次,很容易就可以想到,若能通过一个app来修改相机的图像数据,那岂不是可以任意篡改其他应用的二维码扫描结果?基于这样的问题和目标,我开始了Hook API的学习和原型的开发,为期一周,最终实现了这样的一个app,整理于此。
应该修改哪个API?
Android camera的preview图像数据都是通过回调函数onPreviewFrame(byte[] data, Camera camera)传递给应用层的app来使用,大家所熟知的微信,支付宝等二维码扫描功能都是在此处拿到图像数据加以分析扫描,所以我的目标就是修改这里的byte[] data。这是一个java层的回调函数,直接修改java层的API并非易事,所以我的着手点定位在native C层。此时就必须通过分析Android source code来找出需要hook的native API了。从onPreviewFrame一步步往下层追踪可以发现,camera从驱动层获取到数据后最终会经过native层的camera service来处理各个应用所注册的preview callback,具体实现在frameworks/base/services/camera/libcameraservice/CameraService.cpp中,其中有个接口
void CameraService::Client::handlePreviewData(int32_t msgType, const sp<IMemory>& mem, camera_frame_metadata_t *metadata) { ............ if (c != 0) { // Is the received frame copied out or not? if (flags & CAMERA_FRAME_CALLBACK_FLAG_COPY_OUT_MASK) { LOG2("frame is copied"); copyFrameAndPostCopiedFrame(msgType, c, heap, offset, size, metadata); } else { LOG2("frame is forwarded"); mLock.unlock(); c->dataCallback(msgType, mem, metadata); } } else { mLock.unlock(); }}
可以发现最终会调用copyFrameAndPostCopiedFrame函数,这个函数将图片数据进行了拷贝,并执行应用层相应的callback。也就是说应用层onPreviewFrame所拿到的数据仅仅是一份拷贝,这也恰好是我所需要的,因为这样我们即使修改了这里的拷贝后的图片数据,也不会影响系统相机本身的取景画面。是否可以说copyFrameAndPostCopiedFrame和handlePreviewData都可以是我们hook的目标?其实不然,因为它们都是内部函数调用,编译完成后其相对函数地址就已经确定存在于libcameraservice.so文件中,要用我们自己编写的函数地址来替换这个地址可并不容易,而且Android版本繁多,libcameraservice.so的实现也不停地变化,直接hook难保兼容性。继续跟踪copyFrameAndPostCopiedFrame,其实现如下:
void CameraService::Client::copyFrameAndPostCopiedFrame( int32_t msgType, const sp<ICameraClient>& client, const sp<IMemoryHeap>& heap, size_t offset, size_t size, camera_frame_metadata_t *metadata) { // It is necessary to copy out of pmem before sending this to // the callback. For efficiency, reuse the same MemoryHeapBase // provided it's big enough. Don't allocate the memory or // perform the copy if there's no callback. // hold the preview lock while we grab a reference to the preview buffer sp<MemoryHeapBase> previewBuffer; if (mPreviewBuffer == 0) { mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); } else if (size > mPreviewBuffer->virtualSize()) { mPreviewBuffer.clear(); mPreviewBuffer = new MemoryHeapBase(size, 0, NULL); } if (mPreviewBuffer == 0) { LOGE("failed to allocate space for preview buffer"); mLock.unlock(); return; } previewBuffer = mPreviewBuffer; memcpy(previewBuffer->base(), (uint8_t *)heap->base() + offset, size); sp<MemoryBase> frame = new MemoryBase(previewBuffer, 0, size); if (frame == 0) { LOGE("failed to allocate space for frame callback"); mLock.unlock(); return; } mLock.unlock(); client->dataCallback(msgType, frame, metadata);}
在copyFrameAndPostCopiedFrame里,图像数据被作为参数来构造成一个MemoryBase对象,这个构造函数是个外部函数,MemoryBase的定义是在libbinder.so中。也就是说,libcameraservice.so是依赖于libbinder.so,并且通过其Android.mk文件可以发现两者是动态链接的。这正是我所需要的hook地点,libbinder.so本身也是版本较稳定的基础动态链接库。MemoryBase的实现是在frameworks/base/libs/binder/MemoryBase.cpp中。
MemoryBase::MemoryBase(const sp<IMemoryHeap>& heap, ssize_t offset, size_t size) : mSize(size), mOffset(offset), mHeap(heap){ LOGW("I can hook here");}
综上可知,MemoryBase::MemoryBase即我们可以Hook的API, 并且可以通过mHeap->base()+mOffset访问和修改图像数据buffer。
如何注入我的API?
众所周知,Android的内核是Linux,而在Linux下的API Hook主要就是用到ptrace这个方法。ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号。ptrace函数在Android下同样可用,不过前提是调用ptrace的注入进程必须具有root权限,这也就限定了我的目标原型只能在root过的手机上起作用。首先我们来分析如何来实现一个注入程序。
1、注入程序的实现与分析
char *sos[] = { "libcameraservice.so", NULL};
int main(int argc, char *argv[]) { int pid; struct soinfo si; struct elf_info einfo; extern dl_fl_t ldl; void *handle = NULL; long proc = 0; long hooker_fopen = 0; pid = atoi(argv[1]); ptrace_attach(pid); ptrace_find_dlinfo(pid); handle = ptrace_dlopen(pid, "/system/lib/libbinder2.so",RTLD_NOW); ptrace_read(pid,handle,&si,sizeof(struct soinfo)); proc = (long)ptrace_dlsym(pid, handle, "_ZN7android10MemoryBaseC1ERKNS_2spINS_11IMemoryHeapEEElj"); replace_all_rels(pid, "_ZN7android10MemoryBaseC1ERKNS_2spINS_11IMemoryHeapEEElj", proc, sos); ptrace_detach(pid); exit(0); }整个注入过程包括,跟踪(ptrace_attach),搜寻dl库函数地址(ptrace_find_dlinfo),加载需要注入的动态链接库libbinder2.so(ptrace_dlopen),搜寻libbinder2.so中需要替换的函数地址(ptrace_dlsym),替换(replace_all_rels),停止跟踪(ptrace_detach)。下面,我们来逐步分析。
跟踪(ptrace_attach)
ptrace_attach做的事情就是跟踪目标进程(目标进程中止),读取当前寄存器的值并保存起来,修改pc和cpsr寄存器的值,让目标进程恢复执行。
int ptrace_attach(int pid) { regs_t regs; int status = 0; if (ptrace(PTRACE_ATTACH, pid, NULL, NULL ) < 0){ printf("ptrace_attach failed:%d\n",errno); LOGE("ptrace_attach failed:%d",errno); exit(0); } status = ptrace_wait_for_signal(pid, SIGSTOP); ptrace_readreg(pid, ®s); memcpy(&oldregs, ®s, sizeof(regs)); ptrace_dump_regs(&oldregs, "old regs");#ifdef ANDROID#ifdef THUMB regs.ARM_pc = 0x11; regs.ARM_cpsr |=0x30;#else regs.ARM_pc= 0;#endif#else regs.rip = 0;#endif ptrace_writereg(pid, ®s); ptrace_cont(pid); printf("waiting.. sigal...\n"); status = ptrace_wait_for_signal(pid, SIGSEGV); return 0;}搜寻dl库函数地址(ptrace_find_dlinfo)
linux下动态链接是通过libdl.so进行的,其中dlopen和dlclose用于加载一个动态链接库,dlsym用于获取一个动态库中函数或者变量的地址。一般在注入之前,libdl.so已经被加载进目标进程的内存里了,所以ptrace_find_dlinfo便是负责找出这三个函数的地址,以便用于后续加载我们自己的动态链接库。首先,ptrace_find_dlinfo通过调用get_linker_base方法来获取目标进程中libdl.so的虚拟内存地址,继而从首地址开始遍历,找出三个函数的对应地址,并保存起来。
static Elf32_Addr get_linker_base(int pid, Elf32_Addr *base_start, Elf32_Addr *base_end) { unsigned long base = 0; char mapname[FILENAME_MAX]; memset(mapname, 0, FILENAME_MAX); snprintf(mapname, FILENAME_MAX, "/proc/%d/maps", pid); FILE *file = fopen(mapname, "r"); *base_start = *base_end = 0; if (file) { //400a4000-400b9000 r-xp 00000000 103:00 139 /system/bin/linker while (1) { unsigned int atleast = 32; int xpos = 20; char startbuf[9]; char endbuf[9]; char line[FILENAME_MAX]; memset(line, 0, FILENAME_MAX); char *linestr = fgets(line, FILENAME_MAX, file); if (!linestr) { break; } // printf("........%s <--\n", line); if (strlen(line) > atleast && strstr(line, "/system/bin/linker")) { memset(startbuf, 0, sizeof(startbuf)); memset(endbuf, 0, sizeof(endbuf)); memcpy(startbuf, line, 8); memcpy(endbuf, &line[8 + 1], 8); if (*base_start == 0) { *base_start = strtoul(startbuf, NULL, 16); *base_end = strtoul(endbuf, NULL, 16); base = *base_start; } else { *base_end = strtoul(endbuf, NULL, 16); } } } fclose(file); } return base;}
dl_fl_t *ptrace_find_dlinfo(int pid) { Elf32_Sym sym; Elf32_Addr addr; struct soinfo lsi;#define LIBDLSO "libdl.so" Elf32_Addr base_start = 0; Elf32_Addr base_end = 0; Elf32_Addr base = get_linker_base(pid, &base_start, &base_end); if (base == 0) { printf("no linker found\n"); return NULL ; } else { printf("search libdl.so from %08u to %08u\n", base_start, base_end); } for (addr = base_start; addr < base_end; addr += 4) { char soname[strlen(LIBDLSO)]; Elf32_Addr off = 0; ptrace_read(pid, addr, soname, strlen(LIBDLSO)); if (strncmp(LIBDLSO, soname, strlen(LIBDLSO))) { continue; } printf("soinfo found at %08u\n", addr); printf("symtab: %p\n", lsi.symtab); ptrace_read(pid, addr, &lsi, sizeof(lsi)); off = (Elf32_Addr)lsi.symtab; ptrace_read(pid, off, &sym, sizeof(sym)); //just skip off += sizeof(sym); ptrace_read(pid, off, &sym, sizeof(sym)); ldl.l_dlopen = sym.st_value; off += sizeof(sym); ptrace_read(pid, off, &sym, sizeof(sym)); ldl.l_dlclose = sym.st_value; off += sizeof(sym); ptrace_read(pid, off, &sym, sizeof(sym)); ldl.l_dlsym = sym.st_value; off += sizeof(sym); printf("dlopen addr %p\n", (void*) ldl.l_dlopen); printf("dlclose addr %p\n", (void*) ldl.l_dlclose); printf("dlsym addr %p\n", (void*) ldl.l_dlsym); return &ldl; } printf("%s not found!\n", LIBDLSO); return NULL ;}加载需要注入的动态链接库libbinder2.so(ptrace_dlopen)
libbinder2.so的文件名作为参数压栈,并调用dlopen,将该动态库加载到目标内存中。
void *ptrace_dlopen(pid_t pid, const char *filename, int flag) {#ifdef ANDROID regs_t regs; //int stat; ptrace_readreg(pid, ®s); ptrace_dump_regs(®s, "before call to ptrace_dlopen\n");#ifdef THUMB regs.ARM_lr = 1;#else regs.ARM_lr= 0;#endif regs.ARM_r0= (long)ptrace_push(pid,®s, filename,strlen(filename)+1); regs.ARM_r1= flag; regs.ARM_pc= ldl.l_dlopen; ptrace_writereg(pid, ®s); ptrace_cont(pid); printf("done %d\n", ptrace_wait_for_signal(pid, SIGSEGV)); ptrace_readreg(pid, ®s); ptrace_dump_regs(®s, "before return ptrace_call\n"); return (void*) regs.ARM_r0;#endif}
</pre> <strong>搜寻libbinder2.so中需要替换的函数地址(ptrace_dlsym)</strong></p><p><strong> </strong>需要替换的函数名和动态链接库的句柄作为参数压栈,并调用dlsym,搜寻该函数在目标内存中的虚拟地址。<pre name="code" class="cpp">void *ptrace_dlsym(pid_t pid, void *handle, const char *symbol) {#ifdef ANDROID regs_t regs; //int stat; ptrace_readreg(pid, ®s); ptrace_dump_regs(®s, "before call to ptrace_dlsym\n");#ifdef THUMB regs.ARM_lr = 1;#else regs.ARM_lr= 0;#endif regs.ARM_r0= (long)handle; regs.ARM_r1= (long)ptrace_push(pid,®s, (void*)symbol,strlen(symbol)+1); regs.ARM_pc= ldl.l_dlsym; ptrace_writereg(pid, ®s); ptrace_cont(pid); printf("done %d\n", ptrace_wait_for_signal(pid, SIGSEGV)); ptrace_readreg(pid, ®s); ptrace_dump_regs(®s, "before return ptrace_dlsym\n"); return (void*) regs.ARM_r0;#endif}替换(replace_all_rels)
停止跟踪(ptrace_detach)
ptrace_detach很简单,恢复寄存器的值并停止跟踪。
void ptrace_detach(int pid) { ptrace_writereg(pid, &oldregs); if (ptrace(PTRACE_DETACH, pid, NULL, NULL ) < 0) { perror("ptrace_detach"); exit(-1); }}
(未完待续)
- Android hook system API —— 修改系统相机preview data
- android加固系列—5.加固前先学会破解,hook(钩子)jni层系统api
- android Camera preview data 流程
- 修改IAT,HOOK API
- 修改IAT,HOOK API
- HOOK 系统 API
- HOOK 系统 API
- 系统API的Hook
- Android 调用系统相机返回data为null
- android 调用系统相机拍照,返回的data为null
- Android调用系统相机onActivityResult返回参数data为null
- android hook api
- android hook API
- android hook api
- android hook api
- android hook api
- android hook api
- android API HOOK
- ubuntu之apache正向代理及反向代理(ProxyPass\ProxyPassReverse)
- 快速搭建项目 windrapid 介绍
- Ubuntu下如何查看软件安装目录及安装版本
- android init.rc增加一个自定义的service
- Python optparser 和getopt
- Android hook system API —— 修改系统相机preview data
- AOJ 2121 Castle Wall
- CXF JAX-WS 注释
- android OTA的时候删除data分区里面的部分数据
- Unity NGUI 描点控件的位移动画
- Java实现单链表反转
- jaxws的web service的自定义fault
- 3D 空间金字塔采样
- ContextClassLoader介绍