逆向分析常用技巧

1
2
按Unity的规则,dll被存放在附件游戏目录的Snake\Snake_Data\Plugins\Interface.dll,sample:snake,redhat-2019,https://mp.weixin.qq.com/s/MKaMMihP8gPSYKswAxjNqQ
dnSpy反编译Assembly-CSharp.dll

C/C++逆向

1
2
3
4
#define HIWORD(l)  ((WORD)((DWORD_PTR)(l))  >> 16))
#define HIDWORD(l) ((DWORD)(((DWORDLONG)(l) >> 32) & 0xFFFFFFFF))

strace ./hvm #通过strace查看使用了哪些系统调用

Edit->Plugins->Find crypt v2尝试查找特征加密算法

通过Ctrl+S找.rdata/.data段或者Shift+F12找特征字符串,根据DATA XREF定位到代码段相应位置,再通过X找代码调用关系,定位关键代码部分。

windows找入口,start->start_0->…

1
2
3
4
5
6
  v7 = (_QWORD *)sub_1400112D0();
  if ( *v7 && (unsigned __int8)sub_14001114F(v7) )
    j__register_thread_local_exe_atexit_callback(*v7);
  Code = sub_140014500(); #code 入口
  if ( !(unsigned __int8)sub_140011280() )
    j_exit(Code);

代码量大(超过1M),且逐字节递进对比,上pin跑

借助指令运行数量,依次确定每个字符

算法简单,上angr跑

1
2
3
4
5
6
virtualenvwrapper
列出虚拟环境列表:workon,也可以使用:lsvirtualenv
新建虚拟环境: mkvirtualenv angr[虚拟环境名称]
启动/切换虚拟环境:workon angr [虚拟环境名称]
删除虚拟环境: rmvirtualenv angr [虚拟环境名称]
离开虚拟环境: deactivate

在路径探索中,一般使用广度优先算法进行探究,当然我们也可以自由设置使用其它方法,例如深度优先等。具体可以使用angr.exploration_techniques

  • 变量作为标准输入,常见:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def stdin(): #variable passed by stdin
  passwd_len = int(sys.argv[1])
  
  flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(passwd_len)]
  flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')])
  
  state = proj.factory.full_init_state(
        args=[],
        add_options=angr.options.unicorn,
        stdin=flag,
    )
    
  # Constrain the first 28 bytes to be non-null and non-newline:
  for k in flag_chars:
    state.solver.add(k >= 0x20)
    state.solver.add(k < 0x7f)
  • 变量作为参数传递,不常见:
1
2
3
4
5
6
7
8
def argv(): #variable passed by argv
  argv1 = claripy.BVS("argv1",100*8)
  state = proj.factory.entry_state(args=["",argv1])
#  add_cond(state, argv1)
  simgr = proj.factory.simgr(state)
  simgr.explore(find=0x400A84, avoid=[0x400A90])
#  simgr.explore(find=lambda s:"correct!" in s.posix.dumps(1))
  print simgr.one_found.solver.eval(argv1, cast_to=str)
  • 程序中有反运行指令(sample:codegate_2017-angrybird),也可以先用Fill with Nops将程序进行patch。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#00000000004007BD                 call    sub_40072A is a dead function, start from next instruction that is 0x4007c2
START_ADDR = 0x4007c2
FIND_ADDR = 0x404fab  # This is right before the printf
state = proj.factory.entry_state(addr=START_ADDR)
#text:0000000000400762                 mov     rbp, rsp need to do that manually
state.regs.rbp = state.regs.rsp

#.text:0000000000400781                 mov     [rbp+var_70], offset off_606018
#.text:0000000000400789                 mov     [rbp+var_68], offset off_606020
#.text:0000000000400791                 mov     [rbp+var_60], offset off_606028
#.text:0000000000400799                 mov     [rbp+var_58], offset off_606038
# using the same values as the binary doesn't work for these variables, I think because they point to the GOT and the binary is using that to try to fingerprint that it's loaded in angr. Setting them to pointers to symbolic memory works fine.
state.mem[state.regs.rbp - 0x70].long = claripy.BVS('c0', 64) #0x1000
state.mem[state.regs.rbp - 0x68].long = claripy.BVS('c1', 64) #0x1008
state.mem[state.regs.rbp - 0x60].long = claripy.BVS('c2', 64) #0x1010
state.mem[state.regs.rbp - 0x58].long = claripy.BVS('c3', 64) #0x1018

