CTF逆向题目两大主题:暴力破解、算法分析破解(组图)

1、背景

在CTF比赛中,除了分析程序的工作原理外,CTF逆问题还需要根据分析结果进一步得到FLAG。逆向是解题竞赛系统中的一种独立题型,也是PWN题型的必备技能。常与攻防系统中的PWN题相结合。CTF逆向主要涉及逆向分析和破解技巧,也需要有扎实的反汇编、反编译、加解密基础。

CTF中的反向题一般都是常见的考点

1、常用算法和数据结构。

2、各种排序算法、树、图和其他数据结构。

4、代码混淆、代码虚拟化、代码流修改、反调试等。

5、软件加密外壳是软件保护技术的集中应用。

CTF逆向问题两大主题:蛮力破解、算法分析破解

1、蛮力破解:通过修改汇编代码跳过程序内部验证部分,从而改变程序的正常逻辑,最终满足问题要求获得flag。这测试了在反向分析样本时定位样本验证码的能力。

2、 算法破解:这主要需要分析样本中加密部分的汇编代码,还原其加密算法,根据分析结果编写相应的解密程序,最后计算出flag。这就考验了样本分析过程中的耐心和扎实的逆向能力以及一定的代码开发能力。

逆向解题的常用技巧

1、逆向分析与函数猜测相结合,通过逆向分析缩小猜测范围。猜测是反向指出方向,猜测的想法是反向重新验证。

2、结合示例中的汇编代码上下文和整体程序功能,注意程序中给出的文字提示信息。

3、实际比赛中的逆题多为以提问为目的的题,目的性强,功能结构单一,无关代码少。

4、程序代码量极大时,可以先判断是否引用了更多的开源代码,程序的主要逻辑比较简单。

2、逆向基础

CTF逆向常用工具

Ollydbg、IDA、PCHunter、Exeinfo PE (PeTool)、CFF 探索、exeScope、ApiMonitorTrial、winhex。

CTF逆向需要涉及知识点

组装知识:window下X86和X64;android下的ARM和ARM64。

文件结构:windox下的PE文件;android下的dex文件和ELF文件。

反调试技术:window和android下的调试与反调试对抗技术。

装箱和拆箱:PE的装箱和拆箱,ELF和dex的装箱和拆箱。

开发能力:C、C++、python等语言的开发能力。

CTF样本逆向解题流程

1、违规保护:

获取示例程序时,首先使用ExeInfoPePE工具查看程序属于哪个平台,如windows X86/X64、android、linux等,是否采取了代码保护措施,如代码混淆、保护壳、各种反调试等,如果是这样,在分析样品前,需要通过样品混淆、拆包、反反调试等技术去除或绕过这些保护措施。

2、位置键码:

我们需要对目标软件进行反汇编,然后结合IDA和OD来快速定位关键代码(如验证函数、关键字符串信息、程序导入表)。

3、动静结合:

找到程序的关键代码后,我们需要对其进行详细的逆向分析。如果程序能在IDA中F5生成伪代码,那么我们先根据伪代码进行静态分析,然后在不明确的地方使用ollydbg工具进行动态调试,观察验证我们的猜想。

4、破解验证算法:

经过详细的逆向分析,在程序的关键代码(例如验证算法)之后,需要根据分析结果进行暴力破解或编写算法解密代码来获取或生成flags。

CTF中常见的验证算法

1、 直接对比验证:

密钥一般不加密,直接与内置程序中的密钥进行比较(即硬编码比较)。这类问题比较简单。

2、加密对比验证:

3、 反向自定义实现算法:

4、其他类型的加密问题:

如果实在解决不了,也可以尝试绕过或暴力破解穷举法。

如何找到关键代码

1、序列跟踪方法:

如果得到的样例程序比较少,代码量不大,主函数入口容易找到,可以使用顺序追踪的方法,从程序的主函数入口开始依次追踪,分析一步一步完成程序执行过程,基本了解程序。的验证部分。至于每一类程序的主要功能的搜索方法,大家可以百度一下。这里需要区分程序入口点和main函数的区别。一般需要找到main函数,但不是所有的main函数。到了MFC程序,还是需要根据具体情况来分析。如果有条件,也可以编写相应的程序,实现,然后拆机练习,找到程序的主要功能。这需要更多的实践、积累和总结。

2、 字符串查找:

