CTF逆向实战ichunqiu
入门篇前三
在CTF逆向题目或实际软件破解中,常常会碰到.NET家族语言开发的程序,如C#。针对这类程序,使用IDA Pro打开时会额外提供一个选项,即 Microsoft .NET asssembly(微软.NET汇编语言),IDA Pro会将程序翻译为微软中间语言,使用reflector分析。
微软中间语言(MSIL, Microsoft Intermediate Language):也被称作MSIL汇编。虽然微软.NET框架很智能,其实它只认IL。无论程序采用什么高级语言编写,都将被编译器编译为IL并保存在PE文件中。虽被称作汇编,但相比win32汇编语言,IL可谓是名副其实的高级语言:它支持面向对象,通常不直接操作内存地址,以堆栈机的方式运行。由于运行完全受.NET监控,因此IL属于托管(Managed)代码,与之对应的是本机代码,被称为非托管代码。

GUID:
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符。GUID主要用于在拥有多个节点、多台计算机的网络或系统中。在理想情况下,任何计算机和计算机集群都不会生成两个相同的GUID。GUID 的总数达到了2^128(3.4×10^38)个,所以随机生成两个相同GUID的可能性非常小,但并不为0。所以,用于生成GUID的算法通常都加入了非随机的参数(如时间),以保证这种重复的情况不会发生。
GUID一词有时也专指微软对UUID标准的实现。
在 Windows 平台上,GUID 广泛应用于微软的产品中,用于标识如注册表项、类及接口标识、数据库、系统目录等对象。
字符移位:
例如:如果是小写字母v9 = (v8 - 83) % 26 + 97,等价于v9 = (v8 - 97 + 14) % 26 + 97,即向后移动14个字符;如果输入是大写字母,则:v9 = (v8 - 51) % 26 + 97,等价于v9 = (v8 - 65 + 14) % 26 + 97,也是向后移动14个字符;如果输入是其他字符,则不做任何操作。最后把经过移位后的字符串与check字符串即”Z3h_i5_psu1b_h0_F3jsfg3”相比较,相等就输出正确flag,最后逆向过来就是移位12
v9={ v9 - 97 + 12 } % 26 + 97 v9={ v9 - 65+ 12 } % 26 + 65
入门篇四
在CTF逆向实战题目中,如果主办方是python的忠实爱好者,那么可能会出现使用python语言编写,使用py2exe或pyinstaller进行打包成exe,针对这种程序一般需要采取特定方式进行处理。
可以通过py开头的字符串常量信息,确定是由python编写后打包的程序。
本步骤我们使用pyinstxtractor解包目标程序,并进行逆向分析获取flag字符串
CTF逆向分析中常使用pyinstxtractor.py脚本来提取pyinstall打包的exe文件的内容,脚本同时也可以提取出可执行文件中的pyz文件的内容。这里需要注意的一点是,如果源程序是使用python3进行编译打包的,需要使用python3执行该脚本,如果使用python2执行则会提取出错。依据提取后的内容,可以有选择地对pyc文件进行逆向,或者有时可以直接获取python代码。
在CTF逆向分析中,如果程序在运行阶段时动态生成flag字符串,可以通过windbg加载目标程序进行搜索字符串,获取对应范围内的flag。
Windbg是在windows平台下,强大的用户态和内核态调试工具。相比较于Visual Studio,它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能,却比VS更为强大。它的另外一个用途是可以用来分析dump数据。windbg更多用于进行软件漏洞分析,相较于Ollydbg其用户界面友好性较差,但是针对崩溃点可以输出详细的调试信息。
如果需要了解windbg的详细使用命令,可以在命令窗口输入?或者在线查询即可。
windbg命令行输入g命令,使得程序继续运行起来,在程序中输入任意字符,并按回车:
程序暂停后,在命令行窗口输入命令 s -a 0x1 0x10000000 "@flare-on.com"
s表示搜索字符串,-a表示ASCII码形式,如果使用-u表示unicode形式。接下来的参数分别为起始地址、结束地址和搜索内容,最大搜索范围不能超过0x10000000。

