2016年9月25日星期日

Shadowsocks 的定制和 “不安全性” 的分析

用 Shadowsocks 有很长一段时间了,确实非常好用,速度不错而且能够满足很多工作中的需求。之前也只是一直用着没有做过多的分析,直到最近看见 ShadowsocksR 这个非官方的项目发现 Shadowsocks 稍做修改便能够实现定制化,比较方便。

SS 的工作流程图下图所示:


流程图中其实显示的很清楚了,要定制自己的 Shadowsocks 服务只需要对 # 2 处的代码进行相应的混淆,而在 # 3 处进行反混淆即可,通过看 SS 代码可以看到这部分的内容在 shadowsocks/tcprelay.py 文件定义的类 TCPRelayHandler 方法 _handle_stage_stream() 中处理,该段的代码如下所示:

def _handle_stage_stream(self, data):
    if self._is_local:
        # ss-local encrypt data and send to ss-server, _remote_sock -> ss-server
        if self._ota_enable_session:
            data = self._ota_chunk_data_gen(data)
        data = self._encryptor.encrypt(data)
        self._write_to_sock(data, self._remote_sock)
        ...

可以看到 ss-local 加密之后写进 socket 发往 ss-remote,这种情况下的 data 为明文 data,此时加密方式可以自己定义,也可以自己修改报文,伪造其他协议。使用 SSR 来免流就是用的这个方法,伪造成 HOST 为服务商的 HTTP 请求达到欺骗基站的目的。

这种情况下还需要在 ss-server 做对应的解密,找到该类的 _on_remote_read() 函数进行相应的反操作即可。

def _on_remote_read(self):
    # handle all remote read events, deal with HTTP request data.
    data = None
    ...
    if self._is_local:
        data = self._encryptor.decrypt(data)
    else:
        data = self._encryptor.encrypt(data)

另外说一点,SS 现在确实在部分情况下可以被较准确地探测,前提是:

1. 使用了 RC4 、AES-256-* 类型的流加密方式,加密之后前 16 字节为随机生成的 IV,第 17 字节表示通过 SS 的协议类型(1 - IPv4,3 - DNS,4 - IPv6),紧跟着从 18 字节开始表示目的地址,长度不定。
2. 没有开启 SS 的 AutoBan 功能,防火墙没有做其他类型的尝试数量限制。

SS Server 端的特性是,如果解密之后的明文在第 17 字节位置上的值不是(1、3、4)中的一个,那么就立马断开连接,否则就继续运行对后面的目的地址进行连接和请求。

而 breakwa11 给出的测探 SS Server 的方法并不是要破解整个密文,而是构造特定密文给 SS Server 去解密,并观察响应。前面 16 字节随便构造,这段密文总可以解密成功,虽然这里的明文完全没有办法知道是啥,不过也不需要知道就是了。根据流加密的特性,遍历 17 字节位置上的所有情况,总有解密之后为(1、3、4)中的某一个的,这时的 SS Server 不会立马断开,而是去解密 18 字节位置开始的目的地址,这样通过 SS Server 的响应差别便可以完成测探。

第一次测探确认成功之后会进行第二次测探,修改 18 字节位置(即:目的地址),虽然这里没办法之后解密之后的目的地址是什么,但是目前 IPv4 基本上全被占用的情况下(假设),即使修改目的地址仍然可以建立连接,如果第二次也不是马上断开,那么就能够确定是运行着 SS Server。

这个探测方法确实是可以成功探测到特殊情况下 SS Server 的存在,不过感觉意义不大,有点像鸡蛋里头挑骨头的做法。而且这也不是 SS 的错,只能说是这类型加密算法的特性,SS 本来就是一个加密协议这样做也没什么有问题的地方。

同时可以发现,breakwa11 在这个 comment 里也有提到,chacha20 这类使用 8 字节 IV 的加密更少被探测,因为默认的 AES-256-* 是 16 字节 IV,而 GFW 主要探测的为 16 字节 IV,所以换成 chacha20 往往 SS 速度会得到提升,因为绕过了部分区域的 QoS。(但并不是这种不能被探测,根据上面的做法修改第 10 位开始的目的地址就行了)

*附上抓的部分请求,可以大致描绘 SS 的网络活动,比较简单不具体分析了。