如果给定的示例程序没有被混淆,并且有明显的字符串信息提示,那么可以使用字符串搜索功能,根据程序运行的提示,查找程序提示的字符串,反向查找提示的字符串。该程序。参考地址。比如IDA中shift+F12的字符串窗口,在OD->所有引用文本字符串中搜索,字符串搜索的优先级很高,很多情况下对我们解决问题非常有效,所以得到后程序中,您可以优先考虑字符字符串查找尝试。

3、系统函数断点:

如果程序很大,没有字符串提示信息可以使用,那么我们可以根据示例程序使用的函数来猜测定位关键验证码,这需要C语言或C++语言和Windows核心编程的知识, 熟悉 一些通用函数都有功能,比如:程序中出现弹窗,那么程序可能调用MessageBox函数,如果程序有输出,那么程序可能调用printf函数等,所以你可以通过程序显示的状态来到对应的函数断点处,然后堆栈反向回溯找到它的引用位置,进而找到关键代码。

3、装配基础

(以下知识点只起到抛砖引玉的作用)

x86 程序集

32位CPU有16个寄存器,32位寄存器存储4个字节的数据。他们的名字是:

4个数据寄存器(EAX、EBX、ECX和EDX);

2个索引和指针寄存器(ESI和EDI);

2个指针寄存器(ESP和EBP);

6个段寄存器(ES、CS、SS、DS、FS和GS);

1个指令指针寄存器(EIP);

1 个标志寄存器 (EFags)。

X64总成

64位CPU有16个通用寄存器,寄存器存储8字节数据。他们的名字是:

rax,rbx,rcx,rdx,rsi,rdi,rsp,rbp

r8,r9,r10,r11,r12,r13,r14,r15

32位使用堆栈帧作为传递参数的存储位置,而64位使用寄存器,分别使用rdi、rsi、rdx、rcx、r8、r9作为1-6个参数。rax 作为返回值。

64位没有栈帧指针,32位使用ebp作为栈帧指针,64位取消这个设置,rbp作为通用寄存器使用。

rax 用作函数返回值。

rsp栈指针寄存器,指向栈顶

rdi、rsi、rdx、rcx、r8、r9作为函数参数,依次对应第一个参数和第二个参数。. .

rbx, rbp, r12, r13, r14, r15作为数据存储,遵循被调用者的使用规则。简而言之,它们被随意使用。在调用子函数之前,应该备份它,以防止它被修改。

r10 和 r11 用作数据存储,并遵循调用者的使用规则。简单地说,使用前必须保存原始值。

大会知识

函数的 3 种调用约定(Call):_cdecl、_stdcall、_fastcall

_cdecl:C语言默认的函数调用方式。所有参数从右到左压入堆栈,调用者清除这些参数。堆栈恢复常用指令 add,esp,x,x 表示参数占用的字节数

_stdcall:是C++标准函数调用方式。所有参数从右到左压入堆栈。如果它是调用类的成员,则最后压入堆栈的是 this 指针。

这些堆栈上的参数在返回时由被调用函数清除,使用指令 ret x,其中 x 是参数占用的字节数。

_fastcall:是编译器指定的函数的快速调用方法。由于大多数函数的参数数量很少,因此使用堆栈传递非常耗时。因此,_fastcall通常规定前两个参数通过寄存器传递,其余参数通过堆栈传递。但是不同编译器编译出来的寄存器是不一样的,返回方式一般都是ret x。

总结三个调用约定:

区分函数参数和局部变量

函数局部变量的存在形式:mov eax,dword ptr[ebp -4]

函数参数表示法:mov eax, [esp+arg_0]

Arm32 组装(安卓)

ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能同时访问,可以访问哪些寄存器取决于ARM处理器的工作状态和具体的工作模式。但在任何时候,通用寄存器R14~R0、程序计数器PC,一个状态寄存器都是可以访问的。

未分组寄存器R0~R7,共8个;

分组寄存器R8~R12、R13~R14R8~R12:FIQ模式下有单独的一组R8~R12,共5个;

其他6个模式共用一组R8~R12,共5个;共10个;

R13~R14:USR和SYS模式(表格第一列)共用一组2个R13~R14so动态调试取参数,其他5个模式各有一个单独的10个R13~R14组;共12个;

程序计数器PC为R15,共1;

组寄存器 R13、R14

寄存器 R13 通常做堆栈指针 SP

寄存器R14用作子程序链接寄存器(Link Register-LR),也称为LR,指向函数的返回地址。

