2016年10月11日星期二

ANDROID 逆向实例(二)- XDCTF Mobile 100

刚刚接触安卓逆向不久看见 XDCTF 有部分安卓逆向的题目,拿过来看看,试试手,第一次分析 CTF 相关的题目确实用了不少时间,不过同时也学到了不少东西。

常规手段还原出 smali 文件(这里偷了个懒,直接用的 apktool),没有混淆也没有 so 加固,只需要理出 smali 处理流程即可。根据 AndroidManifest.xml 找到 com.example.ring.myapplication.MainActivity, 故从对应的 MainActivity.smali 作为起点分析。

进行了一系列的初始化操作,然后理出比较重要的代码:进入 p 函数、绑定类 d 为点击事件。

.method protected onCreate(Landroid/os/Bundle;)V
    ...
    invoke-direct {p0}, Lcom/example/ring/myapplication/MainActivity;->p()V
    ...
    new-instance v1, Lcom/example/ring/myapplication/d;
    invoke-direct {v1, p0}, Lcom/example/ring/myapplication/d;->(Lcom/example/ring/myapplication/MainActivity;)V
    invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.end method

跟进一下 p() 函数:

.method private p()V
    ...
    const-string v1, "url.png"
    invoke-virtual {v0, v1}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;
    ...
    invoke-virtual {v0, v2, v3, v1}, Ljava/io/InputStream;->read([BII)I
    ...
    const/16 v1, 0x90
    const/4 v3, 0x0
    const/16 v4, 0x10
    invoke-static {v2, v1, v0, v3, v4}, Ljava/lang/System;->arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V
    ...
    iput-object v1, p0, Lcom/example/ring/myapplication/MainActivity;->v:Ljava/lang/String;
    ...
.end method

可以看到这里打开了 asset 目录下的 “url.png”,然后读取了 0x90 ~ 0xa0 位置的内容保存到 MainActivity->v 里,下面看下 d 类:

.method public onClick(Landroid/view/View;)V
    ...
    invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
    move-result-object v0 
    ...
    # 下行是用来获取 MainActivity->v 的
    invoke-static {v2}, Lcom/example/ring/myapplication/MainActivity;->a(Lcom/example/ring/myapplication/MainActivity;)Ljava/lang/String;
    move-result-object v2
    invoke-static {v1, v2, v0}, Lcom/example/ring/myapplication/MainActivity;->a(Lcom/example/ring/myapplication/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z
    move-result v0
    ...
    if-eqz v0, :cond_0
    ...
    const-string v2, "Congratulations!"

好的,发现成功条件了,当 MainActivity->a(String,String) 返回结果为真时程序成功,看下这里的两个 String,一个是 MainActivity->v(上面说过,其实是 url.png 的 0x90 ~ 0xa0 位置), 一个是用户输入的字符串(即为 Flag),下面先看一下 MainActivity->v :


xxd 可以看到 MainActivity->v 即是 "this_is_the_key.",然后跟进 MainActivity->a(String,String) 函数,看后续流程,由下面的代码可以看出,当用户输入的内容经过转换后和 array_0 位置的内容相同为成功条件。

.method private a(Ljava/lang/String;Ljava/lang/String;)Z
    ...
    new-instance v0, Lcom/example/ring/myapplication/c;
    invoke-direct {v0}, Lcom/example/ring/myapplication/c;->()V
    invoke-virtual {v0, p1, p2}, Lcom/example/ring/myapplication/c;->a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    ...
    const/16 v1, 0x20
    new-array v1, v1, [B
    fill-array-data v1, :array_0
    new-instance v2, Ljava/lang/String;    
    invoke-direct {v2, v1}, Ljava/lang/String;->([B)V      # 将 array_0 位置的内容转化成字符串
    invoke-virtual {v0, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)
    move-result v0
    return v0
.end method

在比较之前,函数初始化了类 c,然后执行 c->a(String,String) ,这里有点复杂,偷个懒使用 jeb 还原 java 代码看,这里。



可以看到做的主要的事情有:进入 c->a(String) 函数将 bytes 分为两个一组,然后组内交换顺序,那么现在的 MainActivity->v 就变成了 "htsii__sht_eek.y";另外初始化了类 a,并执行了 a->a(byte[]) 和 a->b(byte[]) 操作,看下类 a 的代码。


可以看出这里其实是以 MainActivity->v 作为了 AES 加密 Key,加密了用户输入的字符串这个操作。所以最后的比较即是:AES 密文和 array_0 位置数据的比较,写了个解密代码,运行出来了 Flag。

import java.io.*;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;

class test  
{
 public static void main (String[] args) throws java.lang.Exception
 { 
         String key = "htsii__sht_eek.y";
         SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
         Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
         byte[] bye = {21,-93,-68,-94,86,117,-19,-68,-92,33,50,118,16,13,1,-15,-13,3,4,103,-18,81,30,68,54,-93,44,-23,93,98,5,59};
         /*
            byte[] byteContent = content.getBytes("utf-8");  
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);// 初始化   
            byte[] result = cipher.doFinal(byteContent);  
            String str2 = new String(result,"UTF-8");
            System.out.println(str2); // 加密   
            */
            
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] result2 = cipher.doFinal(bye);
            String str3 = new String(result2, "utf-8");
            System.out.println(str3);
 }
}


# LCTF{1t's_rea1ly_an_ea3y_ap4}

* 逆向的 apk 上传到了 Github