对比结果是输入的重新排序

定义下标数组,根据重排序函数计算结果下标数组,再根据对比结果和结果下标数组得到原输入。

ReverseVersion(NJUPT-2018)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
src = [i for i in range(64)] #[0,1,2,3...]
dest = [-1] * 64
dword_601064 = 0
def trans(a1):
  global dword_601064
  if a1 <= 63:
    dest[dword_601064] = src[a1]
    dword_601064 += 1
    trans(2*a1 + 1)
    trans(2*(a1+1))
trans(0) #[0, 1, 3, 7, 15, 31, 63, 32, 16, 33, 34, 8, 17, 35, 36, 18, 37, 38, ...]
target = 'bcec8d7dcda25d91ed3e0b720cbb6cf202b09fedbc3e017774273ef5d5581794'
flag = [' '] * 64
for i in range (len(dest)):
  flag[dest[i]] = target[i]
print ''.join(flag) #bc2e3b4c2eb03258c5102bf9de77f57dddad9edb70c6c20febc01773e5d81947

搜索范围是输入的和或字符范围很小,采用遍历可见字符爆破

give_a_try(网鼎杯-2018-2nd)

函数难以直接转换为python实现,可以采用c++语言嵌套汇编的方式,将汇编语言直接嵌套到一个函数,通过栈传递参数,通过eax返回结果。

1
2
3
4
5
6
7
8
9
10
int check_one(int rand_value, int ch)
{
  //ebp = esp, ebp+4 = ret_addr
  __asm {
    mov     eax, dword ptr [ebp + 8]
    movzx   ecx, byte ptr [ebp + 12]
    ...
    mov     eax, edx
  } //pass return value to eax
}
HighwayHash(湖湘杯-2018)

函数调用关系复杂,难以直接转换为python实现或者直接嵌套到asm块。通过PEditor或者LordPE将exe的Entry Point改为0x00000000(避免LoadLibrary调用dll的主函数),Characteristics的DLL标志位设为1(64bit改为0x2022,32bit为0x2102),通过RVA(Relative Virtual Address)获取函数地址,再通过LoadLibrary调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<windows.h>
typedef __int64(__fastcall *f)(__int64 buff, unsigned __int64 len);
int main()
{
    HINSTANCE hdll;
    f func;
    long long i, result;
    hdll = LoadLibrary(TEXT("reverse.dll"));
    if (hdll == NULL)
    {
        printf("Load dll Error: %x\n", GetLastError());
        return 0;
    }
    printf("Dll base is %llx\n", hdll);
    func = ((f)((char*)hdll + 0x17A0)); //sub_1400017A0   proc near
    for (i = 0; i<50; i++)
    {
        result = func((long long )&i, 4);
        if (result == 0xD31580A28DD8E6C4)
            printf("Len is %d\n", i-9);
    }
}

控制跳转

.text:00401ADC                 mov     [ebp+var_14], offset loc_401360
.text:00401B4C                 lea     ecx, [ebp+var_14] ;跳转目标地址
.text:00401B4F                 push    ecx
.text:00401B50                 push    offset aS_0     ; "%s"
.text:00401B55                 mov     edx, [ebp+var_18]
.text:00401B58                 push    edx             ; LPSTR,返回地址
.text:00401B59                 call    ds:wsprintfA

将存放wsprintf函数的返回地址处的堆栈地址作为了第一个参数,第三个参数是要跳转的地址,通过wsprintf修改返回地址,达到控制跳转的目的(类似pwn的劫持程序流)。去到0x401360,按c修改为代码模式。

Hook函数

careful(dasctf-202306)

1
2
3
4
5
6
7
//原函数入口处修改为跳走E9 ****
  *v0 = 0xE9u; //E9后面的地址 = 目标地址 - 当前地址 - 5
  *(_DWORD *)(v0 + 1) = (char *)sub_4010C0 - (char *)gethostbyname - 5;
//新函数再修改回来,并调用原函数
  *(_DWORD *)lpAddress = dword_404374;
  v1[4] = byte_404378;
  return gethostbyname(&name);

init/fini

在start函数中指定.init.fini函数分别在main函数之前和执行结束之后运行。

IDA->Jump->Jump to segment,查看.preinit_array.init_array定义初始化时调用的函数的指针数组,.fini_array定义在销毁时调用的函数指针数组。根据initfini.c,在启动时调用了__libc_init_array(),它首先通过引用开始和结束标签来调用.preinit_array节中的所有函数指针,然后它调用.init部分中的_init()函数。最后,它调用.init_array部分中的所有函数指针。在main()完成之后,对libc_fini_array()的拆除调用会导致调用.fini_array中的所有函数,最后调用_fini()。(sample:hijack,安恒月赛2020-01)

