1、背景
在CTF比赛中, CTF逆向题目除了需要分析程序工作原理, 还要根据分析结果进一步求出FLAG。逆向在解题赛制中单独占一类题型, 同时也是PWN题的前置技能。在攻防赛制中常与PWN题结合。CTF逆向主要涉及到逆向分析和破解技巧,这也要求有较强的反汇编、反编译、加解密的功底。
CTF中的逆向题目一般常见考点
1、常见算法与数据结构。
2、各种排序算法, 树, 图等数据结构。
3、识别加密算法与哈希算法代码特征,识别算法中魔改的部分。
4、代码混淆, 代码虚拟化, 修改代码流程, 反调试等。
5、软件加密壳是软件保护技术的集中应用。
CTF逆向题目两大主题:暴力破解、算法分析破解
1、暴力破解:通过修改汇编代码而跳过程序内部校验部分,从而改变程序的正常逻辑,最后满足题目要求来获取flag。这就考验在逆向分析样本时候,对样本验证代码定位能力。
2、算法破解:这主要需要分析样本中的加密部分的汇编代码,将其加密算法进行还原,并根据分析结果写出对于的解密程序,最后算出flag。这就考验了对样本分析过程中的耐心和扎实的逆向能力和一定的代码开发能力。
逆向解题常见技巧
1、逆向分析与功能猜测结合, 通过逆向分析进行缩小猜测范围, 猜测为逆向指出方向, 逆向再验证猜测的思路。
2、结合样本中汇编代码上下文与整体程序功能, 关注程序中给出的文字提示信息。
3、实际比赛的逆向题目多数是为出题而出题, 有目的性强, 功能结 构单一, 无关代码少等特点。
4、遇到程序代码量极大时, 可以先判断是否引用了较多的开源的代码, 而程序的主逻辑相对简单。
2、逆向基础
CTF逆向常用工具
Ollydbg、IDA、PCHunter、Exeinfo PE(PeTool)、CFF Explore、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、破解验证算法:
通过详细逆向分析完,程序的关键代码(例如:验证算法)之后,接着就要根据分析出的结果,进行暴力破解或者进行算法解密代码的编写以此来获取或生成flag。
CTF中常见的验证算法
1、 直接比较验证:
密钥一般没有经过加密,直接跟内置程序中的key进行比较(也就是硬编码方式比较),此类题型比较简单。
2、加密比较验证:
密钥一般会进行如异或、base64、MD5、RC4等形式的加密,此类题型需要识别出其加密方式,然后再根据其算法特点还原出相应的key值,现实CTF比赛中可能出现密钥用不同加密算法分段加密或嵌套验证等多种加密组合起来的方式,也需要去深入识别分辨。
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个标志寄存器(EFlags)。
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 用作函数参数,依次对应第1参数,第2参数。。。
rbx,rbp,r12,r13,r14,r15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改。
r10,r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值。
函数(Call)的3种调用约定:_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汇编(Android)
ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可以访问的,取决ARM处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R14~R0、程序计数器PC、一个状态寄存器都是可访问的。
未分组寄存器 R0 ~ R7,共8个;
分组寄存器 R8 ~ R12,R13 ~ R14 R8 ~ R12:其中FIQ模式下有单独的一组 R8 ~ R12,共5个;
另外 6种模式共用一组R8 ~ R12,共5个;总共10个;
R13 ~ R14:其中USR和SYS模式(表格的第一列)共用一组R13 ~ R14共2个,另外5种模式下各有独自的一组R13 ~
R14共10个;总 共12个;程序计数器 PC 即R15,共1个;
分组寄存器R13、R14
寄存器R13通常做堆栈指针SP
寄存器R14用作子程序链接寄存器(Link Register-LR),也称为LR,指向函数的返回地址。
Arm64汇编(Android)
汇编中共有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指针寄存器),它用于指向即将要执行的下一条指令。
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
总结:识别函数的方法就是汇编指令中是否有包含:B、BL、BLX、BX、PC的汇编指令。
ARM函数调用约定采用的是:ATPCS
ATPCS的英文全称是ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)
总结:参数1参数4 分别保存到 R0R3 寄存器中 ,剩下的参数从右往左一次入栈,被调用者实现栈平衡,返回值存放在 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:链接寄存器(link register)。存储着当被调用函数返回时,将要执行的下一条指令的地址。
r15:用作程序计数器(program counter)。存储着当前执行指令的地址。每条执行被执行后,该计数器会进行自增(+1)。
函数的返回值放到r0中。
fp叫做frame pointer寄存器,即栈帧指针寄存器;sp叫做stack pointer寄存器,即栈指针寄存器。
在ARM指令系统中是地址递减栈,入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的你操作。包括push/pop和LDMFD/STMFD等。
函数返回值
1.结果为一个32位的整数时,可以通过寄存器R0返回。
2.结果为一个64位整数时,可以通过R0和R1返回,依此类推。
3.结果为一个浮点数时,可以通过浮点运算部件的寄存器f0,d0或者s0来返回。
4.结果为一个复合的浮点数时,可以通过寄存器f0-fN或者d0~dN来返回。
5.对于位数更多的结果,需要通过调用内存来传递。
…持续更新
如果你是准备学习网络安全/黑客技术,下面这些你应该能用得上:
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析😝有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓
小白成长路线图
许多入门者转行网络安全,或者是有一定基础想进一步深化学习,却发现不知从何下手。接下来我将从成长路线开始一步步带大家揭开网安的神秘面纱。
1.成长路线图
共可以分为:
一、基础阶段
二、渗透阶段
三、安全管理
四、提升阶段
同时每个成长路线对应的板块都有配套的视频提供:
视频配套资料&国内外网安书籍、文档
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料
SRC技术文档汇总
还有大家最喜欢的黑客技术、
绿盟护网行动
网络安全源码合集+工具包
网络安全面试题
最后就是大家最关心的网络安全面试题板块
所有资料共87.9G,朋友们如果有需要全套《网络安全入门+进阶学习资源包》,可以扫描下方CSDN官方合作二维码免费领取哦~