CTF逆向入门:从UPX脱壳到Delphi程序分析实战

发布时间:2026/7/21 11:33:47
CTF逆向入门:从UPX脱壳到Delphi程序分析实战 1. 项目概述为什么从UPX和Delphi开始你的CTF逆向之旅如果你对网络安全竞赛CTF中的逆向工程Reverse Engineering感兴趣但一看到汇编代码和复杂的调试器就头疼那么恭喜你这篇内容就是为你准备的。很多新手教程一上来就讲IDA Pro的复杂操作和x86汇编指令这就像让一个刚学游泳的人直接去冲浪很容易劝退。我个人的经验是逆向入门的关键在于建立“正反馈”——你需要快速获得“我搞定了”的成就感而不是在无尽的挫败感中放弃。“从UPX脱壳到Delphi程序找Flag”这个路径正是为新手量身打造的“新手村黄金路线”。UPXUltimate Packer for eXecutables是CTF逆向题中最常见、最简单的压缩壳Packers之一它的脱壳过程几乎是标准化的有现成的工具可以一键完成。而用Delphi一种经典的Windows桌面程序开发工具编写的程序其代码结构清晰字符串、按钮事件等资源相对容易定位不像经过高度混淆的C程序那样令人无从下手。通过这条路线你可以绕过最枯燥的底层汇编分析先专注于理解“程序在做什么”以及“如何找到关键信息Flag”从而快速建立起逆向分析的基本逻辑和信心。简单来说这个项目就是带你亲手完成一次完整的CTF逆向解题拿到一个被UPX加壳的Delphi程序先脱去它的“外壳”让程序恢复原貌然后像侦探一样在程序内部寻找隐藏的Flag。我们会使用Pikachu靶场一个知名的网络安全练习平台中的相关题目作为实战案例确保每一步都有清晰的靶子可打。整个过程你不需要深厚的编程功底只需要有耐心和好奇心跟着步骤走就能亲眼看到Flag从乱码中浮现出来。2. 逆向工程核心思路拆解理解“壳”与“原程序”的关系在深入动手之前我们必须先搞懂两个核心概念“加壳”和“脱壳”。这能让你明白自己每一步在做什么而不是机械地执行命令。2.1 程序加壳的原理与目的你可以把一个正常的可执行程序.exe文件想象成一个精心包装的礼物盒。盒子里面装着真正的礼物程序的核心代码和数据而盒子本身就是“壳”。开发者给程序加壳主要有两个目的压缩体积就像用压缩软件打包文件一样让程序变得更小便于分发。UPX最初就是为了这个目的设计的。防止分析与破解这是CTF和软件安全中更常见的原因。一个坚固的壳会让逆向分析者无法直接看到“礼物”原代码他们必须先想办法打开这个“盒子”。壳会在程序运行时先于原代码执行负责将压缩或加密的原代码在内存中解压、解密并还原然后再跳转到原程序的入口点执行。在CTF逆向题中出题人给程序加壳就是为了增加第一步的分析难度考察选手对程序加载、内存操作等底层知识的掌握。2.2 UPX壳的特点与识别UPX是一款开源、免费的可执行文件压缩工具因其高压缩比和稳定性而广泛应用。在CTF中它之所以成为“入门壳”是因为普及性高很多出题人用它来设置第一道门槛。有成熟的脱壳机UPX本身是开源工具因此存在与其配套的“脱壳机”Unpacker可以自动化完成脱壳过程。这降低了手动脱壳的技术门槛。特征明显用UPX加壳的程序其文件结构中包含明显的UPX签名很容易被识别。如何识别一个程序是否被UPX加壳使用查壳工具这是最快捷的方法。常用的工具有Detect It Easy(DIE)、PEiD虽然老旧但经典。你只需要把程序拖进工具窗口它就会告诉你是否检测到UPX壳。观察程序行为用文本编辑器如Notepad以十六进制模式打开.exe文件在文件开头附近搜索字符串“UPX”如果找到很可能就是UPX壳。使用命令行在装有UPX工具的环境下执行upx -l 目标文件.exe如果显示压缩信息则证明是UPX壳。注意并非所有带UPX签名的程序都能用UPX官方工具直接脱壳。出题人有时会修改UPX壳的签名或结构制造“魔改UPX壳”这时就需要手动脱壳技巧。但对于入门练习我们接触的都是标准壳。2.3 Delphi程序的分析切入点Delphi使用Pascal语言并且其编译生成的程序具有一些鲜明的特征这为我们逆向分析提供了便利的突破口丰富的字符串资源Delphi程序中的字符串常量如提示信息、错误信息、成功的Flag通常会被集中存储在程序的资源段或数据段中相对容易查找。事件驱动架构按钮点击、窗体创建等都有明确的事件处理函数。通过查找这些事件相关的字符串或标准函数调用可以快速定位到关键代码逻辑。特定的运行时库函数Delphi程序会调用其特有的运行时库RTL函数如Button1Click事件、StrCmp字符串比较等。熟悉这些函数特征有助于在反汇编代码中定位关键点。窗体资源Delphi程序的界面窗体信息也存储在资源中使用专用工具如DeDe或IDR可以反编译出窗体的结构甚至部分代码。我们的核心思路就是先脱壳恢复程序原貌然后利用Delphi程序的特性快速定位到处理用户输入、进行验证判断的核心代码区域从中找到或推导出Flag。3. 环境与工具准备打造你的逆向工作台工欲善其事必先利其器。我们不需要一开始就配置一个“全宇宙最强”的复杂环境以下几款工具足以应对入门阶段的绝大多数UPX脱壳和Delphi逆向题目。3.1 核心工具清单及安装查壳工具Detect It Easy (DIE)作用快速检测可执行文件使用的编译器、链接器、加壳类型。获取从其GitHub仓库或官网下载便携版解压即用。为什么选它比老旧的PEiD更新更准支持更多壳和编译器签名界面友好。脱壳工具UPX 官方命令行工具作用用于给程序加壳/脱壳。对于标准UPX壳一键脱壳。获取从UPX官网下载对应操作系统版本。安装解压后将upx.exe所在目录添加到系统的环境变量PATH中方便在任意命令行窗口使用。验证安装打开命令提示符CMD或PowerShell输入upx --version能显示版本信息即成功。静态分析工具IDA Pro (Freeware) 或 Ghidra作用反汇编器将二进制程序转换成可读的汇编代码并进行静态分析。IDA Pro是行业标准但免费版功能受限Ghidra是NSA开源的工具完全免费且功能强大。入门推荐新手强烈建议从Ghidra开始。它完全免费支持反编译将汇编代码转换成更易读的类C代码这对理解程序逻辑帮助巨大。虽然界面不如IDA Pro流畅但用于入门学习绰绰有余。获取与安装Ghidra从GitHub发布页下载需要安装Java运行环境JRE 11然后运行ghidraRun.bat启动。IDA Freeware从Hex-Rays官网下载注意免费版不支持64位文件和反编译插件。动态调试工具x64dbg作用在程序运行时动态跟踪其执行流程、查看和修改内存与寄存器值。获取从官网下载便携版解压即用。为什么选它比经典的OllyDbg更现代对64位程序支持更好界面直观插件丰富社区活跃。它是动态分析尤其是跟踪脱壳过程、分析算法的利器。Delphi专用分析工具IDR (Interactive Delphi Reconstructor)作用专门用于分析Delphi程序的工具能反编译出窗体结构、事件处理函数地址、字符串资源等是分析Delphi程序的“神器”。获取在GitHub或相关安全论坛搜索其最新版本。注意不同版本的IDR对不同版本的Delphi编译的程序支持度不同如果遇到无法分析的情况可以尝试换用其他版本或结合其他方法。文本与十六进制编辑器010 Editor 或 HxD作用以十六进制和文本形式查看、编辑文件。用于手动修复PE文件头、查看文件签名等。获取010 Editor是商业软件但功能强大HxD是免费轻量级选择。3.2 环境配置建议操作系统推荐使用Windows 10/11。绝大多数CTF逆向题目尤其是涉及PE文件的都是针对Windows环境。可以在物理机或虚拟机如VMware, VirtualBox中搭建。工作目录建立一个专门的工作文件夹例如D:\CTF_Reverse在里面为每个练习题目创建子文件夹将工具和题目文件都放进去保持工作区整洁。虚拟机快照如果你在虚拟机中练习强烈建议在安装配置好所有工具后创建一个干净的快照。因为动态调试可能会运行来历不明的程序快照可以让你随时回滚到一个安全干净的状态。实操心得不要陷入“工具收集癖”。新手阶段熟练掌握上述2-3个核心工具如DIE查壳、UPX脱壳、Ghidra分析的用法远比拥有一大堆用不来的工具更重要。先解决“有没有”的问题再追求“好不好”。4. 实战第一步UPX脱壳详解与操作现在我们进入实战环节。假设我们从Pikachu靶场下载到一个名为challenge_upx.exe的文件。4.1 识别确认壳类型打开Detect It Easy将challenge_upx.exe文件拖入主窗口。在右侧的“检测”信息栏中你会看到类似的结果Compiler: Embarcadero Delphi Linker: Borland Delphi 2009 Packer: UPX(3.96)[NRV,brute]这明确告诉我们这是一个用Delphi编译的程序并且被UPX 3.96版本加壳了。4.2 使用UPX工具进行自动化脱壳由于检测到是标准UPX壳我们可以尝试使用UPX官方工具进行脱壳。打开命令提示符CMD导航到challenge_upx.exe所在的目录。执行脱壳命令upx -d challenge_upx.exe-d参数代表解压脱壳。如果脱壳成功你会看到类似输出Ultimate Packer for eXecutables Copyright (C) 1996 - 2023 File size Ratio Format Name -------------------- ------ ----------- ----------- 102400 - 51200 50.00% win64/pe challenge_upx.exe Unpacked 1 file.这表示文件已成功脱壳体积通常会变大因为解压了。脱壳后会生成一个新的challenge_upx.exe文件覆盖原文件或生成新文件取决于UPX版本建议先备份原文件。为了区分我们可以将脱壳后的文件重命名为challenge_upx_unpacked.exe。4.3 验证脱壳结果再次使用Detect It Easy检查challenge_upx_unpacked.exe。此时“Packer”一栏应该显示为“Nothing found”或者具体的编译器信息而不再有UPX。这表明壳已成功脱去。4.4 处理脱壳失败的情况如果upx -d命令执行失败例如提示“NotPackedException: not packed by UPX”可能原因有壳被修改魔改UPX这是CTF中常见的套路。UPX工具只认识标准结构的UPX壳出题人稍微修改壳的头部信息就能让官方工具失效。文件已损坏或不完整。应对魔改UPX壳的手动脱壳思路入门了解手动脱壳的核心是“抓取内存中的原程序镜像”Dump。因为无论壳怎么变化它的最终任务都是在内存中还原出原始程序并执行。我们可以利用调试器来完成。使用x64dbg加载程序用x64dbg打开加壳的程序。找到原程序入口点OEPUPX壳的执行流程是壳代码运行 - 解压原代码到内存 - 跳转到原程序的入口点。我们需要在调试器中找到这个“跳转”指令。一个常见技巧是寻找一个POPAD指令恢复所有通用寄存器后紧跟一个远跳转JMP到一个较大地址如0x0040XXXX这个跳转的目标很可能就是OEP。在OEP处转储Dump内存当程序执行到OEP时原代码已经在内存中完整展开。此时使用x64dbg的插件如Scylla或菜单功能将当前进程的内存镜像转储到一个新的.exe文件中。修复导入表IAT转储出来的文件可能无法直接运行因为其导入表记录程序调用了哪些系统函数地址还是内存中的地址。需要使用Scylla等工具的“IAT AutoSearch”和“Get Imports”功能自动查找并修复导入表然后“Fix Dump”应用到转储的文件上。测试运行运行修复后的文件看是否能正常工作。注意事项手动脱壳涉及调试技巧是逆向工程中阶内容。对于纯新手如果遇到upx -d失败的题目可以先搜索是否有现成的脱壳脚本或记录或者暂时跳过先掌握标准流程。Pikachu靶场的入门题通常不会设置魔改壳。5. 实战第二步分析脱壳后的Delphi程序成功脱壳后我们得到了challenge_upx_unpacked.exe。现在开始寻找Flag。5.1 字符串检索——最直接的线索程序中的任何提示信息、成功信息、加密后的Flag对比字符串都可能以明文或编码形式存储在文件中。使用Ghidra进行字符串搜索用Ghidra创建一个新项目导入challenge_upx_unpacked.exe进行默认分析。分析完成后在菜单栏选择Search - For Strings...。在弹出的窗口中点击“Search”按钮。Ghidra会列出程序中的所有字符串常量。仔细浏览这个列表。关注包含以下关键词的字符串flag,Flag,FLAGsuccess,correct,wrong,error,try againcongratulation,win,lose看起来像Base64编码的字符串如末尾带字符集为A-Za-z0-9/看起来像十六进制的字符串仅包含0-9, A-F一些可疑的、无上下文的单词或短语。使用专用工具搜索也可以使用Strings命令行工具Sysinternals Suite中包含或IDA Pro的字符串窗口原理相同。示例你可能会在字符串列表中发现Congratulations! The flag is: flag{this_is_a_sample}这样的明文Flag。那么题目就直接解决了。但通常不会这么简单Flag可能是经过运算后生成的。5.2 使用IDR定位关键代码如果字符串搜索没有直接发现Flag我们就需要深入代码逻辑。对于Delphi程序IDR能极大提升效率。打开IDR加载challenge_upx_unpacked.exe。IDR会自动分析。分析完成后你可以在左侧看到Forms窗体、Units单元、Procedures过程/函数等树状结构。查看窗体Forms展开Forms你能看到程序的所有窗口界面。点击一个窗体如TForm1右侧会显示其属性包括上面有哪些按钮TButton、输入框TEdit、标签TLabel等控件。定位按钮事件这是关键找到那个看起来是“提交”、“检查”、“验证”的按钮比如Button1。在IDR中这个按钮会有一个OnClick事件属性其值是一个内存地址例如0x00451234。这个地址就是点击按钮后要执行的代码位置跳转到事件代码记下这个地址如0x00451234然后打开Ghidra或x64dbg。在Ghidra的“地址”栏Window - Address Table直接输入0x00451234并回车会跳转到对应的反汇编/反编译代码处。在x64dbg中按CtrlG输入地址0x00451234同样可以跳转。5.3 静态分析验证逻辑以Ghidra为例假设通过IDR我们找到了验证按钮的点击事件地址0x00451234并在Ghidra中跳转到了该函数。阅读反编译代码Ghidra会将汇编代码反编译成类C的伪代码Decompiler窗口。这比直接看汇编友好得多。寻找关键操作在伪代码中寻找以下模式获取用户输入可能会调用类似GetWindowTextA或Delphi的TEdit.Text属性获取函数。字符串比较寻找strcmp,lstrcmpA,CompareStr等函数调用。比较的一方是用户输入另一方可能是一个硬编码的字符串可能是明文也可能是加密后的。条件跳转比较之后通常会有一个if判断根据比较结果决定是显示成功信息包含Flag还是失败信息。分析加密/变换逻辑如果比较的不是明文而是一串乱码或经过处理的字符串那么代码中在比较之前很可能对用户输入进行了一系列操作如Base64编码、异或运算、简单加减法、MD5哈希等。你需要仔细阅读这段变换逻辑。正向推导理解代码如何将输入A变成B然后与硬编码的C比较。如果B C则成功。逆向推导解题我们知道硬编码的C也知道变换逻辑那么就可以反向运算求出正确的输入A。这个A可能就是Flag本身也可能是提交后程序显示的Flag。5.4 动态调试验证与获取Flag以x64dbg为例静态分析可能无法完全理清逻辑或者我们想动态地看到每一步的值。这时就需要调试器。在关键点下断点用x64dbg打开脱壳后的程序。按CtrlG输入从IDR获得的地址0x00451234跳转到验证函数开始处。按F2在此处设下断点地址变红。运行程序按F9运行程序。程序界面会弹出。触发断点在程序输入框中尝试输入一些测试内容如test然后点击那个验证按钮。此时x64dbg会立即中断在0x00451234处程序暂停。单步执行与分析F7单步步入Step Into遇到函数调用会进入函数内部。F8单步步过Step Over遇到函数调用直接执行完不进入。结合寄存器窗口、栈窗口、内存窗口观察每一步执行后关键数据如你输入的字符串、中间运算结果、最终比较值的变化。观察比较过程当执行到比较函数如strcmp时查看其两个参数通常在寄存器RCX/RDX或栈上指向的内存内容。其中一个是你输入处理后的结果另一个就是程序内置的“正确值”。修改与测试你甚至可以在内存窗口中直接修改“正确值”或者你输入处理后的中间值让比较通过来测试逻辑是否正确。通过静态分析与动态调试相结合你就能清晰地理解程序的验证逻辑并计算出正确的Flag。6. Pikachu靶场实例演练一步步找到Flag让我们以一个虚构但典型的Pikachu UPXDelphi题目为例串联整个流程。题目描述这是一个简单的密钥验证程序。你需要找到正确的密钥。步骤一获取与查壳下载题目文件pikachu_delphi_upx.exe。用DIE查壳确认是UPX(3.96)[NRV,brute]加壳的Delphi程序。步骤二脱壳备份原文件。命令行执行upx -d pikachu_delphi_upx.exe -o pikachu_unpacked.exe用DIE检查pikachu_unpacked.exe确认壳已脱。步骤三初步探索运行pikachu_unpacked.exe看到一个窗口有一个输入框TEdit和一个“Check”按钮TButton。随意输入123并点击“Check”弹窗提示“Wrong Key!”。步骤四使用IDR定位用IDR打开pikachu_unpacked.exe。在Forms下找到TForm1看到其下有Edit1和Button1。查看Button1的属性找到OnClick: 0x00401000。步骤五Ghidra静态分析用Ghidra分析pikachu_unpacked.exe在地址栏跳转到0x00401000。查看反编译的伪代码关键部分可能如下所示void FUN_00401000(void) { char *pcVar1; int iVar2; char local_108 [256]; // 获取输入框文本 pcVar1 (char *)FUN_00401560(Edit1); // 假设这是获取TEdit.Text的函数 // 对输入进行简单变换每个字符ASCII码1 iVar2 0; while (pcVar1[iVar2] ! \0) { local_108[iVar2] pcVar1[iVar2] 1; iVar2 iVar2 1; } local_108[iVar2] \0; // 与内置字符串比较 iVar2 lstrcmpA(local_108, j!dpnf!up!tusjoh); if (iVar2 0) { // 成功显示Flag MessageBoxA(0, Success! Flag is: flag{you_found_it}, Congrats, 0); } else { MessageBoxA(0, Wrong Key!, Error, 0); } return; }注以上为简化的示意伪代码分析逻辑程序将用户输入的每个字符的ASCII码加1然后与字符串j!dpnf!up!tusjoh比较。如果相等则显示Flag。步骤六逆向计算Flag已知变换输入[i] 1 密文[i]。已知密文j!dpnf!up!tusjoh。计算输入输入[i] 密文[i] - 1。手动或写个简单脚本计算cipher j!dpnf!up!tusjoh plain .join(chr(ord(c) - 1) for c in cipher) print(plain) # 输出i could be string验证运行程序在输入框中输入i could be string点击“Check”。成功弹出消息框显示Success! Flag is: flag{you_found_it}。至此我们成功找到了Flagflag{you_found_it}。7. 常见问题与排查技巧实录在实际操作中你肯定会遇到各种各样的问题。这里记录了一些典型场景和解决思路。7.1 工具使用类问题问题1Ghidra分析时反编译窗口显示“Unable to decompile”或伪代码非常混乱。可能原因Ghidra没有正确识别程序的架构或入口点或者程序代码被混淆。解决思路检查创建项目时选择的语言/编译器是否合适。对于Windows Delphi程序通常选择x86:default:windows。在Window - Memory Map中确认所有代码段.text都有正确的执行权限。尝试让Ghidra重新分析Analysis - Auto Analyze...调整分析选项。如果反编译结果依然很差可以主要依赖汇编代码窗口进行分析或者尝试使用IDA Pro免费版。问题2x64dbg附加或运行程序时程序崩溃或无法中断。可能原因程序有反调试检测或者调试器设置问题。解决思路以管理员身份运行x64dbg。在x64dbg选项Options - Preferences中尝试禁用一些高级调试功能或启用“隐藏调试器”相关插件如ScyllaHide。尝试使用F9运行后再按F12暂停然后查看调用栈手动寻找可能的关键函数。对于简单的入门题反调试可能性较低优先检查是否为管理员权限问题。7.2 分析过程类问题问题3在字符串列表里找不到任何可疑字符串。可能原因字符串被加密或混淆了Flag可能是动态生成的或者字符串存储在不常见的段中。解决思路动态获取在程序运行起来并输入内容后在x64dbg中使用内存搜索功能CtrlB搜索可能出现的Flag片段如flag{。关注代码逻辑放弃直接找字符串专注于分析验证函数。比较的双方可能都不是明文字符串而是经过计算后的哈希值如MD5或数值。你需要识别出加密/哈希函数。查找资源段使用Resource Hacker等工具查看程序的资源段.rsrc有时Flag会藏在图标、对话框资源里。问题4IDR无法分析程序提示错误或显示为空。可能原因程序版本太新或太旧与当前IDR版本不兼容或者程序被额外处理过。解决思路尝试寻找更新或更旧版本的IDR。放弃IDR直接使用Ghidra/IDA进行字符串搜索和函数分析。可以搜索Delphi特有的函数名如Button1Click、Forms.TForm1等来定位。使用PE-bear或CFF Explorer等PE编辑器查看程序的导入表Import Table寻找Delphi核心库如vclXX.bpl、rtlXX.bpl中的函数手动推测。问题5知道是比较但看不懂比较前后的加密算法。可能原因算法稍复杂或者使用了不常见的运算。解决思路动态跟踪在x64dbg中在获取输入和比较函数之间设断点单步跟踪记录每一步对输入数据的操作。把内存中数据的变化记下来。化繁为简尝试输入有规律的内容如aaaa、abcd、1234观察输出变成了什么。通过对比输入输出往往能猜出算法例如每个字符1或者与固定值异或。识别常量在反编译代码中注意那些神秘的常数如0x9E3779B9是TEA算法的特征常量它们可能是识别标准算法的线索。写脚本模拟一旦猜出算法用Python写一个小脚本模拟加密过程与程序内置的密文对比验证你的猜想。7.3 通用技巧与心得保持耐心与记录逆向是一个反复尝试和推理的过程。用好记事本或笔记软件记录下每一个可疑的地址、字符串、函数名和你的猜想。善用搜索引擎遇到不认识的函数名、常量、错误信息直接搜索。很多CTF题目的解法或关键点都有人讨论过。从简单到复杂严格按照“查壳 - 脱壳 - 字符串搜索 - 定位关键函数 - 静态分析 - 动态调试”的流程来。不要一上来就扎进汇编代码。理解而非死记记住工具的操作步骤很重要但更重要的是理解每一步背后的原理比如为什么要在OEP处Dump。这样遇到变种题目时你才能灵活应对。利用社区Pikachu等靶场通常有Writeup解题报告。在自己努力尝试之后再去对照Writeup学习别人的思路是进步最快的方法。逆向工程就像解谜每一次成功找到Flag都是一次正反馈。从UPX和Delphi这类结构清晰的目标开始能让你快速建立起基本的方法论和信心。当你熟练掌握了这套流程再面对更复杂的壳、更混淆的代码时你就有了一套可靠的“组合拳”来拆解它们。记住核心永远是理解程序要做什么然后找到它做决定的地方。剩下的就是耐心和细心。