异常处理

cm2(swpuctf-2018)
.text:00401372 unk_401372      db  0Fh

产生异常,在相应的异常处理函数下断点

1
2
3
4
void __usercall sub_401600(int a1@<ecx>, int a2@<ebx>)
{
  if ( *(_UNKNOWN **)(a2 + 12) == &unk_401372 )
  {
  • OD调试:ctrl+g,输入KiUserExceptionDispatcherF2下断点,shift+F9忽略异常运行
77C50154 > FC               CLD
77C50155   8B4C24 04        MOV ECX,DWORD PTR SS:[ESP+4]
77C50159   8B1C24           MOV EBX,DWORD PTR SS:[ESP]
77C5015C   51               PUSH ECX
77C5015D   53               PUSH EBX
77C5015E   E8 FE310500      CALL ntdll.77CA3361 #此处跟进去即为用户层的处理函数

77CA3361  -E9 9AE27588      JMP CM2.00401600
  • IDA调试:单步运行到异常处,调试界面右边寄存器窗口下的Modules窗口选择ntdll32.dll,找到KiUserExceptionDispatcher,下断点(异常触发前才下断点,提早下断点异常处理函数未设置好),继续单步运行,触发异常,选择Yes(pass to app),即可在call unk_77CA3361处找到异常处理函数
ntdll32:77C50154 cld
ntdll32:77C50155 mov     ecx, [esp+arg_0]
ntdll32:77C50159 mov     ebx, [esp+0]
ntdll32:77C5015C push    ecx
ntdll32:77C5015D push    ebx
ntdll32:77C5015E call    near ptr unk_77CA3361

ntdll32:77CA3361 loc_77CA3361:                           ; CODE XREF: ntdll32:ntdll32_KiUserExceptionDispatcher+A↑p
ntdll32:77CA3361 jmp     sub_401600
fuelvm

注册SEH(结构化异常处理)句柄

.text:004012B5                 push    offset seh_handler   // 新的异常处理函数
.text:004012BA                 push    large dword ptr fs:0 // 上一个节点
.text:004012C1                 mov     large fs:0, esp      // esp指向创建的结构体首地址
  • 使用int 3手动触发异常
  • 通过xor ecx,ecx; div ecx手动触发了一个除0异常
friendlyRE(纵横杯-2020)

异常的传递过程VEH(向量化异常处理) -> SEH -> UEH(UnhandledExceptionHandler,顶层异常处理器) -> VCH(VectoredContinueHandler,和 VEH 类似,但是只会在异常被处理的情况下最后调用)

  • 从执行顺序来看,VEH是在SEH之前执行的,并且不依赖某一线程,本进程中任何线程出现异常都可以被VEH处理
  • 当所有的VEH都不处理该异常,该异常就会让SEH处理
  • SEH是基于线程栈的异常处理机制,所以它只能处理自己线程的异常
1
2
AddVectoredExceptionHandler(0, Handler); //向VEH链注册一个异常处理函数
SetUnhandledExceptionFilter(sub_4110EB); //UEH,全局SEH过滤函数,当程序crash前会调用回调函数进行处理

当发生异常时,比如内存访问违例时,CPU硬件会发现此问题,并产生一个异常(你可以把它理解为中断)。然后CPU会把代码流程切换到异常处理服务例程。

异常处理服务例程会查看当前进程是否处于调试状态,如果是,则通知调试器发生了异常。

如果不是,则查看当前线程是否安装了的异常帧链(FS[0]),如果安装了SEH(try/catch),则调用SEH,并根据返回结果决定是全局展开或局部展开。如果异常链中所有的SEH都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常(二次异常)。

如果还没人处理,则调用操作系统的默认异常处理代码UnhandledExceptionHandler,不过操作系统允许Hook这个函数,就是通过SetUnhandledExceptionFilter函数来设置。大部分异常通过此种方法都能捕获,不过栈溢出、覆盖有可能捕获不到。

movfuscator混淆,几乎全是mov指令

1
2
3
#https://github.com/xtrm0/demovfuscator
/mnt/hgfs/tools/re/demovfuscator/demov -g cfg.dot -o patched superflat
cat cfg.dot | dot -Tpng > cfg.png

LLVM逆向

check(cmcc-200421)

store i8* %0, i8** %3     ! %3 = %0(input)
%8 = load i32, i32* %4    ! %8 = %4
br label %7               ! jmp to %7
%10 = icmp slt i32 %8, %9 ! if(%8 < %9)
br i1 %10, label %11, label %23 ! jmp to %11 else: jmp to %23
%18 = add nsw i32 %17, 5  ! %18 += %17 + 5
%27 = sub nsw i32 %26, 1  ! %27 = %26 - 1
%15 = getelementptr inbounds i8, i8* %12, i64 %14 ! %15 = get_memory(%12[%14])
%58 = zext i8 %57 to i32  ! %58 = zero_extend(%57) zero extend from i8 to i32
%61 = sext i32 %60 to i64 ! %61 = sign_extend(%60) sign extend from i32 to i64
LLVM混淆,主分发器来控制程序基本块,平坦化后的CFG庞大

flat(nisc-2019)

1
2
3
4
5
6
7
https://github.com/liumengdeqq/deflat
$ workon angr
$ cd /mnt/hgfs/tools/re/deflat/flat_control_flow/
$ python3 deflat.py /mnt/hgfs/matches/2019/190814nisc/re_flat/flat 0x400af0

$ cd /mnt/hgfs/tools/re/deflat/bogus_control_flow/
$ python3 debogus.py /mnt/hgfs/matches/2019/190814nisc/re_flat/flat 0x401040

Andriod逆向

图片资源一般在res目录下的drawable-*或者mipmap-*目录。

Frida

1
2
3
4
5
6
7
>adb push D:\ctf\tools\android\frida\frida-server-16.0.3-android-x86 /data/local/tmp
>adb shell #-s if more devices, adb devices to confirm
chmod 755 /data/local/tmp/frida-server-16.0.3-android-x86
mv frida-server-16.0.3-android-x86 fs
./fs -l 0.0.0.0:12345 #use port 12345 instead of the default port: 27042/27043
# forward port
>adb forward tcp:12345 tcp:12345

IDA远程调试Android中的so文件

android_server,和android_server64是真机用的,android_x86_server,和android_x64_server是模拟器用的,拷贝到手机或模拟器/data/local/tmp/目录下。

1
2
3
4
5
6
adb shell chmod 755 /data/local/tmp/android_server
adb shell
$su
#cd data/local/tmp
#./android_server
adb forward tcp:23946 tcp:23946

选择菜单Debugger -> Attach -> Remote ARM Linux/Android debugger

利用 ctrl+f 快速定位并选择相应的进程

模拟器Android7.0以上版本安装BP证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
openssl x509 -inform DER -in cacert.der -out cacert.pem
openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1
mv cacert.pem 9a5ba575.0

adb root 
adb disable-verity #如失败可忽略
adb reboot

adb root 
adb remount

adb push ./9a5ba575.0 /system/etc/security/cacerts/
adb  shell
chmod 644 /system/etc/security/cacerts/9a5ba575.0
reboot

脱壳(sample:crackme,安恒月赛2018-11)

此方法仅在Android-4.4.2的夜神模拟器成功脱壳,其他Android-5.1.1的模拟器均无法脱壳。

  1. 安装Xposed Installer,框架中点选安装,重启模拟器
  2. 安装Fdex2,在Xposed Installer模块中选定Fdex2,重启模拟器
  3. Fdex2中选定需要脱壳的app,点击app运行一次。脱壳后的dex生成在/data/data/{package}

adb常用命令

1
2
3
4
5
6
7
8
9
10
11
12
>adb devices
>adb kill-server #找不到devices时用于重启服务
>adb shell
>D:\software\Nox\bin\adb.exe pull /data/data/xyz.sysorem.crackme/xyz.sysorem.crackme1911252.dex
>D:\ctf1ctf81124\re_easy>D:\software\Nox\bin\adb.exe push liblibso.so /data/data/xyz.sysorem.crackme/lib/ #最后的/一定不能少,否则会覆盖lib这个软连接
>adb logcat *:W #过滤比W优先级低的log #-c清空
V-Verbose 明细(最低优先级)
D-Debug 调试
I-Info 信息
W-Warm 警告
E-Error 错误
F-Fatal 严重错误

修改lib目录的so文件

so文件针对不同CPU构建编译而成:

arm64-v8a:arm最新的64cpu构架,如骁龙810,820,835等都是基于此构架的,同时兼容A32,T32指令集armeabi-v7a:32位cpu构架,如骁龙800,801等,兼容armv5,armv6

arm构架都是向下兼容的,例如:如果CPU是armv8,没有对应arm64-v8a文件夹,则会执行armeabi-v7a中的so文件。因此一般选lib/armeabi-v7a目录下的liblibso.so,利用IDA修改汇编指令,一般将BEQ/BNE等条件跳转修改为B强制跳转,详见ARM CPU架构手册

修改后利用adb push覆盖/data/data/{package}/lib中原有的so文件。

so文件中的打印一般是_android_log_print,则利用adb logcat查看对应的log。

构造apk加载so文件。

查找so文件中对应native函数的顺序

Java端运行System.loadlibrary(libraryname),会通过dlsym(handle,"JNI_OnLoad")方式调用JNI_OnLoad,通过so文件中的JNI_OnLoad函数调用RegisterNatives方法设置native_sym_tbl表,当Java端需要运行native函数时,从该表中查找函数实现。(sample:easyapp,SWPUCTF-2019)

1
2
3
4
5
6
7
8
9
10
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
typedef struct {
    const char* name; //方法名
    const char* signature; //方法签名
    void*       fnPtr; //native函数指针,根据这个找真正的函数实现
} JNINativeMethod;
.data:00038008                                         ; "Encrypt"
.data:0003800C                 dd offset aLjavaLangStrin ; "()Ljava/lang/String;"
.data:00038010                 dd offset test
v3 = "check", v4 = "(Ljava/lang/String;Ljava/lang/String;)I",v5 = check

如果native_sym_tbl表为空,则采用默认的Java_CLASSNAME_METHODNAME()方式查找,如:Java_com_example_ndktest2_MainActivity_Encrypt()

修改smali文件

利用AndroidKiller打开smali文件,查看Java代码之后根据数值,转16进制后找到smali中对应的数值,进行修改,Android->编译,生成新的apk。

编译出现错误No resource found that matches the given name '@android:style/WindowTitleBackground'.,修改方法:

  1. 找到res/value-v23/styles.xml,把resources下的所有行注释掉
  2. 找到res/values/public.xml,把所有带Base.V23的两行注释掉

直接调用so文件中定义的函数

1
2
3
4
5
6
7
#include <dlfcn.h>
  void *handle = dlopen("./libnative-lib.so", RTLD_LAZY);
  typedef char* (*decode_t)(char *a1, int a2);
  decode_t decode = (decode_t)dlsym(handle, "decode");
  decode(str, 5);
  dlclose(handle);
//gcc main.c -ldl
1
gcc main.c -lnative-lib -L. -I. #-l在给出的名字前面加上lib,后面加上.so来确定库的名称,即libnative-lib.so

GoLang逆向

IDA7.5 with Python3,等分析完成后,File->Script file选择IDAGolangHelper的go_entry.py脚本,点击Try to determine go version based on moduledata,然后Rename functions,还原符号。

dlv调试

追踪某函数调用轨迹:dlv trace test.go Afunc

调试协程goroutines显示所有协程,goroutine 5切换到5号协程,bt查看当前协程的栈状态,frame 3 locals切换到3号栈上并打印栈上的变量

.NET逆向

Unity逆向,使用dnSpy打开Project_Data\Managed\Assembly-CSharp.dll

使用NetReactorSlayer脱壳

Python逆向

python字节码

0  LOAD_FAST             0  's' #加载s
3  STORE_FAST            2  'a' #a=s,如果没有看到STORE_FAST,那么该变量就是函数形参,而其他局部变量在使用之前肯定会使用STORE_FAST进行初始化。
BUILD_SLICE用于[x:y:z]这种类型的slice,结合BINARY_SUBSCR读取slice的值,结合STORE_SUBSCR用于修改slice的值
SLICE+n用于[a:b]类型的访问,STORE_SLICE+n用于[a:b]类型的修改,其中n表示如下:
SLICE+0() TOS = TOS[:].
SLICE+1() TOS = TOS1[TOS:].
SLICE+2() TOS = TOS1[:TOS].
SLICE+3() TOS = TOS2[TOS1:TOS].
i += 1 //使用INPLACE_xxx
i = i + 1 //使用BINARY_xxxx

PyInstaller打包exe还原

判断:包含PyImport字符串(sample:来玩蛇吧,安恒月赛2019-01)

1
2
3
4
D:\ctf1ctf90126\re_she>strings AnhengRe.exe | grep PyImport
PyImport_AddModule
PyImport_ExecCodeModule
PyImport_ImportModule

对于此种exe,使用pyinstxtractor-ng提取,无需对应版本,也无需自己补结构头。

1
>python3 D:\ctf\tools\re\pyinstxtractor-ng\pyinstxtractor-ng.py AnhengRe.exe

pyinstxtractorpyinstallerextractor提取,需要使用和打包时一致的python版本!根据struct.pyc修改AnhengRe.pyc。

在抽取出来的文件中找到同名无后缀文件AnhengRe,加上对应版本12字节的pyc头,增加后缀pyc,有个技巧就是,查看struct文件的magic,直接复制过去。

1
2
33 0d 0d 0a 00 00 00 00 00 00 00 00  
42 0d 0d 0a 20 20 20 20 54 c6 1d 61 db 20 20 20  #python3.7

反编译pyc/pyo

  • https://tool.lu/pyc(支持所有Python版本)
  • python-decompile3(Uncompyle6 is awesome, but it has has a fundamental problem in the way it handles control flow. ONLY work on python3.7/3.8…)
  • uncompyle6uncompyle2仅能反编译python2*)
1
2
3
4
5
>decompyle3 AnhengRe.exe_extracted\AnhengRe.pyc > a.py
>uncompyle6 -o . AnhengRe.exe_extracted\AnhengRe.pyc
编译pyc命令:
python3 -m compileall -b .
python2 -m snake.py
1
2
/mnt/hgfs/tools/re/pycdc/pycdc [PATH TO PYC FILE] #PYC Decompiler
/mnt/hgfs/tools/re/pycdc/pycdas [PATH TO PYC FILE] #PYC Disassembler

如果opcode前面有混淆代码(类似于c的花指令),则需要先将去掉,修改co_code长度(python2在0x1a位置)即可正常反编译。

1
2
3
4
5
6
7
8
9
In [10]: dis.dis(code.co_code)
          0 JUMP_ABSOLUTE       3
    >>    3 JUMP_ABSOLUTE       9
          6 LOAD_CONST         15 (15)
    >>    9 JUMP_ABSOLUTE      14 #经过多次跳转才到程序正确入口
    
0x64 操作为LOAD_CONST,用法举例:LOAD_CONST 1            HEX: 640100
0x71 操作为JUMP_ABSOLUTE,用法举例:JUMP_ABSOLUTE 14     HEX: 710e00
0x65 操作为LOAD_NAME,用法举例:LOAD_NAME 1              HEX: 650100

CST(Concrete Syntax Tree)

语法树Module字符串转代码,sample:CSTPC,yangcheng-2020

1
2
3
from libcst import *
code = Module(...)
print(code.code)

Lua逆向

Ctrl+S找.rdata/.data段,Alt+T(Case sensitive)找Lua,Ctrl+T一直找到类似”\x1BLua[P/Q/R/S]”:

.data:00427018 aLuaq           db 1Bh,'LuaQ',0

一直选中到下一个变量(或者右键选Array),Shift+E,保存为game.luac,再借助unluac反编译luac代码

1
java -jar D:\ctf\tools\re\unluac\unluac_2015_06_13.jar --rawstring game.luac > game.lua

Arduino Micro逆向

NES/Famicom逆向

模拟器/调试器:Mesen。(sample:adventures,Inctf-2021)

Q/W配合方向键启动游戏,工具->作弊,选择血量,运行游戏,每次血量变化时,查找作弊所在地址。

确定地址后,选择Debug->调试器,右键下方中间Breakpoints窗口->Add,Break on选write,继续运行游戏,血量再次变化时,断点触发。

找到需要跳过的逻辑,右键选择要跳到的目标地址,Set Next Statement,直接跳转。

1
BNE $addr #小于则跳转

QuickJS逆向

sample:funny_js,changchengcup-2021

1
2
3
4
5
6
7
8
9
10
11
12
git clone https://gitee.com/haloxxg/QuickJS -b 20200119 --depth 1
vi quickjs.c
85: #define DUMP_BYTECODE  (1)
99: #define DUMP_READ_OBJECT
33899: #if DUMP_BYTECODE
    js_dump_function_bytecode(ctx, b);
#endif
make
./qjsc -e -o test.c test.js
修改test.c中的size和bytecode
~/Downloads/QuickJS$ cc test.c libquickjs.lto.a -o test -lm -ldl
./test