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, &regs);    memcpy(&oldregs, &regs, 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, &regs);    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, &regs);    ptrace_dump_regs(&regs, "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,&regs, filename,strlen(filename)+1);    regs.ARM_r1= flag;    regs.ARM_pc= ldl.l_dlopen;    ptrace_writereg(pid, &regs);    ptrace_cont(pid);    printf("done %d\n", ptrace_wait_for_signal(pid, SIGSEGV));    ptrace_readreg(pid, &regs);    ptrace_dump_regs(&regs, "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, &regs);    ptrace_dump_regs(&regs, "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,&regs, (void*)symbol,strlen(symbol)+1);    regs.ARM_pc= ldl.l_dlsym;    ptrace_writereg(pid, &regs);    ptrace_cont(pid);    printf("done %d\n", ptrace_wait_for_signal(pid, SIGSEGV));    ptrace_readreg(pid, &regs);    ptrace_dump_regs(&regs, "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);    }}



(未完待续)

0 0
原创粉丝点击