Android逆向工程

在Root前提下,我们可以使用Hooker方式绑定so库,通过逆向方式篡改数值,从而达到所谓破解目的。然而,目前无论是软件加固方式,或是数据处理能力后台化,还是客户端数据真实性验证,都有了一定积累和发展,让此“懒技术”不再是破解修改的万金油。再者,阅读汇编指令,函数指针替换,压栈出栈等技术需要一定技术沉淀,不利于开发同学上手。

两年前,也是因为懒,很懒,非常懒,堆积了足够的动力,写了一个基于人工模拟方式,对一个特定规则的游戏进行暴力破解。我们都知道,人工模拟方式,绕过了大量防破解技术,只要还是人机交互模式,并且满足一定的游戏规则,基本是无法防御的。

技术实现原理

因涉及到安全方面的考量,本文主要围绕技术实现原理和关键技术点进行阐述。

技术要求:

  1. 支持多分辨率
  2. 支持多点触摸
  3. 支持输入速率动态变更
  4. 处理能力峰值需要达到30fps

实现方式分三步:

  1. 劫持屏幕
  2. 分析数据
  3. 模拟输出

1.劫持屏幕

先说说劫持屏幕,做过截屏功能的同学应该清楚,Root了之后能访问设备“dev/graphic”文件夹,里面有fb0, fb1, fb2三个screen buffer文件。这里用到的是fb0文件。

抛出一个问题,当前主流屏幕分辨率都在1920*1080区间,一张图片的缓存能去到2M左右,要达到30fps的性能指标,光是屏幕数据的读写耗时,就满足不了要求。怎么做呢?

一般在做图像处理的时候都会想到parallel programming。然而,这里的图片是时间相关的,不适宜采用多线程任务派发。

懒人一番思量后,发现一条捷径,共享内存读取,请看以下代码。

mapbase = mmap(0, **mapsize, PROT_READ, MAP_SHARED, fd, offset);

这行代码广泛存在于各个截屏代码片段中,精髓在于PROT_READ 和 MAP_SHARED上。先科普一下mmap参数中这两个参数吧。

prot : 映射区域的保护方式。可以为以下几种方式的组合:

  • PROT_EXEC 映射区域可被执行
  • PROT_READ 映射区域可被读取
  • PROT_WRITE 映射区域可被写入
  • PROT_NONE 映射区域不能存取

flags : 影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

  • MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
  • MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
  • MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
  • MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
  • MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
  • MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

因为我们不需要写屏,所以prot只需要采用PORT_READ;而我们期望避免屏幕数据的多次创建,flags就需要用到MAP_SHARED,这样文件句柄fd指向的内存块数据就会实时变更,无需多次创建,拷贝,释放数据。

2.分析数据

截取到屏幕数据就好办了,对每一帧进行数据处理,这里完全就是算法问题了。懒人都用搓算法,大概的思路就是:7*7宫格,对于所有相连的两个同色item做了横向映射表和纵向映射表,然后轮寻处理5连,4连和3连。里面还有一些涉及到实现细节的映射表重置与预判,因为不是本文重点,就带过了。

void Handle_X_Combination() {

    LOGE("Handle_X_Combination");

    gen_Horizontal_Matrix(6);

    get_Horizontal_X_Match();

    gen_Vertical_Matrix(0, 6);

    get_Vertical_X_Match();
}

下面是程序运行时的Log信息片段,以供大家参考。

3. 模拟输出

算法会输出当前屏幕的一个模拟手势操作队列,最精彩的当然放到最后,也是此工程的技术点,怎么模拟输出手势的问题。

Android所给予的截屏和模拟操作分别为 adb screenshot 和 adb shell sendevent (根据android版本,有些机型用的是input event,记得没错的话~)
所有需要adb处理的指令,都不能采用高并发方式调用,要不然要么机器重启,要么指令堵塞。所以adb这条路不通。
怎么办呢?

