跳过正文
  1. Writeups/

第十七届CISCN初赛&华东北分区赛

·509 字·3 分钟·
CTF AWDP Reverse Pwn
目录

初赛 Reverse
#

asm_re
#

题目以文本形式给出一段arm汇编。按照跳转指令把控制流梳理一遍,发现loc_100003C64 -> loc_100003C7C ->loc_100003cc0形成for循环。

loc_100003C7C看到EOR异或,直接猜是加密函数。

整体的加密算法是((W8*0x50+0x14)^0x4D)+0x1E

asm_enc

程序后面还有一段循环,代码和上述几乎完全一样。不确定是否有二次加密,用"flag"四个字符带入验证一下,刚好和数据对应上。最后写脚本解出flag。

asm_check

def decrypt(data):
    return (((data-0x1E)^0x4D)-0x14)//0x50

Data = [0x1fd7,0x21b7,0x1e47,0x2027,0x26e7,0x10d7,0x1127,0x2007,0x11c7,0x1e47,
      0x1017,0x1017,0x11f7,0x2007,0x1037,0x1107,0x1f17,0x10d7,0x1017,0x1017,
      0x1f67,0x1017,0x11c7,0x11c7,0x1017,0x1fd7,0x1f17,0x1107,0x0f47,0x1127,
      0x1037,0x1e47,0x1037,0x1fd7,0x1107,0x1fd7,0x1107,0x2787]
flag = []

for i in Data:
    flag.append(chr(decrypt(i)))
print(''.join(flag))

** gdb_debug
#

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
unsigned char data[]={0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 
	0x83, 0x73, 0xAC, 0xF1, 0x06, 0xBE, 0xAD, 0x88, 0x04, 0xD7, 0x12, 
	0xFE, 0xB5, 0xE2, 0x61, 0xB7, 0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2, 
	0x9D, 0x4D, 0xBC, 0x81, 0x8C, 0xE9, 0x88, 0x78};
unsigned char s[]="congratulationstoyoucongratulationstoy";
unsigned char ptr[]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,16, 17, 
    18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37};

int main()
{
	unsigned char R1[38],R2[38],R3[38],flag[38];
	int T = time(0LL);
	srand(T & 0xF0000000);
	for(int i=0;i<38;i++)
	{
		R1[i]=rand();
	}
	for(int k=37;k;k--)
	{
		R2[k]=rand()%(k+1);
	}
	for(int i=0;i<38;i++)
	{
		R3[i]=rand();
	}
	
	for(int k=37;k;k--)
	{
		unsigned char v = ptr[k];
		ptr[k]=ptr[R2[k]];
		ptr[R2[k]] = v;
	}
	for(int i=0;i<38;i++)
	{
		flag[ptr[i]] = data[i]^s[i]^R3[i];
	}
	for(int i=0;i<38;i++)
	{
		flag[i] = flag[i]^R1[i];
	}
	printf("%s",flag);
	return 0;
}

app-debug
#

keyiv都在Native层,尝试使用IDA动调so库未果。

利用frida hook 获取 keyiv

import frida
import sys

jscode = """
Java.perform(function(){
  	var jni = Java.use("com.example.re11113.jni");
    var reclass = Java.use("com.example.re11113.MainActivity");
    reclass.legal.overload("java.lang.String").implementation = function(string){
        console.log("key: " + jni.getkey());
        console.log("iv: " + jni.getiv());
        return this.legal(string);
    }
})
"""

def on_message(message, data):
    if message['type'] == 'send':
        print("[+] {0}".format(message['payload']))
    else:
        print(str(message))

process = frida.get_usb_device(-1).attach("Re11113")
print('[*] Attached')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Running')
script.load()
sys.stdin.read()

frida_hook

另外,尝试unidbg模拟执行来主动调用

package com.reverse.solve;

import com.github.unidbg.Module;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;

public class MainActivity {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final Memory memory;
    private final DalvikModule dm;

    private MainActivity(String Processname, String apkPath, String soPath) throws IOException {
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                //.addBackendFactory(new DynarmicFactory(true))
                .setProcessName(Processname)
                .build();

        memory = emulator.getMemory();
        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(apkPath));
        vm.setVerbose(false);
        //vm.setVerbose(true);

        dm = vm.loadLibrary(new File(soPath), true);
        module = dm.getModule();

