Rev - Android

by halloworld

  • 輔大資工-大三學生
  • 本名:黃品翰 (halloworld)

 

Whoami

Title Text

Play商店

奇怪的store

APK​

你的應用程式來源?

OWASP Moblie Top-10

程式碼

編譯

執行檔

一般流程

逆向

你有聽過逆向嗎?

  • 能夠了解整個app的邏輯以及行為
  • 拿到所需的資訊,或許能看到API?
    •  (e.g. 知道API,針對API進行攻擊)
  • 修改邏輯、打包
    • e.g. 繞過廣告、升級付費會員

逆向的目的

怎麼逆?

helloworld.apk

你可以先試試解壓縮他

大概看一下

配置文件

簽名訊息

資源

(被壓縮的檔案)

Dalvik

bytecode

需用到的

函式庫

編譯需用的

資源

資源

(原生的檔案)

AndroidManifest.xml

ApkTool

怎用?

apktool d <file.apk> -o <dir>
apktool d helloworld.apk -o ./helloworld

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.helloworld">
    <application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name">
        <activity android:label="@string/app_name" android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
  • activity 描述app使用介面
  • intent-filter 配置
  • action 動作 (程式入口點)
  • category 分類
    • 是應用程式中最優先被執行的Activity

META-INF

  • 目的:安裝app時做檢查及校驗
  • 加密使用private key加密
  • MANIFEST.MF
    • apk中的文件,都有hash都會對應到文件
  • CERT.SF
    • 第二道防線,對前一步的每一條內容再hash(sha1)接著進行base64
  • CERT.RSA
    • 存了public key(公鑰),以及加密算法

Smali

JVM

  • Java sourcecode => Java bytecode
  • 接者轉成機械指令,在JVM上執行
    • 跨平台windows, linux, MacOS...

Dalvik Virtual Machine

  • 由Dan Bornstein編寫
  • 適合記憶體和處理器速度有限的系統

Smali

  • 把.dex的bytecode,轉成人類可讀的語言
  • 透過smali,可以將程式邏輯進行竄改

B---byte
C---char
D---double
F---float
I---int
J---long