懒人又一番思量后,linux系统大都采用文件buffer,直接将指令写文件吧。其实adb也是写文件,不过adb做了一层转译,这里涉及到设备层指令代码,不同机型定义的指令代码不尽相同。

要完成此任务,首先要弄清楚几件事情:

  1. 一个点击事件的构成是怎样的
  2. 一个滑动事件的构成多了什么
  3. 事件的指令代码分别代表什么

万能的adb给了我一些思路,adb shell getevent,会打印出当前event的指令。再科普一下,event有很多,包括compass_sensor,light_sensor,pressure_sensor,accelerometer_sensor等等。
我们这里监听的是,touchscreen_sensor。

有了上面的指导信息,要构建一个模拟操作函数就很容易了。操作屏幕打印出想要的模拟的手势,然后写下来就好了。一共会有这么几个模拟操作函数需要创建:


void simulate_long_press_start_event(int touch, int fromX, int fromY);
void simulate_long_press_hold_event(int touch, int fromX, int fromY);
void simulate_long_press_end_event(int touch);
void simulate_press_event(int touch, int fromX, int fromY);
void simulate_move_event(int touch, int fromX, int fromY, int toX, int toY);

下面给出一个我写好的范例出来,大家可以依葫芦画瓢,把剩下的写好。

void simulate_press_event(int touch, int fromX, int fromY) {

    pthread_mutex_lock(&global.writeEventLock);

    LOGE("simulate_press_event");

    INPUT_EVENT event;

    // 0. Multi-Touch
    // 此项目非必要,因为没有用到多点触摸,是另一个项目使用到了
    event.type = 0x3;
    event.code = 0x2f;
    event.value = touch;
    write(global.fd_event, &event, sizeof(event));

    // 1. ABS_MT_TRACKING_ID:
    // 理论上必要,因为Android事件输入是批量处理的,需要用到输入id,
    // 但是这里偷懒使用了同步锁,并且没有多点触摸需求,所以不会有Tracking_ID串扰问题,也就不需要记数了
    event.type = 0x3;
    event.code = 0x39;
    event.value = global.event_id > 60000 ? 10 : global.event_id++;
    write(global.fd_event, &event, sizeof(event));

    // 2. At screen coordinates:
    // 触摸点x,y坐标
    event.type = 0x3;
    event.code = 0x35;
    event.value = fromX;
    write(global.fd_event, &event, sizeof(event));
    event.type = 0x3;
    event.code = 0x36;
    event.value = fromY;
    write(global.fd_event, &event, sizeof(event));

    // 4. Sync
    // 数据同步到设备
    event.type = 0x0;
    event.code = 0x0;
    event.value = 0x0;
    write(global.fd_event, &event, sizeof(event));

    event.type = 0x3;
    event.code = 0x39;
    event.value = 0xffffffff;
    write(global.fd_event, &event, sizeof(event));

    // 4. Pure event separator:
    // 结束符
    event.type = 0x0;
    event.code = 0x0;
    event.value = 0x0;
    write(global.fd_event, &event, sizeof(event));

    pthread_mutex_unlock(&global.writeEventLock);
}

为了大家对Android逆向有一个简单的理解,我们看下面几个问题。

首先,请大家查阅源码:
frameworks/base/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp

截取其中关键的两段:

渲染方式声明:

#ifdef EGL_ANDROID_swap_rectangle
    if (extensions.hasExtension("EGL_ANDROID_swap_rectangle")) {
        if (eglSetSwapRectangleANDROID(display, surface,
                0, 0, mWidth, mHeight) == EGL_TRUE) {
            // This could fail if this extension is not supported by this
            // specific surface (of config)
            mFlags |= SWAP_RECTANGLE;
        }
    }
    // when we have the choice between PARTIAL_UPDATES and SWAP_RECTANGLE
    // choose PARTIAL_UPDATES, which should be more efficient
    if (mFlags & PARTIAL_UPDATES)
        mFlags &= ~SWAP_RECTANGLE;
#endif

具体渲染操作:

void DisplayHardware::flip(const Region& dirty) const
{
    checkGLErrors();  

    EGLDisplay dpy = mDisplay;
    EGLSurface surface = mSurface;  

#ifdef EGL_ANDROID_swap_rectangle
    if (mFlags & SWAP_RECTANGLE) {
        const Region newDirty(dirty.intersect(bounds()));
        const Rect b(newDirty.getBounds());
        eglSetSwapRectangleANDROID(dpy, surface,
                b.left, b.top, b.width(), b.height());
    }
#endif  

    if (mFlags & PARTIAL_UPDATES) {
        mNativeWindow->setUpdateRectangle(dirty.getBounds());
    }  

    mPageFlipCount++;
    eglSwapBuffers(dpy, surface);
    checkEGLErrors("eglSwapBuffers");  

    // for debugging
    //glClearColor(1,0,0,0);
    //glClear(GL_COLOR_BUFFER_BIT);
}

这段代码主要用来检查系统的主绘图表面是否支持EGL_ANDROID_swap_rectangle扩展属性。如果支持的话,那么每次在调用函数eglSwapBuffers来渲染UI时,都会使用软件的方式来支持部分更新区域功能,即:先得到不在新脏区域里面的那部分旧脏区域的内容,然后再将得到的这部分旧脏区域的内容拷贝回到要渲染的新图形缓冲区中去,这要求每次在渲染UI时,都要将被渲染的图形缓冲区以及对应的脏区域保存下来。注意,如果系统的主绘图表面同时支持EGL_ANDROID_swap_rectangle扩展属性以及部分更新属性,那么将会优先使用部分更新属性,因为后者是直接在硬件上支持部分更新,因而性能会更好。

在Android源码中有以下对framebuffer的结构定义:
hardware/libhardware/include/hardware/gralloc.h

typedef struct framebuffer_device_t {
    struct hw_device_t common;  

    /* flags describing some attributes of the framebuffer */
    const uint32_t  flags;  

    /* dimensions of the framebuffer in pixels */
    const uint32_t  width;
    const uint32_t  height;  

    /* frambuffer stride in pixels */
    const int       stride;  

    /* framebuffer pixel format */
    const int       format;  

    /* resolution of the framebuffer‘s display panel in pixel per inch*/
    const float     xdpi;
    const float     ydpi;  

    /* framebuffer‘s display panel refresh rate in frames per second */
    const float     fps;  

    /* min swap interval supported by this framebuffer */
    const int       minSwapInterval;  

    /* max swap interval supported by this framebuffer */
    const int       maxSwapInterval;  

    int reserved[8];  

    int (*setSwapInterval)(struct framebuffer_device_t* window,
            int interval);  

    int (*setUpdateRect)(struct framebuffer_device_t* window,
            int left, int top, int width, int height);  

    int (*post)(struct framebuffer_device_t* dev, buffer_handle_t buffer);  

    int (*compositionComplete)(struct framebuffer_device_t* dev);  

    void* reserved_proc[8];  

} framebuffer_device_t;

以上声明中,成员函数compositionComplete用来通知fb设备device,图形缓冲区的组合工作已经完成。引用参考[2]的文章说明,此函数指针并没有被使用到。那么,我们就要找到在哪里能够获取得到屏幕渲染完成的信号量了。

这个问题建议大家先行阅读所有引用参考文章。然后因为懒,这里就直接给出大家结论,过程需参考surfaceflinger的所有源码。

我们都知道Android在渲染屏幕的时候,一开始用到了double buffer技术,而后的4.0以上版本升级到triple buffer。buffer的缓存是以文件内存映射的方式存储在dev\graphics\fb0路径。每块buffer置换的时候,会有唯一的,一个,信号量(注意修饰语)抛给应用层,接收方是我们经常用到的SurfaceView控件。SurfaceView内的OnSurfaceChanged() API 即是当前屏幕更新的信号量,除此之外,程序无从通过任何其他官方API形式获取屏幕切换的时间点。这也是Android应用商场为何没有显示当前任意屏幕的FPS数值的软件(补充一下,有,需要Root,用到的就是本文后续介绍的技术。准确来说,是本文实现了一遍他们的技术)。

本文将在稍后的独立章节说明如何实现强行暴力获取埋在系统底层surfaceflinger service内的信号量。

Hooker 代码注入

系统屏幕切换所用到的函数是在surfaceflinger内的elfswapbuffer()函数,要获取得系统屏幕切换的信号量,需要劫持surfaceflinger service内的elfswapbuffer()函数,替换成我们自己的newelfswapbuffer()函数,并在系统每次调用newelfswapbuffer()函数时,此向JNI层抛出一个信号量,这样就能强行获得屏幕切换状态量。

而,这样做,需要用到hooker技能,向系统服务注入一段代码,勾住elfswapbuffer()函数的ELF表地址,然后把自己的newelfswapbuffer()函数地址替换入ELF表内。在程序结束后,需要逆向实现一遍以上操作,还原ELF表。

程序用到了以下两个核心文件:

一个文件负责注入系统服务,另一个负责感染系统程序。

Inject surfaceflinger

int main(int argc, char** argv) {

    pid_t target_pid;
    target_pid = find_pid_of("/system/bin/surfaceflinger");
    if (-1 == target_pid) {
        printf("Can‘t find the process\n");
        return -1;
    }

    //target_pid = find_pid_of("/data/test");
    inject_remote_process(target_pid, argv[1], "hook_entry",  argv[2], strlen(argv[2]));

    return 0;
}

Infect surfaceflinger

int hook_entry(char * argv) {

    LOGD("Hook success\n");

    LOGD("pipe path:%s", argv);

    if(mkfifo(argv, 0777) != 0 && errno != EEXIST) {
        LOGD("pipe create failed:%d",errno);
        return -1;
    } else {
        LOGD("pipe create successfully");
    }

    LOGD("Start injecting\n");

    elfHook(LIB_PATH, "eglSwapBuffers", (void *)new_eglSwapBuffers, (void **)&old_eglSwapBuffers);

    while(1){

        int fPipe = open(argv, O_TRUNC, O_RDWR);
        if (fPipe == -1) {
            LOGD("pipe open failed");
            break;
        } else {
            LOGD("pipe open successfully");
        }

        char command[10];
        memset(command, 0x0, 10);

        int ret = read(fPipe, &command, 10);
        if(ret > 0 && strcmp(command, "done") == 0) {
            LOGD("ptrace detach successfully with %s", command);
            break;
        } else {
            LOGD("ret:%d received command: %s", ret, command);
        }

        // close the pipe
        close(fPipe);

        usleep(100);

    }

    elfHook(LIB_PATH, "eglSwapBuffers", (void *)old_eglSwapBuffers, (void **)&new_eglSwapBuffers);

}

我们能看到以上代码还用到了pipe管道通讯,那是因为注入的是一段二进制可执行代码,而我们在退出程序时需要与此二进制代码通讯,以便正常退出。

关于代码的hook,大家可以参考之前的文章:点击打开链接

时间: 2024-11-05 18:57:35

Android逆向工程的相关文章

Android 逆向工程 实践篇

Android逆向工程 实践篇 上篇给大家介绍的是基础+小Demo实践. 如果没有看过的同学可以进去看看.(逆向工程 初篇) 本篇主要给大家介绍如何反编译后修改源码, 并打包运行在手机上. 先介绍下本篇文章用到的工具和资源. 1: Android Killer 1.3.1.0 (工具) 2: crackme.apk 还没有破解之前会提示随意输入用户名密码会提示下面的信息. 下面我们来打开apk, 看看源码是怎么回事. 我先用工具(Android Killer) 打开creckme.apk 这个是

Android逆向工程工具 - 3

<Android逆向工程工具 - 1 https://www.cnblogs.com/cuihengchaliao/p/6661871.html>提到经过修改重新打包的apk文件,需要再重新签名才能安装. 再重新签名时,如果是正规途径的修改,原来版本的签名证书可以使用:但如果是不正规途径的apk包修改,重新签名时原来开发者的证书是不可用的,这时修改者一般会用工具自己生成公钥.私钥对和证书,对修改的apk包进行重新签名,但是因为证书和原版的证书不一样,安装时是无法覆盖原版的,同时原版开发者也会自