Read More

2016年9月17日星期六

ASM 逆向学习笔记 (一)- 汇编基础


因为安卓逆向时而需要分析在 .so 文件中定义的 natvie 函数,所以对汇编的指令伪代码也需要进行学习,在《Android软件安全与逆向分析》中的第六章针对部分指令有所讲述,不过例子感觉举的不太充分,下面对部分内容做一个备忘。

Register

R0 ~ R3 :用作传入函数参数,传出函数返回值,比方说 test 函数需要两个参数一个返回值,那么即使用 R0、R1 作为函数的参数,返回值保存在 R0 里。在子程序调用之间,可以将 R0 ~ R3 用于任何用途。 被调用函数在返回之前不必恢复。如果调用函数需要再次使用 R0 ~ R3 的内容,则它必须保留这些内容。

R4 ~ R11 :用作存放函数的局部变量,作用域是单个子程序。如果被调用函数使用了这些寄存器,它在返回之前必须恢复这些寄存器的值。在程序运行过程中,编译器会自动保护R4~R11。

R12:内部调用暂时寄存器 IP。它在过程链接胶合代码(例如,交互操作胶合代码)中用于此角色。 在过程调用之间,可以将它用于任何用途。被调用函数在返回之前不必恢复 R12。

R13:栈指针 SP,不能用于任何其它用途,SP 中存放的值在退出被调用函数时必须与进入时的值相同。 每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。这样当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。

R14:链接寄存器 LR,用于保存函数的返回地址。如果其他寄存器保存了函数返回地址,则可以在调用之间将 R14 用于其它用途,程序返回时要恢复。(1)保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入 R14 中;子程序通过把 R14 复制到PC来实现返回。(2)当异常发生时,异常模式的 R14 用来保存异常返回地址,将 R14 如栈可以处理嵌套中断。

R15:程序计数器 PC,用于指向当前指令地址的指针。它不能用于任何其它用途。

Command

STR:STR{条件} 源寄存器,<存储器地址>