按照步骤来了,但却没有打印正确的字符串
输入d 0182cc78往前一个地址后找到flag

入门篇五
在CTF逆向题目或实际软件破解中,针对有界面的逆向程序往往有一些确定注册的按钮,点击按钮后跳转到check的代码逻辑。而这些代码,往往是逆向分析中的核心代码,也是动态调试时着重关注的代码逻辑段。因此,确定点击按钮后,按钮响应函数的位置就是带界面逆向分析中的关键所在。
我们这里首先使用xspy定位按钮响应函数位置。

{
olleydbg中的各个窗口
L:打开日志窗口;
E:打开可执行模块窗口;
M:打开内存映射窗口;
W:打开窗口列表;指被反汇编的程序所包含的窗口;
T:打开线程窗口;查看被反汇编的程序所包含的线程;
C:打开CPU窗口;默认打开;
R:打开搜索结果窗口;
…:打开运行跟踪窗口;
K:打开调用堆栈窗口;
}
本步骤通过使用xspy工具查看按钮ID对应按钮响应函数地址关系,再通过OllyIce查看窗口信息,寻找标题与ID的对应关系,从而确定按钮响应函数的虚拟地址。
在CTF逆向分析过程中,如果碰到无法使用xpsy的程序,更稳妥的方式是寻找按钮响应函数中的一些windows函数调用,针对这些函数调用设置断点,同样可以中断在按钮响应函数中,甚至有时候离关键代码比方法1更近。针对像本题目中带界面的程序,常用的思路是中断在读取字符输入的位置,一般针对GetDlgItemTextA/W和GetWindowTextA/W进行下断。


在CTF逆向分析中,往往对应的flag字符串是在程序运行过程中动态生成的,和第一节给你自信中不同,直接通过查看静态对应字符串内容是无法得到正确的flag的,这时候就需要进行内存追码。进行内存追码的前提是,已经成功定位了按钮响应函数,这样才可以让程序中断在关键逻辑代码处,进行追码和分析。