Android逆向工程-破解 哈皮妹-萝莉

转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/18797493 前言 新的一年新的开始,除了继续我的原有课题之外,我还打算研究下Android逆向工程的一些东西,主要包括反编译.Smali.APK打包.签名.反逆向和移动安全等.这篇就是新课题的第一篇文章,不过要牺牲下哈皮妹-萝莉这款应用了.通过对哈皮妹-萝莉的破解,让我更加深刻直观地认识到,Android的安全性是一个多么大的问题,如果我们的应用没有采用特殊手段去阻止破解

Android逆向工程工具Dare的使用方法(Mac OS X中)

其实这篇日志很简单,争取用两句话说完.Dare这个工具是宾州大学计算机系发布的apk逆向工程工具.可以将Android系统中使用的apk文件反编译为Java Class文件.目前支持Linux和Mac OS X中使用,在Mac中的使用方法尤其简单,在这个页面:http://siis.cse.psu.edu/dare/downloads.html 提供可执行文件的下载,下载解压之后在终端中进入其目录,输入: bash dare -d apkoutput WeChat_462.apk 上面是以最新的

Android逆向工程初步(一) 15.4.24

最近看了看Android的逆向工程,破解的书,像是<Android Hack‘s Book>之类的,感觉挺有意思的,看了看一些smali的语法,试着自己写了个demo玩玩: 1.工具: 最新版的apktool2.0:http://connortumbleson.com/2015/04/20/apktool-v2-0-0-released/ 安装方法在:http://ibotpeaches.github.io/Apktool/install/ apk签名工具(懒得手动了):http://www.

Android 逆向工程之步骤

PS:本系列文章中所涉及到的技术.数据和接口地址,仅供学习交流,务必不可做坏事或者是用于商业用途!否则后果自负! 来源:http://blog.csdn.net/zhaokaiqiang1992 逆向工程 代码框架解析 TcpDump抓包 WireShake分析数据包 逆向工程 既然是做高仿,当然需要逆向工程了,由于Android是基于Java的,所以反编译的难度并不大,各种资料很丰富,所以这里就简单介绍. 我们在反编译的时候,可以按照下面的流程进行 使用apktool将apk进行反编译,获取到

android 逆向工程 smail 语法学习

众所周知,android 是开源的,现在市场上反编译别人的劳动果实的人也不少,所以我们也是有必要学习下smail语言,(就是android工程反编译后出的语法语音),看看改怎么给我们的代码 "埋雷" ,才能更好的保护好我们自己的劳动成果. 接下来就让我们来学习下吧~!(事先声明:本人也是初学smail语言,有介绍不当的地方还请海涵,并请指出,大家一起学习) ========================最后附上一片文章  http://www.blogjava.net/midea09

Android逆向工程工具

Android上的编程主要有两种,一种是使用Adroid SDK(Software Development Kit),用Java开发:一种是使用Android NDK(Native Development Kit),用C/C++开发,因为Android内核是基于Linux Kernel的,用C/C++是最便捷的方式但相对复杂.这里针对的是Java开发的方式. Adroid上的Java虚拟机Dalvik虚拟机和经典java运作模式大致相同,但Dalvik虚拟机是为Adroid系统优化定制过的. 标

android(二)

Gradle中buildToolsVersion和TargetSdkVersion的区别是什么 compileSdkVersion, minSdkVersion 和 targetSdkVersion 的作用:他们分别控制可以使用哪些 API ,要求的 API 级别是什么,以及应用的兼容模式.TargetSdkVersion 设为21那么是按6.0设置的(运行时权限),小于21是按6.0以前的方式 进程间怎么通信 binder是安卓中的一个类,它实现了IBinder接口,是安卓中跨进程通信的方式.