        dm.callJNI_OnLoad(emulator);
    }

    private String call_getkey(){
        DvmClass dvmClass = vm.resolveClass("com/example/re11113/jni");
        DvmObject<?> result = dvmClass.callStaticJniMethodObject(emulator, "getkey()Ljava/lang/String;");
        return result.getValue().toString();
    }

    private String call_getiv(){
        DvmClass dvmClass = vm.resolveClass("com/example/re11113/jni");
        DvmObject<?> result = dvmClass.callStaticJniMethodObject(emulator, "getiv()Ljava/lang/String;");
        return result.getValue().toString();
    }

    public static void main(String[]args) throws IOException{
        String Processname="com.example.re11113";
        String apkPath="unidbg-android/src/test/java/com/reverse/solve/app-release.apk";
        String soPath = "unidbg-android/src/test/java/com/reverse/solve/libSecret_entrance.so";

        MainActivity mainactivity = new MainActivity(Processname,apkPath,soPath);
        System.out.println("key: " + mainactivity.call_getkey());
        System.out.println("iv: " + mainactivity.call_getiv());
    }
}

结果是相同的。

unidbg

进行 DES/CBC解密即可,注意得到的结果还要unpad再套上flag

import base64
from Crypto.Cipher import DES

iv = b"Wf3DLups"
key = b"A8UdWaeq"
b64data = "JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw=="

data = base64.b64decode(b64data)
chiper = DES.new(key,DES.MODE_CBC,iv)
flag = chiper.decrypt(data)
print(flag)
#'188cba3a5c0fbb2250b5a2e590c391ce\x08\x08\x08\x08\x08\x08\x08\x08'

whereThel1b
#

python源代码调用了so动态库中的函数。题目的so库是基于Cython编译而来,难度较大。先简单找点资料了解下:

Cython 代码必须编译。从 Python 源代码转换为优化的 C / C ++代码并编译为 Python 扩展模块,这发生在两个阶段:

  1. 包含 Python 扩展模块的 .pyx 源文件由 Cython 编译为 .c 文件。

  2. .c 文件由 C 编译器编译为 .so 文件 ( 或 Windows 上的 .pyd )

    Python中可直接 import

Cython 一类通用工具,为了提高稳健性,其在转换时会对 Python 代码做额外处理(包括对引用计数的调整),从而干扰我们的逆向。当然,也正是因为它是通用工具,其整体框架和对类似 Python 字节码的处理也有一定规律。

在linux远程动调,先搭动调环境,这一步踩了不少坑:

  • 务必以root身份运行linux_server,否则提示权限错误
  • Process Option中要填Linux系统中so动态库的绝对地址

结合Cython源码和文档,以及网上查到的各种正向逆向教程,从IDA中找到了一些比较重要的内容,可以作为入手点:

  1. 一些规律性的命名

    • _pyx_mstate_global_static.__pyx_XX 全局静态变量,函数名

    • _pyx_mdef_XX_XX PyMethodDef : Python函数名到C函数的映射

  2. 逆向时重点分析的关键函数

    • _Pyx_CreateStringTabAndInitStrings() 字符串信息

    • __Pyx_pymod_exec_XX() so动态库加载时的初始化

    • __pyx_pw_XX_XX() python对应函数的C实现

分析本题,先从给出的源文件开始,有两行涉及调用so函数。

whereThel1b.whereistheflag(flag)
ret = whereThel1b.trytry(flag)

Python函数没有传指针或者传引用的用法,所以第一个函数whereistheflag 没什么用,结合实际动调应该是输出了whereistheflag 字符串。直接分析第二个函数trytry

在so文件中找到对应的_pyx_pw_11whereThel1b_1trytry函数,因为保留了足够的符号信息,静态分析也能大概梳理出程序的整个加密逻辑。思路大概:

  • 根据GetBuiltinNameGetModuleGlobalNamePyObject_GetAttr 等API和符号名,猜测调用的函数及其参数。

  • 识别结构固定的异常、GC等和语言特性相关的代码,排除这些非关键代码。

so

如图,这一段是random.seed(0)。观察发现IDA的命名还是很规律的:BuiltinName 为导入的模块名,Attr 是从模块中导入的方法。pyargnames 是参数列表,真正传入的参数从下标1开始。

之后调用whereistheflag1,分析方法类似。可以看出调用了base64.b64encode,最后和random.random的得到的随机数xor。

分区赛 Pwn-AWDP
#

待复现

先吐槽一波逆天平台

相关文章

LitCTF2024 Reverse
·163 字·1 分钟
CTF Reverse
reverse writeup of LitCTF(NSS) 2024
HGAME2024 Revserse
·1327 字·7 分钟
CTF Reverse
reverse writeup of Hgame2024
0xGame2023 Reverse
·591 字·3 分钟
CTF Reverse
reverse writeup of 0xgame2023