入门篇六(1)
RVA:RVA 是相对虚拟地址(Relative Virtual Address)的缩写,它是文件映射到内存中的“相对地址”。
FOA:FOA是文件偏移地址(File Offest Address)的缩写,它是文件在磁盘上存放时相对文件开头的偏移地址。
VA,虚拟地址,也就是程序被加载到内存中的地址
RVA,以虚拟地址前边加上个“相对的”,也就是说它还是按虚拟地址来换算,只不过不是从0开始,而是把一个模块的基址作为参考点。
RAW ADRRESS,或者FILE OSSFET,一般称文件偏移,你把一个文件看成一个连续的字节流,OFFSET就是这个字节流中的位置。
//文件偏移又称物理偏移,就是保存在磁盘里的文件。换算关系为:
将VA减去MODULE的BASE就是RVA的值。RVA与OFFSET的关系,是通过每个SECTION内领衔量相等为换算的。举例来说,假设一个EXE文件,BASE为0x00400000,第一个SECTION的名字叫.text,它的RVA是0x2000,那么它的VA就是0x00402000,也就是它个SECTION在内存中的起始地址是0x00402000;若假设这个SECTION的OFFSET是0x1000,那么最终的结果是,文件中从0x1000开始的一个SECTION,被映射到了内存地址0x00402000开始的内存区间。假设有一段代码位于这个SECTION中,它的地址(VA)是0x00402345,相当于它的RVA是0x2345,而它在SECTION内部的偏移量是0x0345,所以它的文件中的偏移量就是0x1000(SECTION的起始偏移量) + 0x0345,即0x1345。
总结:VA与RVA只相差一个BASE值,而VA与OFFSET的换算,关键是要确定这个地址属于哪一个SECTION。
什么是偏移量?
汇编语言中的定义为:
把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。 亦: 存储单元的实际地址与其所在段的段地址之间的距离。本质其实就是“实际地址与其所在段的段地址之间的距离”
更通俗一点讲,内存中存储数据的方式是:一个存储数据的“实际地址”=段首地址+偏移量,
你也可以这样理解:就像我们现实中的“家庭地址”=“小区地址”+“门牌号”
上面的“偏移量”就好比“门牌号”其实就相当于C++的指针一样啦,指出确切的地址而已……
查找来源:(63条消息) RVA、VA、RAW、偏移量_god00的博客-CSDN博客
步骤1:确定Dll导出函数信息
在CTF逆向题目或实际软件破解中,常常会碰到需要对某个关键Dll进行逆向分析,对于一些关键代码或者需要反复使用的函数或类接口,开发者通常使用Dll工程将代码打包成Dll,再将接口开放出来,供使用者调用,这样也一定程度上保护了核心代码。在进行Dll的逆向时,我们一般需要确定关键函数的导出信息,包括:函数所在Dll中的偏移、函数名称、完整函数头等,便于我们后续进行分析。
我们这里分别使用IDA Pro和dumpbin载入分析Dll程序,尝试确定函数导出信息。
(1)首先我们从 开始 点击运行,输入cmd打开命令行窗口,或者直接点击命令提示符,然后在命令行窗口输入 dumpbin /? 显示dumpbin的帮助信息
(2)接下来我们移动到DllReverser.dll的文件夹,输入命令 cd C:\Documents and Settings\Administrator\Desktop\1_06_DllReverser,这个文件夹看自己的dll文件在哪
这样是为了便于后续的命令行操作,平常也要养成在平行目录操作的好习惯。
(3)接下来我们查看DllReverser.dll的导出函数信息,输入 dumpbin /EXPORTS DllReverser.dll
通过输出结果我们可以看出,dll包括一个导出函数check_flag,通过名字可以判断出是用来检测flag字符串的函数,其相对虚拟地址偏移(RVA)为0x1010。
(4)然后,我们使用IDA Pro载入Dll进行分析,可以看到同样可以识别出是x86程序
(5)这里可以看到Dll文件加载的虚拟基址为0x10000000,加上之前得到的偏移为0x10001010,使用快捷键 G 输入0x10001010或者直接点击函数窗体check_flag函数,都可以定位到导出点函数
(6)使用快捷键 F5 可以进行反编译,获得函数伪C代码
(7)这里根据猜测,a1应该是传入的flag字符串,类型为char*,我们使用 右键 -> Rename lvar/ Set lvar type来修改a1的名称和类型
(8)再通过 右键 -> Hexadecimal 将后面的判断部分改为十六进制
通过修改后的代码,大概可以看到check函数的代码逻辑,接下来我们考虑动态调试,进一步梳理代码逻辑。同时,我们得到了函数声明如下: signed int __cdecl check_flag(char *flag)。
本步骤我们通过使用dumpbin命令和IDA Pro查看了函数导出表函数,并通过反编译功能简单梳理了代码静态逻辑,为动态调试做准备。
步骤2:Ollydbg的LoadDll动态调试Dll文件
本步骤使用Ollydbg的LoadDll插件加载目标Dll,对关键函数进行动态调试
具有LoadDll插件支持的Ollydbg,在载入Dll程序时,会弹框提醒是否调用LoadDll.exe,进行载入。正常情况下,载入后会自动暂停到Dll文件入口处位置。但是由于无法针对特定函数传入参数进行调用,因此也相对麻烦。
在CTF逆向实战中,针对Dll逆向程序,尤其是脱壳处理,会优先使用LoadDll模块,这样可以快速进行分析调试和文件转存。
(1)首先打开桌面上OllyIce文件夹下的OllyIce.exe,将DllReverser.dll拖入到OD窗体中,可以观察到OD直接提示是Dll文件,询问是否使用LoadDll.exe:
(2)点击是,可以发现OD反汇编显示在10001000处,但是实际EIP为00410148
(3)使用 ALT + M 快捷键,或者点击OD菜单栏中M按钮,查看程序内存映射情况

