news 2026/7/30 5:29:30

【系列:CCG Crypto CrackMe 逆向全解析 · 第 1 篇】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【系列:CCG Crypto CrackMe 逆向全解析 · 第 1 篇】

导读:2001 年,CCG(China Cracking Group)的 Blowfish 放出了一个 CrackMe,声称"本题多解"。这个系列将完整记录破解它的全过程——从最基础的字节解析,到自定义壳的算法复原,到最后跑出一个能通过真实程序验证的 Keygen。系列的第一条原则很朴素:任何结论都必须能用代码复现,本系列都是围绕破解过程中一步步推导过程来写的。第一篇,我们先不谈算法,只把手里这个 123,904 字节的文件,用最笨的方法过一遍。

逆向分析的第一步,不是猜壳猜算法,而是把文件当成一堆确定的字节。这篇文章不做任何推测,只用 Python 逐字段解析一个 crackme 样本的 PE 结构,每个数字都附带可运行的代码,看完你也能自己验证一遍。

很多逆向教程一上来就说"这是 UPX 壳"“这是自定义加密”。

但这些判断的依据是什么?大多数时候,答案是经验和直觉,不是当场验证的事实。

这篇文章想做一件更基础的事:打开crackme_crypto.exe,只用structopen,把能确定的东西一条条读出来。不下结论,只呈现数字。

文件大小从来不是"大概"

网上流传的说法是这个样本"大约124KB"。用代码量一下就知道,这种四舍五入的说法从一开始就是错的。

importos path='crackme_crypto.exe'size=os.path.getsize(path)print(size)# 123904

123904 字节,不是 124000,也不是 126976(124×1024)。逆向工作里,"大约"没有意义,只有精确的字节数才能拿来做后续比对。

PE 头的位置不用猜,文件里写着

DOS 头的第 0x3C 字节处,存的就是 PE 头的偏移量e_lfanew。这是 PE 格式规定死的,不需要经验判断。

importstructwithopen(path,'rb')asf:data=f.read()e_lfanew=struct.unpack('<I',data[0x3C:0x40])[0]print(hex(e_lfanew))# 0xe0pe_sig=data[e_lfanew:e_lfanew+4]print(pe_sig)# b'PE\x00\x00'

e_lfanew = 0xE0,跳到这个偏移,四个字节正好是PE\0\0。这两个值互相印证:偏移对了,签名也对了,说明这是一个结构完整的 PE 文件,而不是拼接出来的伪装文件。

COFF 头里的三个数字,决定了后面怎么读

PE 签名之后紧跟的 20 字节是 COFF 文件头,里面的MachineNumberOfSectionsSizeOfOptionalHeader三个字段,直接决定了后续解析要怎么走。

coff_offset=e_lfanew+4machine,num_sections=struct.unpack('<HH',data[coff_offset:coff_offset+4])size_opt_header=struct.unpack('<H',data[coff_offset+16:coff_offset+18])[0]print(hex(machine))# 0x14cprint(num_sections)# 6print(hex(size_opt_header))# 0xe0

Machine = 0x14C对应 32 位 x86 架构。NumberOfSections = 6告诉我们后面要读几个节区表条目。SizeOfOptionalHeader = 0xE0告诉我们可选头占多少字节,从而算出节区表从哪里开始。这三个数字不是背景信息,是接下来每一步计算的输入参数。

入口点:从 RVA 到实际虚拟地址,只差一次加法

可选头里最关心的是入口点。这里涉及两个数字:AddressOfEntryPoint(相对虚拟地址)和ImageBase(镶像基址),两者相加才是程序运行时真正跳转到的地址。

opt_offset=coff_offset+20magic=struct.unpack('<H',data[opt_offset:opt_offset+2])[0]entry_rva=struct.unpack('<I',data[opt_offset+16:opt_offset+20])[0]image_base=struct.unpack('<I',data[opt_offset+28:opt_offset+32])[0]entry_va=image_base+entry_rvaprint(hex(magic))# 0x10bprint(hex(entry_rva))# 0x44bd6print(hex(image_base))# 0x400000print(hex(entry_va))# 0x444bd6

Magic = 0x10B说明这是 PE32(32位)格式,不是 PE32+。ImageBase = 0x400000是默认加载基址。两者相加,AddressOfEntryPoint的 RVA0x44BD6换算成实际虚拟地址就是0x444BD6

这个地址意味着什么,现在先不下结论。它只是一个确定的数字,后续调试器里下断点会用到。

六个节区,六组数字,先只记录

节区表紧跟在可选头之后,起始偏移是opt_offset + size_opt_header。每个节区头固定 40 字节,按顺序读就行。

sec_table_offset=opt_offset+size_opt_headerforiinrange(num_sections):off=sec_table_offset+i*40name=data[off:off+8].rstrip(b'\x00')virtual_size,virtual_address=struct.unpack('<II',data[off+8:off+16])size_raw,ptr_raw=struct.unpack('<II',data[off+16:off+24])print(name,hex(virtual_address),hex(virtual_size),hex(size_raw),hex(ptr_raw))

跑出来是六组固定的数字:

节区名称VirtualAddressVirtualSizeSizeOfRawDataPointerToRawData
0UPX!0x10000x120000xA4000x1000
1UPX!0x130000x10000x8000xB400
2UPX!0x140000xA0000x6000xBC00
3UPX!0x1E0000x20000x6000xC200
4.rsrc0x200000x230000xF2000xC800
5UPX!0x430000x30000x2A000x1BA00

注意一个细节:五个节区的名字字面就是UPX!四个字符,第 4 个节区叫.rsrc。同时,VirtualSizeSizeOfRawData之间的差距,在几个节区里相当大。

这些名字和数字本身不能证明任何事——名字字段可以被任意改写,大小差异也可能有多种原因。它们只是文件里当场读出来的字符串和整数。这组数字要说明什么,留到下一篇。

文件末尾的六个字节

节区数据之外,文件还有个容易被忽略的角落——最后几个字节。

tail=data[-6:]print(tail)# b'BF2000'print(tail.decode())# BF2000

文件末尾的最后 6 个字节,解码成 ASCII 正好是BF2000这六个字符。它出现在文件的最末尾,不属于任何节区的正常数据范围。

这六个字符是什么含义,为什么会出现在这个位置,这篇不做推测。它是下一篇要处理的第一个问题。

先把地基打稳

回顾一下这次拿到的确定信息:文件大小 123904 字节,PE 头偏移0xE0,架构0x14C,6 个节区,入口点实际地址0x444BD6。这些都是当场用代码验证过的事实,不依赖任何转述。

节区表里五个UPX!名称和一个.rsrc,还有文件末尾那六个字符BF2000,这两组线索先记下来,不着急下结论。

逆向的第一原则很简单:先把能用代码确认的东西全部确认完,再去谈假设和推理。你在分析文件时,通常是先看结构还是先跑一遍再说?


小结

本文是CCG Crypto CrackMe 逆向全解析系列的开篇,只做了一件事:把crackme_crypto.exe的 PE 结构逐字段解析出来,不下任何算法结论。

  • 文件大小精确为123,904 字节(而不是网上常见的"约124KB")
  • PE 头、COFF 头、Optional 头的关键字段全部现场验证:Machine=0x14CNumberOfSections=6、入口点实际地址0x444BD6
  • 6 个节区表条目全部逐字段读出,5 个命名为UPX!,1 个为.rsrc
  • 文件末尾 6 字节解码为BF2000

这些都只是事实,还不是结论。5 个UPX!节区是否真的是 UPX 压缩?BF2000这 6 个字符意味着什么?下一篇开始回答。

下一篇预告

《字符串侦察:从 find() 命中到编译器身份识别》——在不碰任何反汇编工具的前提下,我们将展示如何通过字符串搜索和上下文分析,从这个文件里挖出编译器遗留的源文件路径,直接锁定程序使用的密码学算法组件。

参考文献与引用

  • Microsoft PE/COFF 格式官方规范:learn.microsoft.com/windows/win32/debug/pe-format——本文所有字段偏移量和结构定义均依据此规范
  • pefile — Python PE 解析库:[github.com/erocarrera/pefile](https://github.com/erocarrera/pefile

觉得有用?点个关注,持续获取优质内容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 5:28:41

本地代码大模型评测实战(五):公平对比的5个陷阱

模型评测方法论&#xff1a;公平对比的5个陷阱 系列目录 篇1: 模型选型 篇2: 评测框架 篇3: 数据挖掘的13个发现 篇4: 8个坑和1个崩溃 篇5: 公平对比的5个陷阱 ← 当前 你以为在选模型&#xff0c;其实在选评测方法。方法论的偏差&#xff0c;比模型之间的差距更大。 榜单第一名…

作者头像 李华
网站建设 2026/7/30 5:22:10

庞加莱回归定理:宇宙循环的数学基础与物理意义

你是否曾想过&#xff0c;如果时间足够长&#xff0c;宇宙中的一切会不会精确地重复发生&#xff1f;你此刻阅读这篇文章的瞬间&#xff0c;会不会在未来的某个时刻再次出现&#xff1f;这个看似科幻的问题&#xff0c;其实在数学和物理学中有一个严谨的理论基础——庞加莱回归…

作者头像 李华
网站建设 2026/7/30 5:10:18

Altium Designer Gerber文件生成全流程详解与实战技巧

1. 项目概述&#xff1a;为什么Gerber文件是PCB制造的“普通话”在电子硬件开发领域&#xff0c;完成一块PCB的设计只是万里长征的第一步。当你把精心绘制的.PcbDoc文件发给板厂&#xff0c;得到的回复很可能是&#xff1a;“请提供Gerber文件。”对于很多刚接触Altium Designe…

作者头像 李华
网站建设 2026/7/30 5:08:05

STM32多通道ADC采集实战:从DMA驱动到软件滤波的嵌入式开发指南

1. 项目概述&#xff1a;从“点”到“面”的模拟世界感知在嵌入式开发&#xff0c;尤其是基于STM32这类MCU的项目中&#xff0c;我们常常需要与真实的物理世界打交道。温度、压力、光照、电压……这些连续变化的模拟量&#xff0c;是MCU理解外部环境的关键。而ADC&#xff08;模…

作者头像 李华
网站建设 2026/7/30 5:07:44

Quantum ESPRESSO ph.x 输入文件避坑指南:单q点声子计算实战

1. 项目概述&#xff1a;声子谱计算中的“临门一脚” 在凝聚态物理和材料计算领域&#xff0c;声子谱的计算是理解材料晶格动力学性质、预测热学行为乃至判断结构稳定性的核心手段。对于使用Quantum ESPRESSO&#xff08;QE&#xff09;这套开源第一性原理计算软件包的从业者来…

作者头像 李华