Arm64 组装(安卓)

汇编中共有 34 个寄存器。包括31个通用寄存器、SP寄存器、PC寄存器、CPSR寄存器。

31 个通用寄存器中:

X0-X30:表示它是一个64位的寄存器。

W0-W30:表示它是一个32位的寄存器。

X31:也称为零寄存器(一般用于变量初始化),它也有两种表示: XZR:表示一个64位的零寄存器,在内存中存储8个字节。

WZR:表示一个32位的零寄存器,在内存中存储4个字节。

SP:保存栈指针(栈顶指针),使用SP或WSP访问SP寄存器,即操作局部变量地址。

PC:程序计数器(PC Pointer Register),用于指向下一条即将执行的指令。

CPSR:状态寄存器

FP(X29):保存栈帧地址(栈底指针)

LP(X30):X30通常称为程序的链接寄存器,保存子程序结束后需要执行的下一条指令。

在掌握ARM汇编的基本结构之前,需要复习并记住以下条件指令

1.1、 B 跳转指令

1.2、 BL 带返回的跳转指令

1.3、 带有返回和状态转换的 BLX 跳转指令

1.4、 带状态切换的 BX 跳转指令

2.将跳转地址值直接写入程序计数器PC。

通过将跳转地址值写入程序计数器PC,可以实现4GB地址空间的任意跳转。跳跃前,使用MOV LR,PC

ARM 函数调用约定使用:ATPCS

ATPCS的英文全称是ARM-THUMB procedure call standard(ARM-Thumb procedure call standard)

总结:参数1~4分别存放在寄存器R0~R3中,其余参数从右向左压入栈中,被调用者实现栈平衡,返回值存放在R0中。

功能参数:

当参数个数小于等于4时,使用r0~r3这4个寄存器进行参数传输;如果参数个数大于4个,则剩余参数通过sp指向的数据栈传递。

比如有3个参数,那么r0代表函数的第一个参数,r1代表函数的第二个参数,r2代表函数的第三个参数。

比如有6个参数,那么r0-r3代表前4个参数,然后剩下的两个参数通过在栈上开辟一个8字节的空间来传递。

r0-r3:存放传递给函数的参数值,多余的参数在栈上传递。

r4 -r11:存储函数的局部变量,Thumb模式不会使用r8之后的寄存器

r12:是内部过程调用暂存寄存器(intra-procedure-call scratch register)。

r13:存储堆栈指针(sp)。在计算机中,堆栈非常重要。该寄存器保存指向栈顶的指针。更多关于堆栈的信息可以在这里找到。

r14:链接寄存器(链接寄存器)。存储被调用函数返回时要执行的下一条指令的地址。

r15:用作程序计数器。存储当前执行指令的地址。每次执行后,计数器都会递增 (+1).

函数的返回值放在 r0 中。

fp称为帧指针寄存器,即栈帧指针寄存器;sp称为堆栈指针寄存器,也就是堆栈指针寄存器。

在ARM指令系统中,就是​​地址递减栈。栈操作的参数的堆叠顺序是从右到左,参数的堆叠顺序是从左到右。包括push/pop和LDMFD/STMFD等。

函数返回值

1.当结果为32位整数时,可以通过寄存器R0返回。

2.当结果为64位整数时,可以通过R0和R1返回,以此类推。

3.当结果为浮点数时,可以通过浮点运算单元的寄存器f0、d0或s0返回。

4.当结果为复数浮点数时,可通过寄存器f0-fN或d0~dN返回。

5.对于更多位的结果,需要通过调用内存来传递。

4、反调试

窗口反调试技术

反调试技术

1.IsDebuggerPresent 函数

实现原理:只能用于自身进程的检测。通过查询进程环境块(PEB)中的IsDebugged标志,如果处于调试状态,则返回非0,如果调试状态未调试,则返回0。

2.NeQueryInfomationProcess 函数

原理:用于提取给定进程的信息。函数参数1代表进程句柄,参数2代表信息类型。如果第二个参数ProcessDebugPort的值设置为0x7,则可以用来返回句柄标识的进程是否被调试。如果处于调试状态,则返回调试端口,处于非调试状态,则返回 0。

3.CheckRemoteDebuggerPresent 函数

实现原理:可用于自己的进程和其他进程。通过查询进程环境块(PEB)中的ISDebugged标志,如果处于调试状态,则返回值返回非0,如果未调试,则返回0。