这里可以看到LoadDll.exe的加载基址为00400000,DllReverser.dll的加载基址为10000000,因此关键函数的虚拟基址仍然为10001010。针对动态调用可以采用两种方式,一种直接修改EIP到10001010,但是这样堆栈中是没有参数数据的,我们采取另外一种方式,通过push + call的方式进行调用。
(4)接下来,我们将反汇编窗口向下拉,可以发现在最后有一段空白区域,或者直接按CTRL+G后输入10006FEF跳转到相应地址
(5)通过IDA Pro静态伪代码分析可知,flag长度为16个字节,’\0’结尾,然后每个字符都需要满足isalpha函数。isalpha函数是系统库函数,判断一个字符是否为字母(a-z、A-Z)是则返回true。我们接下来在空白区域填充16个字母,并以’\0’结尾。右键依次选择 二进制 -> 编辑,或者使用 CTRL + E 快捷键
(6)在弹出框中ASCII码部分,填充“ABCDEFGHabcdefgh”,注意不要勾选保持大小,点击确定
(7)可以观察到对应虚拟地址空间,10006FEF开始地址的字节码已经被成功修改
(8)使用 CTRL + G 快捷键,输入10001000,跳转到反汇编窗口的起始位置
(9)单击选中10001000对应行,右键选择汇编或者使用快捷键空格,对该处地址的汇编代码进行修改
(10)依次修改为 push 0x10006fef 和 call 0x10001010 两句汇编语句
(11)一切就绪,我们设置EIP到10001000处。右键选择此处为新EIP,将EIP设置到我们修改的汇编代码处
在CTF逆向分析中,往往仅仅需要对关键代码逻辑进行分析,但是有时候触发跳转到关键代码逻辑的判断比较难以完成,可以在确定之前步骤不影响程序结果的基础上,直接修改EIP或者使用jump语句进行跳转,将控制流修改到关键函数入口处,从而进行直接的调试。采取此种方式时,要注意函数上下文条件是否满足,尤其是堆栈参数和堆栈平衡等。
(12)设置完毕可以观察寄存器窗口,发现EIP已经修改为10001000,我们使用快捷键 F8 进行单步。然后在到达 call check_flag 语句时,使用快捷键 F7 进行步入操作
(13)在进入check_flag函数领空后,观察右下角的堆栈信息可以发现,最上面为返回的EIP地址,下面为push指令压栈的函数参数
本步骤通过使用OD的LoadDll插件功能,加载了Dll文件,并通过修改填充虚拟地址数据,汇编修改代码,重定向EIP等方法,完成了关键函数的调用,至此Dll文件动态调试工作已经准备完毕,接下来我们对算法进行简单分析
步骤3:check_flag函数算法分析
本步骤对check_flag关键函数的算法进行简单分析,着重对比前两个小check逻辑
在完成前两个步骤的基础上,本步骤我们继续针对check_flag函数进行调试,并对check算法进行简单分析。
(1)在函数入口10001010处,我们继续使用快捷键 F8 进行单步,直到1000101D处的 repne scas byte ptr es:[edi] 语句,我们可以看到我们修改填充的字符串,被放置到EDI寄存器中。
REPNE SCAS BYTE PTR ES:[EDI] 指令详解
[(63条消息) REPNE SCAS BYTE PTR ES:EDI] 指令详解_小小攻城师的博客-CSDN博客
(2)通过累加edi寄存器,直到edi指向内存指针为0,加上接下来的 not ecx 和 dec ecx相当于是执行了strlen(flag)代码,最终字符串长度放置在ecx寄存器中,感觉这个应该就是求字符串长度,原理现在还不是很明白
这里执行完毕后,ecx寄存器的值为0x10 = 16,对应我们填充的字符串长度。
(4)接下来是一处比较,对应IDA Pro中 strlen(flag) == 16。满足这个校验后,接下来进入一段循环代码:

参考IDA Pro静态分析结果进行分析,可以得出本部分汇编指令首先将edi置0,然后在循环中累加,作为esi的偏移,esi指向目标字符串。通过调用函数10001111的返回值(isalpha,返回值放置到eax寄存器中),判断是否满足要求。
(5)我们单击10001052处代码,右键选择 断点 -> 运行到选定位置,或者使用快捷键 F4 ,直接跳过循环逻辑
(6)我们来到最后一处校验逻辑


从汇编代码动态调试的实际数值可以看出,eax、ecx、edx、edi分别放置对应16字节字符,每四字节一组,然后再通过类似 lea ebx, dword ptr [ecx+eax] 和使用sub指令的方式,进行分别求和或求差,与对应写死的数值进行比较,最终四次比较全部通过,为正确的flag字符串。
入门篇六(2)
通过入门篇六(1)中的分析,可以得到其中重要的函数check_flag,新建一个dll工程,
1 |
|
本段C++代码是Dll工程的主文件,第一个函数DllMain为Dll的入口函数,可以在该函数中填充Dll被加载和卸载时执行的代码。第二个函数为flag字符串的校验函数check_flag,具体算法已经在上小节实验中进行分析,大家可以对比下动态调试结果和实际代码的差异。在逆向分析结束时,对比源码可以帮助修正逆向过程中对算法判断的偏差。
别忘了还有头文件
1 |
|
第一句话是防止重复包含该头文件,第二句是声明一个dll导出函数。这里要注意,在cpp文件中必须要包含该头文件,否则不会生成check_flag函数相关汇编代码。
在上小节中,我们通过了两种方法获取了导出函数信息和函数声明。从开发者的角度考虑,如果需要动态调试Dll和验证flag字符串,除了上小节中的方法一外,还可以使用正向逻辑,即编写一个新程序调用该dll,然后针对这个新程序进行动态调试。 其实就是建立一个可执行文件调用刚才写的dll,再对这个可执行文件进行逆向分析
然后再次新建,向工程中添加C++ Source file 文件 main.cpp ,并输入如下内容:
1 |
|
这里可以看到已经输出Wrong,接下来我们拷贝生成的exe和之前的dll到同一目录,就可以对dll的check_flag函数进行动态调试。
本步骤通过函数指针的方式,编写生成了调用Dll中check_flag关键函数的程序,用于下一步进行动态调试。
使用这种方法可以避免繁琐的OD修改操作,将Dll间接转换成可执行程序调试,相当于自己封装了一个针对性的LoadDll.exe,在已知关键函数和函数声明的前提下,具有较好的效果。
(1)拷贝出DllReverserApp.exe和DllReverser.Dll到同一目录下:

程序自动在入口点处中断一次,我们接下来需要在check_flag函数位置设置断点,可以采取包括查看常量字符串引用、查看内存镜像窗口、设置API断点等方法。
(3)我们采取最常用的字符串方式,右键选择 Ultra String Reference -> Find Ascii:
(4)找到三个关键字符串,双击引用字符串,可以到我们编写的核心代码位置:

(5)使用快捷键 F2 在00401088处设置断点,然后使用 F9 运行到断点位置,然后使用 F7 跟进函数
至此,已经成功跳转到check_flag函数领空,和方法二取得同样效果。
本步骤演示了通过调试调用Dll函数的程序效果,并通过定位到核心代码,可以继续进行动态调试,这个在上节实验里方法一处已经进行了演示,过程与之前相同。
解决这类问题要注意的是,由于是在寄存器中进行操作,相当于是DWORD无符号整型,因此需要考虑溢出的问题,如果有溢出需要加上0x100000000。
在本题示例中,由于之前对字符范围进行了限制,因此根据相减和加和结果判断,这里没有产生溢出。这里的方程组比较简单,相等于是求解两个二元一次方程组。如果针对更复杂多元高次方程组,可以使用matlab或者Mathematic进行方程组求解。

