初赛 Reverse#
asm_re#
题目以文本形式给出一段arm汇编。按照跳转指令把控制流梳理一遍,发现loc_100003C64
-> loc_100003C7C
->loc_100003cc0
形成for循环。
在loc_100003C7C
看到EOR异或,直接猜是加密函数。
整体的加密算法是((W8*0x50+0x14)^0x4D)+0x1E
。
程序后面还有一段循环,代码和上述几乎完全一样。不确定是否有二次加密,用"flag"
四个字符带入验证一下,刚好和数据对应上。最后写脚本解出flag。
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#
key
和 iv
都在Native层,尝试使用IDA动调so库未果。
利用frida hook 获取 key
和 iv
:
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()
另外,尝试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());
}
}
结果是相同的。
进行 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 扩展模块,这发生在两个阶段:
包含 Python 扩展模块的
.pyx
源文件由 Cython 编译为.c
文件。
.c
文件由 C 编译器编译为.so
文件 ( 或 Windows 上的.pyd
)Python中可直接
import
Cython 一类通用工具,为了提高稳健性,其在转换时会对 Python 代码做额外处理(包括对引用计数的调整),从而干扰我们的逆向。当然,也正是因为它是通用工具,其整体框架和对类似 Python 字节码的处理也有一定规律。
在linux远程动调,先搭动调环境,这一步踩了不少坑:
- 务必以root身份运行linux_server,否则提示权限错误
- Process Option中要填Linux系统中so动态库的绝对地址
结合Cython源码和文档,以及网上查到的各种正向逆向教程,从IDA中找到了一些比较重要的内容,可以作为入手点:
一些规律性的命名
_pyx_mstate_global_static.__pyx_XX
全局静态变量,函数名_pyx_mdef_XX_XX
PyMethodDef : Python函数名到C函数的映射
逆向时重点分析的关键函数
_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
函数,因为保留了足够的符号信息,静态分析也能大概梳理出程序的整个加密逻辑。思路大概:
根据
GetBuiltinName
、GetModuleGlobalName
、PyObject_GetAttr
等API和符号名,猜测调用的函数及其参数。识别结构固定的异常、GC等和语言特性相关的代码,排除这些非关键代码。
如图,这一段是random.seed(0)
。观察发现IDA的命名还是很规律的:BuiltinName
为导入的模块名,Attr
是从模块中导入的方法。pyargnames
是参数列表,真正传入的参数从下标1开始。
之后调用whereistheflag1
,分析方法类似。可以看出调用了base64.b64encode
,最后和random.random
的得到的随机数xor。
分区赛 Pwn-AWDP#
待复现
先吐槽一波逆天平台