4.查找窗口A,枚举窗口

实现原理:通过检测运行环境的调试器的窗口信息。

5.OutputDebigString 函数

实现原理:调试器在调试应用程序时,通过触发异常来执行调试功能。通过使用SetLastError函数设置错误码方法,并使用OutputDebugString函数打印出来,如果程序被额外调试,则GetLastError得到错误。代码在前面使用

SetLastError 的错误代码是相同的。如果未调试,则错误代码可能是任何值。

6.注册表检测

实现原理:通过查找调试器引用的注册表信息来判断,当前环境下的注册表是否有调试器的信息,

下面是注册表中调试器的常用位置。

SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug(32 位系统)

SOFTWARE\Wow6432Node\Microsoft\WindowsNT\CurrentVersion\AeDebug(64位系统)

此注册表项指定在应用程序中发生错误时触发哪个调试器。默认情况下,它设置为 Dr. Watson。如果此表的键更改为 OllyDbg(与其他调试器 X64dbg、Windbg、ollyIce 一样)so动态调试取参数,则应用程序可以确定它正在被调试。

7.BeginDebugged 标志检测

实现原理:应用程序运行时,fs:[30h]指向PEB基地址。如果指向的 BeginDebugged 标志为 0,则不调试应用程序,否则调试。

8.检测进程头标志

实现原理:PEB结构的Reserved数组中有一个未公开的位置ProcessHeap,位于PEB结构的0x18处。ProcessHeap包含ForceFlags标志,可以用来判断是否处于调试状态。

9.检测 NTGlobalFlag 标志

实现原理:调试器启动的进程和正常创建启动的进程不同,所以它们创建内存推送的方式也不同。NTGlobaFlag 标志未由 Microsoft 发布,位于 PEB 偏移量 0x68。如果值为0x70,则表示程序由调试器启动。

10.检查父进程是否为explorer.exe

实现原理:对于正常启动的应用程序,其父进程为explorer.exe。如果进程处于调试状态,那么它的父进程就是调试器进程。所以只要父进程不是explorer.exe进程,就可以识别为调试状态。

反调试检测总结

上述反调试技术是应用比较广泛的技术,反调试技术一般通过各种方案组合。除上述解决方案外,反调试技术还包括对程序中执行代码段的验证、正在运行的调试器检测、调试器签名检测、断点检测等。反调试是逆向工程的第一道门槛,只有跨过这个门槛,才能真正开始逆向工程。

可以借鉴以下调试和反调试的技术总结方案。

参考链接:

java层检测

1.isDebuggerConnected 函数

检测原理:在调试app时,调用android.os.Debug.isDebuggerConnected返回true,否则返回false。

2.android:可调试标签

检测原理:如果在Android中的AndroidManifest.xml文件中将debuggable属性值设置为true,则表示可以长时间调试,属性值为false表示无法调试。

3.包签名验证

检测原理:通过检测app的包签名信息,如果包签名不一致,则重新打包。

本机层检测

1.检测自己进程的TracerPid值

检测原理:可以通过/proc/pid/status或/proc/pid/task/pid/status获取TracerPid值。默认值为0,附加调试将成为调试PID值。

2.基于IDA调试器检测

检测原理:android_server端口号(通过/proc/net/tcp检测23946端口),android_server文件信息,调试器进程名,

3.检测自己的流程图

检测原理:通过检测/proc/pid/maps来判断是否要调试,检测自己的进程是否有保护敏感模块信息。

4.检测父进程zygote进程

检测原理:因为zygote是所有程序的父进程,所以所有应用app也是通过fork创建的,通过/proc/pid/cmdline获取。如果当前父进程这么久都不是zygote进程,说明已经调试过了。

5.检查自己的状态

检测原理:app在被挂载和调试的过程中会被挂起,所以通过/proc/pid/stat或者/proc/pid/task/stat获取。当第三个字段属性为 t 时,表示正在调试应用。调试暂停挂起。

6.抢占Ptrace

检测原理:在android系统中,一个app只能被ptrace一次。所以先ptrace自己。

android反调试总结

由于android系统是开源的,所以上述反调试的系统属性和功能可以通过修改系统源代码,然后重新编译来测试。对于这种修改系统的方式,其实可以使用自己的ptrace方法。如果 ptrace 后 tracePid 值仍为 0,则表示系统已重新修改和编译。