STR R0,[R1],#8     ;将 R0 中的字数据写入以 R1 为地址的存储器中,并将新地址 R1+8 写入 R1 。
STR R0, [R1, #8]   ;将 R0 中的字数据写入以 R1+8 为地址的存储器中。

LDR:LDR{条件} 目的寄存器,<存储器地址>

LDR R0,[R1,R2,LSL#2]!     ;将存储器地址为 R1+R2×4 的字数据读入寄存器 R0,并将新地址 R1+R2×4 写入 R1。
LDR R0,[R1],R2,LSL#2      ;将存储器地址为 R1 的字数据读入寄存器 R0,幵将新地址 R1+R2×4 写入 R1。
LDR R0,0x12345678           ;将 0x12345678 地址保存的数据放入 R0
LDR{cond}  register,=[expr | label-expr] ;伪代码

B 指令向前向后跳转的情况

cond_1: ;A
cmp r0, #0
beq cond_1f ; r0==0那么向前跳转到B处执行, f 表示 forward
bne cond_1b ; 否则向后跳转到A处执行, b 表示 backward
cond_1: ;B

感叹号[!]:可以用来使指令执行后,更新基底位地址。

LDR R0, [R1, #8]! ;[R1+8]->R0 
                 ;R1+8->R1
LDR R0!, {R1-R4}  ; R1=[R0], R2=[R0+#4]

Reference

Category:
Read More

ANDROID 逆向学习笔记 (三)- IDA 动态调试 so


前面几篇所说的均是对安卓进行 smali 静态分析的内容,本节探讨一下动态的调试和分析,将会使用到的是 IDA Pro (金山 leak)和 Android Studio 的部分工具(模拟器,ddms,adb 等)。

Android Studio 的官方网址是: https://developer.android.com/studio/intro/index.html,可以找到不同系统的版本,下载并安装好了之后在 ~/Library/android/sdk/tools 路径下可以找到安卓开发调试所需要的各种小工具。
安装好之后启动 Android Studio 找到 AVD Manager 按钮,即可建立自己的 AVD(Android Virtual Device),创建的过程中根据自己需要选择对应的参数吧,创建好之后如下图所示,此时可以直接点击绿色箭头运行,或者是终端输入来运行虚拟机。

emulator -netdelay none -netspeed full -avd $AVD_NAME
从图中可以看到我创建了名为 4_2_AMD 和 4_2_X86 的两个 AVD,X86 相对于 AMD 来说速度比较快,但是有部分软件和程序不能在 X86 上模拟只能使用 AMD。

在 IDA 的安装目录的 dgbsrv 文件夹下有调试需要用到的 android_server 二进制文件,只有在安卓机器上运行了这个文件之后 IDA 才可以正常 attach,然后这里有一个坑,X86 的模拟器运行这个是会出错的,所以需要换 AMD 的模拟器来调试,执行命令如下:

adb push IDA/android_server  /data/local/tmp
adb shell
cd /data/local/tmp
chmod 755 android_server
./android_server

没有出错的情况下会提示在监听 23946 端口,这个时候使用 adb forward 将 AVD 的端口映射到本地,如果是本机运行的 IDA 即可直接 attach。不过,如果要是其他主机需要 attach 呢,我这里使用的命令如下。

adb forward tcp:23946 tcp:23946
socat -v tcp-listen:12344,fork tcp-connect:localhost:23946

IDA -> Debugger -> Attach -> remote ARMLinux/Android debugger,按要求填写即可。

如果上述步骤都成功的话会进入这个界面,点击需要调试的进程即可进行动态调试。下面使用 MSC 的第二题来做详细说明。

MSC Challenge # 2

先反编译 crackme1.apk,看一下 smali 代码,在 com/yaotong/crackme/MainActivity.smali 中可以看到按钮绑定的 onClick() 事件调用了 securityCheck() 来检查输入是否正确,而该函数定义含有 native 属性,可知该函数是在 .so 文件中定义的,尝试静态分析 .so 文件,直接把 libcrackme.so 拉入 IDA 中进行分析,关键代码如下所示。

  v5 = (_BYTE *)((int (__fastcall *)(JNIEnv *, int, _DWORD))(*v3)->GetStringUTFChars)(v3, v4, 0);
  v6 = off_628C;
  while ( 1 )
  {
    v7 = (unsigned __int8)*v6;
    if ( v7 != *v5 )
      break;
    ++v6;
    ++v5;
    v8 = 1;
    if ( !v7 )
      return v8;
  }

可以看到使用用户传入的字符串跟 off_628C 位置的数据进行了比较,双击进入 off_628C 的位置,如下所示,可以看到静态分析得到的 key 是 [wojiushidaan],输入 apk 后并不正确,说明这个字符串在动态运行的时候做了变换,所以还需要使用 IDA 进行动态的分析。

.data:0000628C ; .data         ends                    ; "wojiushidaan"

这里静态的分析有一个小 trick,使用 IDA 的时候可以通过 shift + F12 来显示所有定义的字符串内容,本例中通过 shift + F12 可以看到,检测到了 “wojiushidaan” 这个字符串,不过这里并不是真正的答案。



先启动 crackme.apk 之后再用 IDA 进行 attach,IDA 闪退,说明程序做了反调试机制,检测到程序的处于被调试状态则退出,所以猜测时 .so 文件加载时进行了特殊的处理,来看一下 .so 文件的运行情况。

因为很多情况 .so 文件在 apk 运行的开始就会进行加载,所以如果直接运行软件然后再去 IDA 找对应的进程进行 attach 这样很有可能就会直接错过 .so 文件加载时的运行情况,所以这里使用

adb shell am start -D -n com.yaotong.crackme/.MainActivity

这样会启动 crackme 但是不会往下运行,因为添加了 -D 参数,说明等待调试器连接启动程序之后刮起,后面的 com.yaotong.crackme/.MainActivity 对应着上文中的 smali 目录结构和主 Activity 的名称。

这样启动的程序就可以用 IDA 来 attach 了,然后再在 .so 文件载入时下一个断点,这样就能够实时的分析 .so 文件加载时的运行情况了。下断点的方式为,Debugger -> Debugger setup -> suspend on library load/unload。另外这里值得一提的是,因为程序处于挂起状态,如果直接操作 IDA 的话没有任何反应,所以这里需要使用 jdb 恢复运行。

ddms
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

看到执行 jdb 命令之前首先执行了 ddms(即 Dalvik Debug Monitor Service),jdb 的运行在 OS X 上需要 ddms 为启动状态,在之前提到的 sdk/tools 文件夹中可以找到 ddms,启动即可。此时点击 IDA 的绿色小箭头运行即可。

此时正常运行,到加载 .so 文件时会弹出两个框,ESC 取消掉即可,然后即可进行实时调试,找出反调试的位置,去掉反调试之后继续调试找到变换后的 key。系统加载 .so,在完成装载、映射和重定向以后,就首先执行 .init 和 .init_array 段的代码,之后如果存在 JNI_OnLoad 就调用该函数。对一个 .so 进行分析,需要先看看有没有 .init_array section 和 .init section ,.so加壳一般会在初始化函数进行脱壳操作。

F8 单步调试后发现运行到 JNI_OnLoad 伪代码 33 行的这一段之后调试器会闪退,F7 跟进函数看看具体的操作。

v5 = dword_4BE442B4(v8, 0, &unk_4BE3F6A4, 0);

F7 指示跳转到 pthread_create 函数,所以猜测这里使用了 phtread_create 来检查函数是否有调试器 attach 的操作,如果存在即退出,这是一种反调试的手段,知道上述情况之后再开一个 IDA 来进行 .so 的静态分析,找到 JNI_OnLoad 函数的 33 行。

v5 = dword_62B4(handle, 0, sub_16A4, 0);

故这里的 dword_64B4 即为 pthread_create,根据 pthread_create 定义,可知第三个参数为新线程的起始地址,即新开了一个线程执行 sub_16A4 操作,并且在这个操作执行的过程中调试器闪退。刚开始看的时候可能会有一个疑问,为什么不直接静态分析 dword_64B4 位置的内容呢,因为静态分析时这个位置的内容还没有被赋值,所以直接看的话会是这样的。

.bss:000062B4 dword_62B4  % 4   # 只有在动态调试时的这个 % 4 会变成相应的地址,才可以继续分析

sub_16A4 在 F5 之后的伪代码如下所示,可以看到循环调用了 sub_130C 和 dword_62B0(3) 两条指令,猜测是不断对程序进行调试器检测的操作。

void __noreturn sub_16A4()
{
  while ( 1 )
  {
    sub_130C();
    dword_62B0(3);
  }
}

不过直接看 sub_16A4 的代码显得比较复杂,这时可以在 libc.so 文件的 fopen 函数下断点,因为检测是否 attach 调试器一般都是通过 fopen 打开对应的 pid 文件 [/proc/$pid/status],然后获取对应 TracerPid,如果该值不为 0 则说明存在 attach 的调试器。

下断点的方式:在对应函数处右键 add breakpoint, 或者是在函数的起始行将行首的小圆点,点亮。下好断点之后,点击 IDA 的运行,PC 指针会执行到断点处后停止,如图所示。
这时将 Hex View 设置与 R0 同步可以看到 R0 为 /proc/1182/status,即当前程序的 Pid 为 1182。接下来看看是在什么地方调用的 fopen 函数,将 IDA View 设置与 LR (返回地址)同步,可以看到如下。
可以看到 LR 处于 libcrackme.so 里,且相对位移为 4BE3F420 - 4BE3E000 = 1420,那么在静态分析中找到偏移为 1420 位置,可以发现其实是在 sub_130C 里,而这个 sub_130C 正是 sub_16A4 内调用的函数,故推测 sub_130C 函数为调试检测函数,将 sub_16A4 内的 [BL sub_130C] 指令替换成 [MOVS R0,R0] 即可取消该调试函数。

而之所以使用 [MOVS R0, R0](指令码:A0 E1)而不是 NOP(指令码:C0 46),是因为 ARM 的平台中并不像 X86 一样拥有 NOP 指令。

然后保存 libcrackme.so 文件,重新打包进 apk 文件、签名,此时已经可以随意 attach 调试器并且没有调试器检测,动态调试断点到 Java_com_yaotong_crackme_MainActivity_securityCheck 函数的 v6 查看结果即可。

Kill Bypass反调试

使用 [kill -19 $Pid ]来达到挂起程序的目的,然后 attach IDA 的调试器,此时因为程序处于挂起状态,反调试不起作用,直接分析即可,详情见 伪·MSC解题报告 - QEver

其他参考资料:

安卓动态调试七种武器之孔雀翎 – Ida Pro -蒸米

*文中分析的 apk 文件和部分工具可以在 AndroidRevStudy 找到
Read More

2016年9月12日星期一

ANDROID 逆向实例(一)- acce3ft.aevbbb


对于恶意软件不进行打码处理了,网址是 http://hpwxn.com/ 浏览之后可以下载两个 apk ,不过从文件大小来看应该是一个文件只不过进行了改名。

一般这种恶意软件既不会加壳也不会做复杂的混淆处理,分析起来很简单。直接反编译看 AndroidManifest.xml,找到如下一行。

<activity android:label="@string/app_name" android:name="acce3ft.aevbbb.MainActivity">

可以看到程序的 MainActivity, 直接看 acce3ft/aevbbb/MainActivity.smali 文件。

OnCreate

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4

    const/4 v3, 0x1

    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    invoke-super {p0, v3}, Landroid/app/Activity;->requestWindowFeature(I)Z

    const/high16 v0, 0x7f030000

    invoke-virtual {p0, v0}, Lacce3ft/aevbbb/MainActivity;->setContentView(I)V

    invoke-static {}, Lacce3ft/aevbbb/util/MyApplication;->b()Landroid/content/Context;

    move-result-object v0

    invoke-static {}, Lacce3ft/aevbbb/util/c;->a()Z

    move-result v1

    if-nez v1, :cond_0

    invoke-virtual {p0, v0}, Lacce3ft/aevbbb/MainActivity;->a(Landroid/content/Context;)V

    invoke-virtual {p0}, Lacce3ft/aevbbb/MainActivity;->finish()V

    :cond_0
    new-instance v1, Landroid/content/ComponentName;

    const-class v2, Lacce3ft/aevbbb/NetstateReceiver;

    invoke-direct {v1, v0, v2}, Landroid/content/ComponentName;-><init>(Landroid/content/Context;Ljava/lang/Class;)V

    invoke-virtual {v0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;

    move-result-object v0

    const/4 v2, 0x2

    invoke-virtual {v0, v1, v2, v3}, Landroid/content/pm/PackageManager;->setComponentEnabledSetting(Landroid/content/ComponentName;II)V

    invoke-static {}, Lacce3ft/aevbbb/util/c;->d()V

    return-void
.end method

进行了一系列的初始化操作,主要就是查看当前手机是否符合恶意软件的运行环境(权限、组件的开启情况等),需要注意的是在最后调用了 Lacce3ft/aevbbb/util/c;->d() 跟一下这个函数,该片段主要的代码结构如下

const-string v1, "CONTROL_NUMBER"

    sget-object v2, Lacce3ft/aevbbb/util/a;->g:Ljava/lang/String;

    invoke-interface {v0, v1, v2}, Landroid/content/SharedPreferences;->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

    move-result-object v1

    sput-object v1, Lacce3ft/aevbbb/util/a;->g:Ljava/lang/String;
即将 Lacce3ft/aevbbb/util/a; 类的各个参数进行重新赋值,本片段中是给 g 参数进行复制,因为重复地对该类进行赋值和读取操作,所以推测该类作用为储存恶意软件运行参数,查看该类的 smali 文件。

.method static constructor <clinit>()V
    .locals 1

    const/4 v0, 0x0

    sput v0, Lacce3ft/aevbbb/util/a;->a:I

    const-string v0, "sss"

    sput-object v0, Lacce3ft/aevbbb/util/a;->b:Ljava/lang/String;

    const-string v0, "15274907736@189.cn"

    sput-object v0, Lacce3ft/aevbbb/util/a;->c:Ljava/lang/String;

    const-string v0, "aaaa123123"

    sput-object v0, Lacce3ft/aevbbb/util/a;->d:Ljava/lang/String;

    const-string v0, "15274907736@189.cn"

    sput-object v0, Lacce3ft/aevbbb/util/a;->e:Ljava/lang/String;

    const-string v0, "15274907736"

    sput-object v0, Lacce3ft/aevbbb/util/a;->f:Ljava/lang/String;

    const-string v0, "15274907736"

确实如之前猜测一样,是参数类,邮箱账号和密码如上所示,登陆一下看看。


中招的挺多的,从邮件分析了一下恶意软件的运行机制,会从各个网银、理财等软件里向手机发起验证码,进行转账第一笔的 5 块转账,目的是为了查看是否能够走通流程,如果成功的话,会转出后续的余额,看了一下最多的一个账户余额为 7 w,到我查看的时候已经所剩无几。

OnStart


主要进行了两个操作,如果可以获取管理员权限(ROOT)即进行添加管理员操作(状态码:1),不行的话就尝试修改默认 SMS 使用的 package 为恶意软件的 package(状态码:0),执行完毕之后带着状态码进入 Lacce3ft/aevbbb/MainActivity;->startActivityForResult() 函数。

而与 startActivityForResult() 对应的, onActivityResult() 中设置了各个状态码对应的操作。几个比较明显的代码段如下。

const-string v0, "device_policy" # 检查当前是否为管理权限

invoke-virtual {p0, v0}, Lacce3ft/aevbbb/MainActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;

move-result-object v0

check-cast v0, Landroid/app/admin/DevicePolicyManager;

new-instance v1, Landroid/content/ComponentName;

const-class v2, Lacce3ft/aevbbb/MyAdmin;

invoke-direct {v1, p0, v2}, Landroid/content/ComponentName;->(Landroid/content/Context;Ljava/lang/Class;)V

invoke-virtual {v0, v1}, Landroid/app/admin/DevicePolicyManager;->isAdminActive(Landroid/content/ComponentName;)Z

move-result v0
---
const-string v1, "isAdminActive" # 设置当前应用的管理员权限

invoke-interface {v0, v1, v3}, Landroid/content/SharedPreferences$Editor;->putInt(Ljava/lang/String;I)Landroid/content/SharedPreferences$Editor;

invoke-interface {v0}, Landroid/content/SharedPreferences$Editor;->commit()Z 
---
const-string v1, "android.provider.Telephony.SMS_DELIVER" # 设置当前应用为默认 SMS

invoke-virtual {v0, v1}, Landroid/content/Intent;->setAction(Ljava/lang/String;)Landroid/content/Intent;

iget-object v0, p0, Lacce3ft/aevbbb/MainActivity;->b:Landroid/content/Intent;

invoke-virtual {p0, v0}, Lacce3ft/aevbbb/MainActivity;->startService(Landroid/content/Intent;)Landroid/content/ComponentName;

后续行为不做分析了,基本上千篇一律,删除并举报了邮箱。不过值得一提的是,在 360 显危镜和腾讯哈勃的 app 扫描里,该软件并没有被确定是恶意软件。

*文中分析的 apk 文件可以在 AndroidRevStudy 找到
Read More

2016年9月5日星期一

ANDROID 逆向学习笔记 (二)- Smali 和 Dalvik


上一篇文章说到了 apk 解压得到 classes.dex 再反编译得到 smali 代码文件,那么这个关键的 classes.dex 是什么呢?提到 .dex 那么就会想到 Dalvik,Dalvik 是谷歌专门为安卓设计的虚拟机而 .dex 之于 Dalvik 就相当于 Windows 里的 .exe,是 Dalvik 可以直接执行的格式所以分析 .dex 文件是安卓逆向很重要的一部分。

关于 Dalvik 存在的意义、运行方式和 JVM 虚拟机之间差异的问题感兴趣的可以自行搜索了解,另外在高版本的安卓系统中,谷歌已经使用 ART 代替了 Dalvik 为实现更高的效率,可能存在部分差异,后续文中如果没有特殊说明均是针对 Dalvik。

因为 classes.dex 是运行在 Dalvik 的可执行文件,所以是否熟悉 Dalvik 的操作指令是决定我们阅读反编译文件快慢的重要因素。Dalvik 全部的 OpCodes 可以在 这里 查看,另外关于 OpCodes 的解释可以看 什么是 OpCode,Dalvik 的 OpCodes 相对来说很多还是很好理解的,不过结合英文和《Android 软件安全与逆向分析》来看仍然发现部分表述不很清晰的地方,本篇主要对一些容易混淆和迷惑的地方做一个注解。

Dalvik OpCodes

iget v2, p1, Lcom/tencent/mm/sdk/modelbase/BaseResp;->errCode:I
#将 p1 中的 errCode 以 I (整型)的方式取出放在 v2 变量里,
#另外 p1 的类型是 com.tencent.mm.sdk.modlbase.BaseResp。

nop #不进行任何操作,
#不过需要注意的是有的情况不存在 nop 指令,需要使用 movs r0,r0 代替。
*put*  # 赋值操作
iput-object v0, p0, Lcom/yodo1/sdk/wxapi/WXPayEntryActivity;->api:Lcom/tencent/mm/sdk/openapi/IWXAPI;
# 将 v0 的值放入 p0 类指针指向的类的 api 元素
# 并且 v0 和 api 的类型为 com.tencent.mm.sdk.openapi.IWXAPI

invoke* # 执行方法操作
invoke-direct {p0}, Ljava/lang/Object;->()V
# 执行类指针 p0 指向的类的初始化函数,此时大括号里的内容为参数
# 即 p0,而 baksmali 反编译出来的 p0 代表的是 $this 指针

Smali Program

*field* # 定义变量
.field public static final STATUS_FAILED:I = 0x0
# 定义 STATUS_FAILED 为一个 I 型的静态变量,并且值为 0
# .field 可能会存在 synthetic 属性,而这个属性在 Java 源代码里是不会出现的,一般涌来表示继承类之间的相互关系


.class public Lcom/disney/WMW/WMWActivity;          # 当前类名
.super Lcom/disney/common/BaseActivity;             # 父类名
.source "WMWActivity.java"                          # 源文件名
.implements Lcom/burstly/lib/ui/IBurstlyAdListener; # 接口名

.locals    3 # 当前代码段使用 3 个本地变量
.registers 5 # 当前代码总共使用 5 个寄存器,包括本地变量和函数参数
# 两种命名所达到的效果一样,不同的是 .locals 声明不包括函数参数所使用的寄存器,
# 而 .registers 包含当前代码段所有使用到的寄存器。修改 smali 时需要注意使用的寄存器是否增加

.method public onResult(IILjava/lang/String;)V # 当前函数的调用参数有 4 个,分别是 p0(this->)、p1(I)、p2(I)、p3(L) 
    .locals 6                                  # V 命名法,6 个本地变量,对应的则是 .registers 10

Strings.xml

使用将 apk 解压之后得到的 res 文件夹中会存在很多 strings.xml 文件,这样的文件往往是用来将代码中的特殊字符串替换为较简单字符串。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="yodo1_string_dialog_btn_no"></string>
    <string name="yodo1_string_dialog_btn_yes"></string>
    <string name="yodo1_string_dialog_exit_message">真的要退出游戏吗?</string>
    <string name="yodo1_string_dialog_tips_wait">请稍后 ...</string>
    .........
</resources>
有时这样的对应关系可能会透漏部分关键字的信息,所以根据对应的 name 来查找全文可能会有不错的收获。

静态分析步骤

crackme.apk -> 解压 -> 查看 xml 文件 -> baksmali / classes.dex -> MainActivity.smali ->  lib/armeabi/crackme.so -> smali -> 重新打包 crackme.apk -> 重新签名
Read More

2016年9月1日星期四

ANDROID 逆向学习笔记 (一)- smali 简单介绍


由于 Web 安全相对来二进制来说更容易上手,最早的时候选择了相对简单的路。随着接触的时间变长,感觉局限于 Web 而抵触二进制实在狭隘,同时也是生活的百无聊赖给了充足的时间,开始尝试学一些新的事物。


在此之前没有接触过安卓或者是 Java 方面的内容,学起来难免有些迟钝,不过好在时间很多~

OK,闲扯到此结束,q

我是通过《Android 软件安全与逆向分析》这本书结合网上的资源(Drops/Pediy/博客)来开始学习的。书能够给予部分基础知识的详解,而网上的资源则大多偏向于实践,针对具体某个apk进行分析,结合起来学习加上自己亲身实践效果很好。不过有一点需要指出的是《Android 软件安全与逆向分析》中的内容并不完全正确,有描述不准确或是明显有错误的地方最好是多印证。

BTW,为了节省时间部分显而易见的地方只做简单的文字说明,可以通过自己实践或者是简单的搜索解决问题,除非必要关键的地方不上传步骤图。另外本文中操作系统为 OS X,部分终端指令与其他系统有所差异 。

接触安卓逆向最早了解到的是 smali,smali 具体定义是什么不过分追究了,先来看一下语法,下面代码用的是 安卓动态调试七种武器之长生剑 - Smali Instrumentation 中的内容。

package com.mzheng; # JAVA 源代码

public class MZLog {

    public static void Log(String tag, String msg)
    {
        Log.d(tag, msg);
    }

    public static void Log(Object someObj)
    {
        Log("mzheng", someObj.toString());
    }

    public static void Log(Object[] someObj)
    {
        Log("mzheng",Arrays.toString(someObj));
    }

}
对应的 smali 代码
.class public Lcom/mzheng/MZLog; # class的名字
.super Ljava/lang/Object;  #这个类继承的对象
.source "MZLog.java" # java的文件名


# direct methods #直接方法
.method public constructor ()V  #这是class的构造函数实现
    .registers 1 #这个方法所使用的寄存器数量

    .prologue  # prologue并没有什么用
    .line 7 #行号
    invoke-direct {p0}, Ljava/lang/Object;->()V #调用Object的构造方法,p0相当于"this" 指针
    return-void #返回空
.end method

.method public static Log(Ljava/lang/Object;)V # Log(Object)的方法实现
    .registers 3
    .param p0, "someObj"    # Ljava/lang/Object; 参数信息

    .prologue
    .line 16
    const-string v0, "mzheng"  #给v0赋值”mzheng”

    invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String; #调用toString()函数

    move-result-object v1 #将toString()的结果保存在v1

    invoke-static {v0, v1}, Lcom/mzheng/MZLog;->Log(Ljava/lang/String;Ljava/lang/String;)V #调用MZLog的另一个Log函数,参数是v0和v1

    .line 17
    return-void
.end method

.method public static Log(Ljava/lang/String;Ljava/lang/String;)V #Log(String, String)的方法实现
    .registers 2
    .param p0, "tag"    # Ljava/lang/String;
    .param p1, "msg"    # Ljava/lang/String;

    .prologue
    .line 11
    invoke-static {p0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #调用android API里的Log函数实现Log功能

    .line 12
    return-void
.end method

.method public static Log([Ljava/lang/Object;)V #Log(Object[])函数实现 ‘[’符号是数组的意思
    .registers 3
    .param p0, "someObj"    # [Ljava/lang/Object;

    .prologue
    .line 21
    const-string v0, "mzheng"

    invoke-static {p0}, Ljava/util/Arrays;->toString([Ljava/lang/Object;)Ljava/lang/String;  #将Object数组转换为String

    move-result-object v1 #转换后的结果存在v1中

    invoke-static {v0, v1}, Lcom/mzheng/MZLog;->Log(Ljava/lang/String;Ljava/lang/String;)V #调用Log(String, String)函数

    .line 22
    return-void
.end method
可以看到 smali 相对于 JAVA 源代码来说虽然复杂了一点,但是比二进制文件逆向得到的汇编代码简单和直观的多,可读性还是很高的。smali 的具体语法和格式在上面已经很清楚了,更加详细的也在《Android 软件安全与逆向分析》中有所说明,这里不加赘述。

知道了 smali 是什么东西之后再来看看它是怎么来的。每个安卓 apk 其实都是一个压缩文件,解压缩之后可以看到有一个 classes.dex ,而反编译 classes.dex 即可得到很多 smali 文件。

反编译 classes.dex 的方法我这里使用的是 baksmali,反编译后会在当前目录生成一个 out 文件夹,smali 文件就在这个文件夹里。
https://bitbucket.org/JesusFreke/smali/downloads
baksmali classes.dex

另外一种比较傻瓜的方法是直接使用 apktool 反编译整个 apk 文件,反编译后会在当前目录生成 crackme 文件夹,下面有一个 smali 子文件夹就存放了 smali 文件。

brew install apktool
apktool d crakeme.apk

apktool 相对于 baksmali 来说就是很多种工具的结合体,把各个步骤结合起来了。推荐使用 baksmali 自己一步步动手操作,这样对各个流程能够有更清晰地认识,而 apktool 的好处是反编译得到的 xml 内容可以直接查看,通过解压缩得到的部分 xml 存在乱码的情况。
Read More