1 |
|
C/C++逆向
1 |
|
Edit->Plugins->Find crypt v2尝试查找特征加密算法
通过Ctrl+S找.rdata/.data段或者Shift+F12找特征字符串,根据DATA XREF定位到代码段相应位置,再通过X找代码调用关系,定位关键代码部分。
windows找入口,start->start_0->…
1 |
|
代码量大(超过1M),且逐字节递进对比,上pin跑
借助指令运行数量,依次确定每个字符
算法简单,上angr跑
1 |
|
在路径探索中,一般使用广度优先算法进行探究,当然我们也可以自由设置使用其它方法,例如深度优先等。具体可以使用angr.exploration_techniques
- 变量作为标准输入,常见:
1 |
|
- 变量作为参数传递,不常见:
1 |
|
- 程序中有反运行指令(sample:codegate_2017-angrybird),也可以先用Fill with Nops将程序进行patch。
1 |
|
对比结果是输入的重新排序
定义下标数组,根据重排序函数计算结果下标数组,再根据对比结果和结果下标数组得到原输入。
ReverseVersion(NJUPT-2018)
1 |
|
搜索范围是输入的和或字符范围很小,采用遍历可见字符爆破
give_a_try(网鼎杯-2018-2nd)
函数难以直接转换为python实现,可以采用c++语言嵌套汇编的方式,将汇编语言直接嵌套到一个函数,通过栈传递参数,通过eax返回结果。
1 |
|
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 |
|
控制跳转
.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 |
|
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 |
|
- OD调试:
ctrl+g
,输入KiUserExceptionDispatcher
,F2
下断点,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 |
|
当发生异常时,比如内存访问违例时,CPU硬件会发现此问题,并产生一个异常(你可以把它理解为中断)。然后CPU会把代码流程切换到异常处理服务例程。
异常处理服务例程会查看当前进程是否处于调试状态,如果是,则通知调试器发生了异常。
如果不是,则查看当前线程是否安装了的异常帧链(FS[0]),如果安装了SEH(try/catch),则调用SEH,并根据返回结果决定是全局展开或局部展开。如果异常链中所有的SEH都没有处理此异常,而且此进程还处于调试状态,则操作系统会再次通知调试器发生异常(二次异常)。
如果还没人处理,则调用操作系统的默认异常处理代码UnhandledExceptionHandler
,不过操作系统允许Hook这个函数,就是通过SetUnhandledExceptionFilter函数来设置。大部分异常通过此种方法都能捕获,不过栈溢出、覆盖有可能捕获不到。
movfuscator混淆,几乎全是mov指令
1 |
|
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 |
|
Andriod逆向
图片资源一般在res目录下的drawable-*或者mipmap-*目录。
Frida
1 |
|
IDA远程调试Android中的so文件
android_server,和android_server64是真机用的,android_x86_server,和android_x64_server是模拟器用的,拷贝到手机或模拟器/data/local/tmp/
目录下。
1 |
|
选择菜单Debugger -> Attach -> Remote ARM Linux/Android debugger
利用 ctrl+f 快速定位并选择相应的进程
模拟器Android7.0以上版本安装BP证书
1 |
|
脱壳(sample:crackme,安恒月赛2018-11)
此方法仅在Android-4.4.2的夜神模拟器成功脱壳,其他Android-5.1.1的模拟器均无法脱壳。
- 安装Xposed Installer,框架中点选安装,重启模拟器
- 安装Fdex2,在Xposed Installer模块中选定Fdex2,重启模拟器
- Fdex2中选定需要脱壳的app,点击app运行一次。脱壳后的dex生成在/data/data/{package}
adb常用命令
1 |
|
修改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 |
|
如果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'.
,修改方法:
- 找到
res/value-v23/styles.xml
,把resources
下的所有行注释掉 - 找到
res/values/public.xml
,把所有带Base.V23
的两行注释掉
直接调用so文件中定义的函数
1 |
|
1 |
|
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 |
|
对于此种exe,使用pyinstxtractor-ng提取,无需对应版本,也无需自己补结构头。
1 |
|
pyinstxtractor、pyinstallerextractor提取,需要使用和打包时一致的python版本!根据struct.pyc修改AnhengRe.pyc。
在抽取出来的文件中找到同名无后缀文件AnhengRe,加上对应版本12字节的pyc头,增加后缀pyc,有个技巧就是,查看struct文件的magic,直接复制过去。
1 |
|
反编译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…)
- uncompyle6(uncompyle2仅能反编译python2*)
1 |
|
1 |
|
如果opcode前面有混淆代码(类似于c的花指令),则需要先将去掉,修改co_code
长度(python2在0x1a位置)即可正常反编译。
1 |
|
CST(Concrete Syntax Tree)
语法树Module字符串转代码,sample:CSTPC,yangcheng-2020
1 |
|
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 |
|
Arduino Micro逆向
NES/Famicom逆向
模拟器/调试器:Mesen。(sample:adventures,Inctf-2021)
Q/W配合方向键启动游戏,工具->作弊,选择血量,运行游戏,每次血量变化时,查找作弊所在地址。
确定地址后,选择Debug->调试器,右键下方中间Breakpoints窗口->Add,Break on选write,继续运行游戏,血量再次变化时,断点触发。
找到需要跳过的逻辑,右键选择要跳到的目标地址,Set Next Statement
,直接跳转。
1 |
|
QuickJS逆向
sample:funny_js,changchengcup-2021
1 |
|