DDCTF2020官方Write Up——Reverse篇

  • A+
所属分类:逆向工程

目录

 0x01 :Android Reverse 1

 0x02:Android Reverse 2

 0x03:Android Reverse 3

  出题人现身说法,带来官方解读

01

Android Reverse 1

团队昵称:Nanase

一血用时:5小时05分


出题

DDCTF2020官方Write Up——Reverse篇

糜波

滴滴出行安全工程师

出题人视角

输入字符串经过AES和XXTEA是加密,然后进行MD5,由于考虑到爆破时间,我们后面给了对称加密的结果。

-----选手Write Up-----

Java层的逻辑是输入flag然后check,check的代码都在native library中。ida打开native library,检查的函数为Java_com_ddctf_MainActivity_checkCode。

DDCTF2020官方Write Up——Reverse篇

主要的逻辑是AES + 一个类似TEA的块加密(12轮,key是2234)+ md5,最后结果要等于一个给定值。


AES可以直接解密,块加密把流程倒过来就是解密,所以现在问题变成了已知md5的结果求原值。后来发了个提示爆破,但是那个要爆破的位数不可能在比赛期间爆出来(每次尝试需要加密+md5)。再后来提示md5的结果就是flag,因此输入md5的结果即可。


02

Android Reverse 2

团队昵称:F**kDecade 

一血用时:8小时02分


出题人

DDCTF2020官方Write Up——Reverse篇

糜波

滴滴出行安全分析专家

出题人视角

APP进行了java层加固和native混淆。此外,除了XXTEA的密钥有所变化外,算法和题目1基本一致。

-----选手Write Up-----

apk整体加了壳。由于apk本身的逻辑非常简单,所以一开始没有脱壳,直接分析libnative-lib.so

libnative-lib.so经过了ollvm混淆,开启了平坦化与字符串混淆。在init_array中对字符串常量解密。


JNIOnLoad中对sub1794C函数进行动态注册。虽然字符串经过了加密,但程序没有反调试,我们可以通过调试查看注册的方法com.ddctf.MainActivity.check。

DDCTF2020官方Write Up——Reverse篇

重点的校验过程在check函数中

ida7.5有个插件可以去平坦化,不过这个题流程不是很复杂,对ollvm熟悉的话也可以硬调调出来。


输入的flag用0补到32字节,然后进入sub_11C5C加密,密钥为1234567980123456。我们在调试中可以在sub_11C5C发现AES的S盒,位于byte_3F000。测试一下加密结果可以肯定这是AES。


AES之后进入sub_12E88进行加密。稍微浏览一下发现了v51 = 52 / -a2 + 6;,这是XXTEA的论述计算方法。传入的key为[2, 2, 3, 4]。不过测试一下发现结果并不一样。再看两眼题目中的XXTEA函数,发现在加密前将key的每个值乘以了10:

else if ( v3 == 93063157 )
{
  *v31 = 10 * v32; // key[i]
  v23 = v65 + 1// i
  v3 = 593851720;
}

XXTEA之后是MD5,很容易能发现MD5的初始向量值。


最后对MD5结果进行比对。由于MD5不可逆(除非爆破,但是计算量太大了),我们暂时无法解出flag。后面出题人给出了md5前的结果,于是第一时间带入脚本求解,得到flag

from Crypto.Cipher import AES
import XXTEA # stantard cipher script
import struct
from hashlib import md5
aes = AES.new(b"1234567890123456", mode=AES.MODE_ECB)
key = struct.pack("<IIII", 20, 20, 30, 40)
tmp = bytes([0x1d,0xb3,0x52,0xe2,0x0a,0xcd,0xc5,0x69,0xd0,0xe5,0x7c,0xdf,0x61,0xae,0xf0,0x8a,0xc1,0x51,0x54,0xde,0x3c,0xf7,0x58,0xd1,0x60,0xd6,0x57,0xde,0xae,0xeb,0x9a,0xaf])
xxtea = XXTEA.new(key)
tmp = xxtea.decrypt(tmp)
aes = AES.new(b"1234567890123456", mode=AES.MODE_ECB)
tmp = aes.decrypt(tmp)
print(tmp)
# DDCTF{FiNal CuP$}


03

Android Reverse 3

团队昵称:Darkkkkkk

一血用时:22小时40分


出题人

DDCTF2020官方Write Up——Reverse篇

黄灿

滴滴出行安全工程师

出题人视角

Android Reverse3难度比起前两题循序渐进略有提升。有了前两题的预热和引导,选手应该可以较轻松的进行整体APK的加固脱壳。本题的初衷在于让同学们更好的体会真实业务场景中对抗的强度并进行核心算法的还原,因此进一步增强了算法的保护。本题在加壳的基础上,进行了so层的字符串加密,函数动态注册,还使用了更强的策略对so进行ollvm混淆,并且进行了轻量级的反调试处理。算法本身仍是AES256+SHA1的组合,但是在AES密钥、iv的保护和构造上,还通过XOR以及服务端动态获取部分密钥进行了一些小幅度的干扰,但是可以比较轻松的通过动态调试进行绕过。在分析了解整体的算法构成以及密钥iv等数据后暴力破解即可得到答案。

-----选手Write Up-----

初步静态分析,发现:

init_array 中字符串解密

JNI_OnLoad 中pthread_create起了子线程检查"/proc/{pid}/status"中的TracerPid

JNI_OnLoad 中动态注册crypt函数

与re2相比,也有字符串加密,更强的控制流平坦化,还加入了反调试。