攻防是一个持续的过程,反调试和过度反调试也是如此。这只是高潮和低谷。上面的列表只是一些简单的解决方案。

5、打包和解包

橱窗包装方案

专门负责保护软件免受未经授权的修改或编译的程序。它附在原始程序中。它通过窗口加载器加载到内存中后,在原程序之前执行以获得控制权。在执行过程中,对原始程序进行解密和还原。恢复后,控制返回到原来的程序并执行。原始代码。

其作用:可以有效防止破解者非法修改程序文件,也可以防止程序被静态反编译。不同的shell侧重于不同的方面,有的侧重于压缩,有的侧重于加密。比如压缩壳的特点是减小软件体积,加密保护不是它的重点。

常见的shell加载过程:

1.保存入口参数

2.获取shell本身需要使用的API地址

3.解密原程序各块数据

4.IAT 的初始化

5.重定位项处理

6.钩子接口

7.跳转到程序的原始入口点(OEP)

贝壳种类:

压缩壳有:UPX、ASPack、PECompact等。

UPX 是开源的:

ASPack shell 官网:

加密外壳:AsProtect、Armadillo、ExeCryptor 等。

虚拟外壳:Themida、Winlicense、VMProtect 等。

窗户脱壳解决方案

手动拆包步骤:

1.找到真正的程序入口点

2.抓取内存镜像文件

3.重建PE文件

定位程序OEP的方法:

1.根据跨段指令搜索OEP

2.查找带有内存访问断点的 OEP

方法:先打开内存模块(Alt+M),然后直接在代码段(如.text段)上执行内存访问断点(F2))。此断点是一次性断点。当段位于读取或执行时被中断。中断发生后,断点会被自动删除。

3.根据栈平衡原理找到OEP

在编写packer软件时,一定要保证shell初始化的寄存器的值与原程序相同。通常pushd/popad、pushfd/popfd命令用于保存和恢复live环境。

4.根据编译语言的特点寻找OEP

各种语言编译的文件入口点各有特点。用同一个编译器编译的程序,入口代码相似,都有一段启动代码,编译程序时编译器会自动与程序连接。完成必要的初始化工作后,调用 WinMain 函数。

安卓打包解决方案

目前市面上的加固产品种类有:360加固、Ai加密加固、棒棒加固、腾讯乐谷、网易易盾、奇威加固、顶级形象加固。

一般将App源程序加密存放在那些目录中,一般是:dex文件的尾部、libs目录、assets目录。

网易易盾:libnesec.so、libbugrpt.so

爱加密:libexec.so、libexecmain.so、ijiami.dat

砰砰:libsecexe.so、libsecmain.so、libDexHelper.so

阿里居安全:aliprotect.dat、libsgmain.so、libsgsecuritybody.so

腾讯安全:libtosprotection.armeabi.so、libtosprotection.armeabi-v7a.so、libtosprotection.x86.so

娜迦:libchaosvmp.so、libddog.so、libfdog.so

360:libprotectClass.so、libjiagu.so、libjiagu_art.so、libjiagu_x86.so

通过盾牌:libegis.so、libNSaferOnly.so

网秦:libnqshield.so

百度:libbaiduprotect.so

腾讯:libshel​​lx-2.10.6.0.so、libBugly.so、libtup.so、libexec.so、libshel​​l.so

APKProtect:libAPKProtect.so

Kiwi 安全性:libkwscmm.so、libkwscr.so、libkwslinker.so

强化功能

安卓解包解决方案

解包的主要过程是在App程序运行后释放源App的数据内存,并复制释放的数据。

1.内存转储方法

通过使用frida框架结合解包脚本dex-dump

通过暴力搜索应用程序内存中的 dex.035 或 dex.036。

通过读取 /proc/pid/maps 找到后,转储数据。

2.挂钩键功能方法

主要使用frida框架进行脚本开发。

传递hook key函数InMemoryDe​​xClassLoader、dvmDexFileOpenPartial、DexClassLoader、dexFileParse、memcmp然后dump解密后的dex数据

3.动态调试方法

通过动态调试app的mmap函数的断点,然后去dump源dex数据。

4.自定义系统方法

重新编译android系统,修改并刷机。

挂钩 Dalvik_dalvik_system_DexFile_defineClassNative。

枚举所有DexClassDef,调用dvmDefineClass强制加载所有类

5.dex2oat 方法

ART模式下,dex2oat生成oat时,内存中的DEX就完成了。

结尾

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论