S---short
V---void
Z---boolean
[XXX---array
Lxxx/yyy---object

架構大概是這樣

disassemble

assemble

Smali

Smali 例子

foo ()V
void foo()
foo (III)Z
foo (Z[I[ILjava/lang/String;J)Ljava/lang/String
boolean foo(int, int, int)
String foo (boolean, int[], int[], String, long)

Smali Register

Smali Function

private:invoke-direct
public, protected: invoke-virtual
static:invoke-static
parent:  invoke-super
example: invoke-xxx {參數},Class;->function(type)
# eg.
public static native String fenugreek(String str);
---------------------------------------------------
invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->fenugreek(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
return-object v1

Lab link

Tools in

Z:/NISRA/20200505_Rev-Android/tools.7z

Lab: Android setting

找出紅色部分的文字

他就是Flag

Android Studio with apk

helloworld.apk

試著改Smali code

把文字改成NISRA

改文字

Sublime 

Smali Syntax highlight

ctrl + shift + p

Open

Sublime

smali

阿改完之後如何打包?

打包完的apk會放在dist底下

apktool b <dir> -o <*.apk>
apktool b ./Helloworld -o ./NISRA.apk

我打包成功了,阿怎麼裝失敗

Apk Sign

keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore
jarsigner -verbose -keystore demo.keystore Helloworld.apk demo.keystore
keytool
-genkey 產生key
-alias 別名
-keystore 指定keystroe名字
-keyalg 密鑰算法
-validity 有效天數
jarsigner
- verbose 詳細輸出
- keystore 證書儲存路徑

Jadx-GUI

通常會從Jadx-GUI看code

再去找Smali相對的位址

不過通常都會被混淆過

PicoCTF2019

droids–系列題目

droids:0

觀察Logcat

利用Android Studio

droids:1

hint:password的位置(res)?

.line 12
    .local v0, "password":Ljava/lang/String;
    invoke-virtual {p0, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

apktool\one\res\values\strings.xml

droids:2

 public static String getFlag(String input, Context ctx) {
        String[] witches = {"weatherwax", "ogg", "garlick", "nitt", "aching", "dismass"};
        int second = 3 - 3;
        int third = (3 / 3) + second;
        int fourth = (third + third) - second;
        int fifth = 3 + fourth;
        int sixth = (fifth + second) - third;
        String str = ".";
        if (input.equals("".concat(witches[fifth]).concat(str).concat(witches[third]).concat(str).concat(witches[second]).concat(str).concat(witches[sixth]).concat(str).concat(witches[3]).concat(str).concat(witches[fourth]))) {
            return sesame(input);
        }
        return "NOPE";
    }

hint:Jadx-GUI

public class FlagstaffHill {
    public static native String sesame(String str);

    public static String getFlag(String input, Context ctx) {
                            //   0            1       2         3        4         5
        String[] witches = {"weatherwax", "ogg", "garlick", "nitt", "aching", "dismass"};
        int second = 0;
        int third = 1;
        int fourth = 2;
        int fifth = 5;
        int sixth = 4;
        String str = ".";
        if (input.equals("".concat(witches[fifth]).concat(str).concat(witches[third]).concat(str).concat(witches[second]).concat(str).concat(witches[sixth]).concat(str).concat(witches[3]).concat(str).concat(witches[fourth]))) {
            return sesame(input);
            //dismass.ogg.weatherwax.aching.nitt.garlick
        }
        return "NOPE";
    }
}

droids:3

public class FlagstaffHill {
    public static native String cilantro(String str);

    public static String nope(String input) {
        return "don't wanna";
    }

    public static String yep(String input) {
        return cilantro(input);
    }

    public static String getFlag(String input, Context ctx) {
        return nope(input);
    }
}
public class FlagstaffHill {
    public static native String cilantro(String str);

    public static String getFlag(String input, Context ctx) {
        return nope(input); - > yep(input);
    }
}
  • 把getFlag, return的function換成yep

  • Jadx不能編輯 => 請改Smali code

  • 拆包、封裝、運行

.method public static getFlag(Ljava/lang/String;Landroid/content/Context;)Ljava/lang/String;
    .locals 1
    .param p0, "input"    # Ljava/lang/String;
    .param p1, "ctx"    # Landroid/content/Context;

    .line 19
    invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->yep(Ljava/lang/String;)Ljava/lang/String;

    move-result-object v0

    .line 20
    .local v0, "flag":Ljava/lang/String;
    return-object v0
.end method

droids:4

public static String getFlag(String input, Context ctx) {
        String str = "aaa";
        StringBuilder ace = new StringBuilder(str);
        StringBuilder jack = new StringBuilder(str);
        StringBuilder queen = new StringBuilder(str);
        StringBuilder king = new StringBuilder(str);
        ace.setCharAt(0, (char) (ace.charAt(0) + 4));
        ace.setCharAt(1, (char) (ace.charAt(1) + 19));
        ace.setCharAt(2, (char) (ace.charAt(2) + 18));
        jack.setCharAt(0, (char) (jack.charAt(0) + 7));
        jack.setCharAt(1, (char) (jack.charAt(1) + 0));
        jack.setCharAt(2, (char) (jack.charAt(2) + 1));
        queen.setCharAt(0, (char) (queen.charAt(0) + 0));
        queen.setCharAt(1, (char) (queen.charAt(1) + 11));
        queen.setCharAt(2, (char) (queen.charAt(2) + 15));
        king.setCharAt(0, (char) (king.charAt(0) + 14));
        king.setCharAt(1, (char) (king.charAt(1) + 20));
        king.setCharAt(2, (char) (king.charAt(2) + 15));
        if (input.equals("".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString()))) {
            return "call it";
        }
        return "NOPE";
    }

Text

public static String getFlag(String input, Context ctx) {
        String str = "aaa";
        StringBuilder ace = new StringBuilder(str);
        StringBuilder jack = new StringBuilder(str);
        StringBuilder queen = new StringBuilder(str);
        StringBuilder king = new StringBuilder(str);
        ace.setCharAt(0, (char) (a + 4));
        ace.setCharAt(1, (char) (a + 19));
        ace.setCharAt(2, (char) (a + 18));
        jack.setCharAt(0, (char) (a + 7));
        jack.setCharAt(1, (char) (a + 0));
        jack.setCharAt(2, (char) (a + 1));
        queen.setCharAt(0, (char) (a + 0));
        queen.setCharAt(1, (char) (a + 11));
        queen.setCharAt(2, (char) (a + 15));
        king.setCharAt(0, (char) (a + 14));
        king.setCharAt(1, (char) (a + 20));
        king.setCharAt(2, (char) (a + 15));
        if (input.equals("".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString()))) {
            return "call it";
        }
        return "NOPE";
    }

整理一下code

print(chr(ord('a') + 0), end='')
print(chr(ord('a') + 11), end='')
print(chr(ord('a') + 15), end='')
print(chr(ord('a') + 7), end='')
print(chr(ord('a') + 0), end='')
print(chr(ord('a') + 1), end='')
print(chr(ord('a') + 4), end='')
print(chr(ord('a') + 19), end='')
print(chr(ord('a') + 18), end='')
print(chr(ord('a') + 14), end='')
print(chr(ord('a') + 20), end='')
print(chr(ord('a') + 15), end='')

# string
alphabetsoup

Text

public static String getFlag(String input, Context ctx) {
        String str = "aaa";
        StringBuilder ace = new StringBuilder(str);
        StringBuilder jack = new StringBuilder(str);
        StringBuilder queen = new StringBuilder(str);
        StringBuilder king = new StringBuilder(str);
        ace.setCharAt(0, (char) (a + 4));
        ace.setCharAt(1, (char) (a + 19));
        ace.setCharAt(2, (char) (a + 18));
        jack.setCharAt(0, (char) (a + 7));
        jack.setCharAt(1, (char) (a + 0));
        jack.setCharAt(2, (char) (a + 1));
        queen.setCharAt(0, (char) (a + 0));
        queen.setCharAt(1, (char) (a + 11));
        queen.setCharAt(2, (char) (a + 15));
        king.setCharAt(0, (char) (a + 14));
        king.setCharAt(1, (char) (a + 20));
        king.setCharAt(2, (char) (a + 15));
        if (input.equals("".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString()))) {
            return "call it";
        }
        return "NOPE";
    }

WTF?

Text

public static String getFlag(String input, Context ctx) {
        String str = "aaa";
        StringBuilder ace = new StringBuilder(str);
        StringBuilder jack = new StringBuilder(str);
        StringBuilder queen = new StringBuilder(str);
        StringBuilder king = new StringBuilder(str);
        ace.setCharAt(0, (char) (a + 4));
        ace.setCharAt(1, (char) (a + 19));
        ace.setCharAt(2, (char) (a + 18));
        jack.setCharAt(0, (char) (a + 7));
        jack.setCharAt(1, (char) (a + 0));
        jack.setCharAt(2, (char) (a + 1));
        queen.setCharAt(0, (char) (a + 0));
        queen.setCharAt(1, (char) (a + 11));
        queen.setCharAt(2, (char) (a + 15));
        king.setCharAt(0, (char) (a + 14));
        king.setCharAt(1, (char) (a + 20));
        king.setCharAt(2, (char) (a + 15));
        if (input.equals("".concat(queen.toString()).concat(jack.toString()).concat(ace.toString()).concat(king.toString()))) {
            return "call it";
        }
        return "NOPE";
    }

WTF?

參考前題,找到如何觸發function

 invoke-static {p0}, Lcom/hellocmu/picoctf/FlagstaffHill;->cardamom(Ljava/lang/String;)Ljava/lang/String;

 move-result-object p0

 return-object p0

smali中找到對應的code

return之前放上觸發function的code

老樣子,記得sign

jarsigner -verbose -keystore demo.keystore Helloworld.apk demo.keystore
jarsigner
- verbose 詳細輸出
- keystore 證書儲存路徑

如何防禦

  • 使用ProGuard程式碼混淆

  • 商用工具Arxan、Dexguard

Reference

Android 反編譯與防止反編譯

https://magiclen.org/android-decompiler/

Made with Slides.com