使用patch过kernel(使TracerPid永远为0)的安卓手机进行调试,字符串dump出来发现其访问http://117.51.142.44/getPW 拿key。通过findcrypt发现AES SBOX。
试了几个静态去平坦化的工具,发现效果一般,直接在check(0x4FD4)函数中的数个可疑处下断点(getPW函数、AES setup、AES encrypt、sha1),动态调试

DDCTF2020官方Write Up——Reverse篇


发现flag的检查算法只是从网上读了一个key,明文padding后AES CBC 256加密,SHA1后与常量比较

写出python正向脚本:

AES_key = [0x33, 0x62, 0x31, 0x63, 0x39, 0x64, 0x37, 0x66, 0x2B, 0x73,
  0xAE, 0xF0, 0x85, 0x7D, 0x77, 0x81, 0x1F, 0x35, 0x2C, 0x07,
  0x3B, 0x61, 0x08, 0xD7, 0x2D, 0x98, 0x10, 0xA3, 0x09, 0x14,
  0xDF, 0xF4
]
pw = b"3b1c9d7f+sxAExF0x85}wx81x1F5,x07;ax08xD7-x98x10xA3x09x14xDFxF4"
from Crypto.Cipher import AESimport binascii

iv = b"x00x01x02x03x04x05x06x07x08x09x0Ax0Bx0Cx0Dx0Ex0F"

aes = AES.new(pw, AES.MODE_CBC, iv)

plaintext = b"DDCTF{1234567890123456789012}"+b"x03"*3# plaintext = b"DDCTF{kyaaaaadI}"+b"x10"*0x10

enc = aes.encrypt(plaintext)
print(enc)
import hashlib

sha256 = hashlib.sha1()
sha256.update(enc)
print(sha256.hexdigest())

尝试使用python多进程爆破,速度太慢,放弃。
使用c编写多进程爆破,脚本如下:

//                                // Created by G6 on 2020-09-05.//
// gcc 1.cpp -O3 -lcrypto
#include<sys/types.h>#include<sys/wait.h>#include <unistd.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <openssl/sha.h>#include <openssl/aes.h>
#define SHA_DIGEST_LENGTH 20
// unsigned char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";unsigned char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>[email protected][\]^_`{|}~ ";unsigned char target[] = "xC8xCDx3Fx8FxB6x23x9Ex39x6Dx25x38xD4x50x88x74xF9x07x1FxC6x16";unsigned char key[] = {
        0x330x620x310x630x390x640x370x660x2B0x73,
        0xAE0xF00x850x7D0x770x810x1F0x350x2C0x07,
        0x3B0x610x080xD70x2D0x980x100xA30x090x14,
        0xDF0xF4
};unsigned char iv_bak[] = {
        01,2,3,4,5,6,7,8,9,10,11,12,13,14,15
};

int fff = 1;
int check(unsigned char *plaintext){
    unsigned char iv[16];
    memcpy(iv, iv_bak, 16);

    AES_KEY aes;
    unsigned char enc[100];

    AES_set_encrypt_key(key,256,&aes);
    AES_cbc_encrypt(plaintext,enc, 32,&aes,iv,AES_ENCRYPT);


    unsigned char digest[SHA_DIGEST_LENGTH];

    SHA1((unsigned char*)&enc, 32, (unsigned char*)&digest);

    // unsigned char target[] = "x52x4fxcax7ex5fx31x28xf1x86xa1x51xd9x42x12x3ax6fx23xacx1ax89";
    if (memcmp(target, digest, 20) == 0) {
        printf("flag: %sn", plaintext);
        printf("flag: %sn", plaintext);
        printf("flag: %sn", plaintext);
        printf("flag: %sn", plaintext);
        printf("flag: %sn", plaintext);
        printf("flag: %sn", plaintext);
    } 

    if (fff==1) {
            char mdString[SHA_DIGEST_LENGTH*4+1];

            for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
                    sprintf(&mdString[i*4], "\x%02x", (unsigned int)digest[i]);
            printf("plaintext: %s n SHA1 digest: %sn", plaintext, mdString);
            fff = 0;
            // for (int i=0; i < 32; i++){printf("%02x ", enc[i]);}
            // printf("n");
    }
    return 0;

}
unsigned char plaintext__[] = "DDCTF{s++++++dI}x10x10x10x10x10x10x10x10x10x10x10x10x10x10x10x10";
void worker() {
    //for(int a1=0; a1<sizeof(charset); a1++) {
        for(int a2=0; a2<sizeof(charset); a2++) {
        fff = 1;
            for(int a3=0; a3<sizeof(charset); a3++) {
                for(int a4=0; a4<sizeof(charset); a4++) {
                    for(int a5=0; a5<sizeof(charset); a5++) {
                        for(int a6=0; a6<sizeof(charset); a6++) {
               //             plaintext__[7] = charset[a1];
                            plaintext__[8] = charset[a2];
                            plaintext__[9] = charset[a3];
                            plaintext__[10] = charset[a4];
                            plaintext__[11] = charset[a5];
                            plaintext__[12] = charset[a6];
                            check(plaintext__);
                        }
                    }
                }
            }
        }
    //}
}
pid_t child_up(int index) {
    pid_t pid;
    pid = fork();
    if (pid == 0) {
        plaintext__[7] = charset[index];
        worker();
    } else {
        return pid;
    }
}
int main() {
    pid_t p = 0;
    for (int i=0;i<sizeof(charset); i++) {
        p = child_up(i);
    }
    waitpid(p, NULL0);

}

64核机器上跑,运气比较好,没多久就跑出来了

DDCTF2020官方Write Up——Reverse篇

DDCTF{[email protected]@D1dI}

-----PWN方向Write Up请查看下一篇-----

本文始发于微信公众号(滴滴安全应急响应中心):DDCTF2020官方Write Up——Reverse篇

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: