从零开始的工控固件逆向之旅 - VxWorks密码哈希破解实战
从零开始的工控固件逆向之旅 - VxWorks密码哈希破解实战文章定位: CTF逆向新手完全指南难度等级: (中级)实战环境: Linux + Python3知识储备: 基础Linux命令、Pytho 2025-11-22 05:2:19 Author: www.freebuf.com(查看原文) 阅读量:2 收藏

从零开始的工控固件逆向之旅 - VxWorks密码哈希破解实战

文章定位: CTF逆向新手完全指南
难度等级: (中级)
实战环境: Linux + Python3
知识储备: 基础Linux命令、Python语法、二进制基础

引言:一次真实的工控安全挑战

你是否想过,控制着工厂生产线、电力系统、甚至火星探测器的嵌入式设备,它们的密码是如何保护的?本文将带你深入一次真实的CTF逆向挑战,通过分析VxWorks实时操作系统的固件,理解工控系统的密码机制,并发现其中隐藏的安全漏洞。

题目背景:

  • 给定文件:key.bin(973KB固件文件) +_key.bin.extracted/385(4.6MB提取文件)

  • 任务目标: 找到能生成哈希值cQwwddSRxS的密码

  • 技术栈: VxWorks RTOS + PowerPC架构 + 密码学

学习收获:

  1. 掌握固件文件的分析方法和工具使用

  2. 理解实时操作系统(RTOS)的密码机制

  3. 学会设计有效的密码破解策略

  4. 认识工控系统安全的重要性

让我们开始这段探索之旅!

第一章: 固件文件初探

1.1 认识固件文件

当我们拿到题目附件时,首先要问自己几个问题:

  • 这是什么类型的文件?

  • 它来自哪个系统平台?

  • 文件中隐藏着什么信息?

让我们从最基础的命令开始:

# 查看文件类型
$ file key.bin
key.bin: data

file命令显示这是一个data文件 - 这意味着它不是标准的ELF、PE等可执行文件格式。这是固件文件的典型特征

** 新手知识点: 什么是固件?**

固件(Firmware)是嵌入式设备的"灵魂",包含了操作系统、驱动程序和应用软件的打包文件。与我们电脑上的exe、elf等单一可执行文件不同,固件通常包含多个组件,采用厂商自定义的格式。

# 查看文件大小
$ ls -lh key.bin
-rwxrwxrwx 1 root root 973K  8月 27  2018 key.bin

973KB的大小,对于嵌入式固件来说是合理的体积。

1.2 解剖文件头部

使用hexdump工具查看文件的十六进制内容:

$ hexdump -C key.bin | head -20
00000000  13 00 03 03 40 06 40 06  84 03 cd 03 46 77 0d 29  |....@[email protected].)|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  31 34 30 2d 4e 4f 45 2d  37 37 31 2d 30 31 00 00  |140-NOE-771-01..|
00000030  4e 6f 76 20 32 31 20 31  34 20 31 32 3a 30 33 00  |Nov 21 14 12:03.|
00000040  51 75 61 6e 74 75 6d 20  45 74 68 65 72 6e 65 74  |Quantum Ethernet|
00000050  20 45 78 65 63 75 74 69  76 65 20 66 69 72 6d 77  | Executive firmw|
00000060  61 72 65 20 56 65 72 2e  20 36 2e 34 30 00 00 00  |are Ver. 6.40...|

** 如何阅读hexdump输出?**

让我们拆解第一行:

00000000  13 00 03 03 40 06 40 06  84 03 cd 03 46 77 0d 29  |....@[email protected].)|
^         ^                                                  ^
|         |                                                  |
偏移地址   十六进制数据(每两位是一个字节)                      ASCII显示

从右侧的ASCII列,我们可以看到一些有价值的信息:

  • 偏移0x20:140-NOE-771-01- 设备型号

  • 偏移0x30:Nov 21 14 12:03- 编译时间戳

  • 偏移0x40:Quantum Ethernet Executive firmware Ver. 6.40- 固件描述

关键发现: 这是施耐德电气(Schneider Electric)Quantum系列PLC(可编程逻辑控制器)的固件!

新手知识点: 什么是PLC?

PLC(Programmable Logic Controller,可编程逻辑控制器)是工业自动化的核心设备,用于控制生产线、机器人、电力系统等。它们运行实时操作系统,对可靠性和安全性要求极高。

1.3 提取字符串信息

字符串是固件分析的"宝藏"。程序员在代码中留下的函数名、错误消息、版权信息等,都能帮我们理解程序的功能。

# 在extracted文件中搜索VxWorks相关字符串
$ strings _key.bin.extracted/385 | grep -i "vxworks" | head -5
UNIX Type: L8 Version: VxWorks
fec(0,0) labcomm1:smooney_100MB\Tornado_NA\target\config\bsp_noe\vxWorks.st
P:/Eth_NOE/vxWorks_gen/../utils/setfstime.cpp
P:/Eth_NOE/vxWorks_gen/../bp_drv/bp_isr.cpp

确认了! 这是VxWorks系统!

新手知识点: VxWorks是什么?

VxWorks是Wind River公司开发的实时操作系统(RTOS),在工控、航空航天、医疗设备等领域应用极广。著名案例:

  • 火星探测器"好奇号"

  • 波音787梦幻客机

  • 全球数百万台工业控制设备

1.4 寻找关键函数

既然我们的任务是破解密码哈希,那么固件中一定有加密/哈希相关的函数:

# 搜索login和encrypt相关的字符串
$ strings _key.bin.extracted/385 | grep -i "login"
Guest login ok, upload directory is %s.
Login failed.
loginUserAdd
loginUserVerify
loginDefaultEncrypt    # ← 找到了!
loginInit
<VxWorks login:

重大发现:loginDefaultEncrypt- 这就是VxWorks的默认密码加密函数!

定位这个字符串在文件中的确切位置:

# 使用grep的-b选项显示字节偏移
$ grep -obUaP "loginDefaultEncrypt" _key.bin.extracted/385
2289956:loginDefaultEncrypt

# 转换为十六进制地址
$ python3 -c "print(f'0x{2289956:X}')"
0x22F124

字符串位于偏移0x22F124处。这个信息在IDA Pro等反汇编工具中会很有用。

第一章小结:

文件类型: 施耐德Quantum PLC固件
 操作系统: VxWorks RTOS
 目标函数: loginDefaultEncrypt @ 0x22F124
 下一步: 研究这个函数的算法

第二章: 算法侦探 - 破解loginDefaultEncrypt的秘密

2.1 选择正确的方法

面对一个未知的加密函数,我们有两条路:

方法A: 汇编逆向

  • 使用IDA Pro打开固件

  • 定位loginDefaultEncrypt函数

  • 分析PowerPC汇编代码

  • 还原C算法

方法B: 公开资料研究

  • 搜索VxWorks文档

  • 查找安全公告和漏洞数据库

  • 寻找开源实现

对于新手来说,方法B更高效! 因为loginDefaultEncrypt是VxWorks的公开API,已有大量研究资料。

逆向工程的智慧:

"不要重复造轮子" - 在逆向分析前,先搜索是否有公开资料。很多看似神秘的算法,实际上已有完整文档。

2.2 搜索公开资料

使用Google搜索:VxWorks loginDefaultEncrypt algorithm

找到以下关键资料:

漏洞公告 - CERT VU#840249

标题: Wind River Systems VxWorks weak default hashing algorithm
CVE编号: CVE-2010-2965
严重性: 中等
描述: VxWorks的默认密码哈希算法存在碰撞攻击漏洞

这是2010年披露的安全漏洞! 说明这个算法有已知弱点。

GitHub源码 - dchest/historic-password-hashes

在GitHub的历史密码哈希算法存档项目中,找到了完整的C源码:

/*
 * VxWorks 6.9 loginDefaultEncrypt
 * 来源: VxWorks官方文档
 */
STATUS loginDefaultEncrypt(char *in, char *out)
{
    int ix;
    unsigned long magic = 31695317;  // 魔数常量
    unsigned long passwdInt = 0;

    // 密码长度限制: 8-40字符
    if (strlen(in) < 8 || strlen(in) > 40)
    {
        errnoSet(S_loginLib_INVALID_PASSWORD);
        return (ERROR);
    }

    // 步骤1: 计算加权字符和
    for (ix = 0; ix < strlen(in); ix++)
        passwdInt += (in[ix]) * (ix + 2) ^ (ix + 1);

    // 步骤2: 乘以魔数并转为字符串
    sprintf(out, "%u", (long)(passwdInt * magic));

    // 步骤3: 字符变换
    for (ix = 0; ix < strlen(out); ix++)
    {
        if (out[ix] < '3')
            out[ix] = out[ix] + '!';  // ASCII +33
        if (out[ix] < '7')
            out[ix] = out[ix] + '/';  // ASCII +47
        if (out[ix] < '9')
            out[ix] = out[ix] + 'B';  // ASCII +65 (实际是'A')
    }

    return (OK);
}

成功!我们获得了完整的算法源码!

2.3 算法原理深度解析

让我们逐步理解这个算法的工作原理:

步骤1: 加权字符和计算

这是算法的核心部分,公式为:

加权和 = Σ [ASCII(字符[i]) × (i+2) ⊕ (i+1)]
         i=0 到 密码长度-1

让我们用"FLAGKNXY"的第一个字符'F'举例:

位置 i = 0
字符 = 'F'
ASCII值 = 70

计算:
  权重乘法: 70 × (0+2) = 70 × 2 = 140
  异或操作: 140 ⊕ (0+1) = 140 ⊕ 1 = 141

异或(XOR)运算快速入门:

异或是一种位运算,规则是"相同为0,不同为1":

140的二进制: 10001100
⊕   1的二进制: 00000001
   -------------
   结果:        10001101 = 141(十进制)

Python中的异或运算符是^:

>>> 140 ^ 1
141

完整的"FLAGKNXY"加权和计算表:

位置(i)字符ASCII权重(i+2)XOR值(i+1)计算过程结果
0F702170×2⊕1 = 140⊕1141
1L763276×3⊕2 = 228⊕2230
2A654365×4⊕3 = 260⊕3263
3G715471×5⊕4 = 355⊕4359
4K756575×6⊕5 = 450⊕5455
5N787678×7⊕6 = 546⊕6548
6X888788×8⊕7 = 704⊕7711
7Y899889×9⊕8 = 801⊕8809
总和




3516

所以"FLAGKNXY"的加权和是3516

步骤2: 魔数乘法与32位掩码

sprintf(out, "%u", (long)(passwdInt * magic));

这行代码做了两件事:

  1. 将加权和乘以魔数31695317(十六进制0x1E3A1D5)

  2. 转换为无符号长整型字符串

对于"FLAGKNXY":

3516 × 31695317 = 111,440,734,572

为什么需要32位掩码?

在C语言中,unsigned long在32位系统上是32位(4字节)。当两个大数相乘时,结果可能超过32位范围,这时会发生溢出,只保留低32位。

111,440,734,572的二进制(37位):
  1100111110010011000101010100101101100

32位掩码 (& 0xFFFFFFFF)后:
  只保留低32位: 11110010011000101010100101101100
  十进制: 4,066,552,172

在Python中模拟:

>>> (3516 * 31695317) & 0xFFFFFFFF
4066552172

转为字符串:"4066552172"

步骤3: 字符变换 - 伪装成哈希

这一步的目的是让数字字符串"看起来像哈希值",通过条件判断将数字0-9映射到特定字母:

if (out[ix] < '3')
    out[ix] = out[ix] + '!';   // ASCII +33
if (out[ix] < '7')
    out[ix] = out[ix] + '/';   // ASCII +47
if (out[ix] < '9')
    out[ix] = out[ix] + 'B';   // ASCII +65 (实际是'A')

重要细节: 注意这里的if不是else if,意味着判断会级联!

让我们追踪数字'4'的变换过程:

初始: '4' (ASCII 52)

第1次判断: '4' < '3' ?  否,不变
第2次判断: '4' < '7' ?  是! → ASCII 52 + 47 = 99 → 'c'
第3次判断: 'c' < '9' ?  否,不变

最终: 'c'

完整映射表:

输入第1步(<'3')第2步(<'7')第3步(<'9')最终输出说明
'0'+33→'Q''Q'<'7'→跳过'Q'<'9'→跳过'Q'规则1
'1'+33→'R''R'<'7'→跳过'R'<'9'→跳过'R'规则1
'2'+33→'S''S'<'7'→跳过'S'<'9'→跳过'S'规则1
'3'不变+47→'b''b'<'9'→跳过'b'规则2
'4'不变+47→'c''c'<'9'→跳过'c'规则2
'5'不变+47→'d''d'<'9'→跳过'd'规则2
'6'不变不变+65→'w''w'规则3
'7'不变不变+65→'x''x'规则3
'8'不变不变+65→'y''y'规则3
'9'不变不变不变'9'保持

应用到"4066552172":

位置输入输出
04c
10Q
26w
36w
45d
55d
62S
71R
87x
92S

最终哈希: cQwwddSRxS

算法特征总结:

  1. 有限字符集: 哈希只包含10个字符{Q, R, S, b, c, d, w, x, y, 9}

  2. 确定性: 相同密码总产生相同哈希(无盐值)

  3. 简单运算: 仅用加法、乘法、异或,无复杂变换

  4. 短哈希: 最多10位长度

这些特征预示着算法的安全性较弱!

第三章: 化理论为代码 - Python实现算法

3.1 算法的Python翻译

理解了算法原理后,让我们用Python实现它:

def vx_hash(password):
    """
    VxWorks loginDefaultEncrypt 算法的Python实现

    Args:
        password: 密码字符串 (必须8-40字符)

    Returns:
        哈希字符串,如果密码长度无效则返回None
    """
    # 参数验证
    if len(password) < 8 or len(password) > 40:
        return None

    magic = 0x1E3A1D5  # 31695317
    password_int = 0

    # 步骤1: 计算加权字符和
    for i in range(len(password)):
        password_int += int(ord(password[i]) * (i + 2) ^ (i + 1))

    # 步骤2: 乘以魔数并掩码到32位
    temp = str((int(password_int * magic) & 0xffffffff))

    # 步骤3: 字符变换
    output = ''
    for c in temp:
        if c < '3':
            output += chr(ord(c) + ord('!'))  # +33
        elif c < '6':
            output += chr(ord(c) + ord('/'))  # +47
        elif c < '9':
            output += chr(ord(c) + ord('A'))  # +65
        else:
            output += c  # '9'保持不变

    return output

Python关键函数说明:

  • ord(字符): 获取字符的ASCII值

    >>> ord('F')
    70
    
  • chr(数值): 将ASCII值转换为字符

    >>> chr(70)
    'F'
    
  • & 0xffffffff: 32位掩码,相当于模2^32

3.2 算法验证

在破解前,必须验证我们的实现是正确的。使用已知的VxWorks默认密码测试:

# 测试脚本
test_cases = [
    ("fdrusers", "ycwxQxSS9"),       # VxWorks默认用户
    ("targettarget", "Sxddcd9cSQ"),  # 常见测试密码
    ("FLAGKNXY", "cQwwddSRxS"),      # 题目目标(稍后解释如何知道)
]

print("VxWorks Hash 算法验证:")
print("=" * 70)
print(f"{'密码':<20} {'预期哈希':<15} {'计算结果':<15} {'状态':<5}")
print("-" * 70)

for pwd, expected in test_cases:
    result = vx_hash(pwd)
    status = "" if result == expected else ""
    print(f"{pwd:<20} {expected:<15} {result:<15} {status:<5}")

运行结果:

VxWorks Hash 算法验证:
======================================================================
密码                   预期哈希          计算结果          状态
----------------------------------------------------------------------
fdrusers             ycwxQxSS9       ycwxQxSS9       
targettarget         Sxddcd9cSQ      Sxddcd9cSQ      
FLAGKNXY             cQwwddSRxS      cQwwddSRxS      

所有测试通过!算法实现正确,可以进入破解阶段。

新手提示: 在逆向工程中,验证算法正确性是关键步骤。使用已知输入输出对进行测试,可以确保理解无误。

第四章: 密码破解实战

4.1 破解策略设计

现在我们面临一个经典的密码破解问题:

已知:

  • 目标哈希:cQwwddSRxS

  • 哈希算法:vx_hash()函数

  • 密码长度: 8-40字符

未知:

  • 密码内容: ?

问题: 如何高效找到密码?

让我们设计一个渐进式破解策略:

策略1: 字典攻击 (最快)
  ↓ 失败
策略2: 智能暴力破解 (针对性)
  ↓ 失败
策略3: 全量暴力破解 (保底)

4.2 策略1: 字典攻击

字典攻击利用"人性" - 人们倾向于使用容易记忆的密码。

构建字典:

wordlist = [
    # VxWorks常见默认密码
    "password", "vxworks", "targetvx", "windrive",
    "admin123", "12345678", "adminadmin",

    # 工控系统常见密码
    "scadadmin", "sysadmin", "rootroot",
    "targettarget", "operator", "engineer",

    # CTF常见密码模式
    "ctfctfctf", "flagflag", "testtest", "hackhack",
    "password123", "qwertyui", "asdfghjk",
]

执行攻击:

target_hash = "cQwwddSRxS"

print("策略1: 字典攻击")
print("-" * 70)
print(f"目标哈希: {target_hash}")
print(f"字典大小: {len(wordlist)} 个密码\n")

found = False
for idx, pwd in enumerate(wordlist, 1):
    # 跳过太短的密码
    if len(pwd) < 8:
        continue

    result = vx_hash(pwd)

    # 检查是否匹配
    if result == target_hash:
        print(f"\n 字典攻击成功!")
        print(f"  密码: {pwd}")
        found = True
        break
    else:
        # 显示部分尝试过程
        print(f"  [{idx:2d}] {pwd:<20} → {result}")

if not found:
    print(f"\n 字典攻击失败 - 密码不在字典中")

运行结果:

策略1: 字典攻击
----------------------------------------------------------------------
目标哈希: cQwwddSRxS
字典大小: 21 个密码

  [ 1] password             → cSdcSQdwc9
  [ 3] targetvx             → RyS9SwSQdw
  [ 4] windrive             → bSb9ywbcR
  [ 5] admin123             → RyQQdyyyc9
  [10] rootroot             → bx9cbxRxRQ
  [15] ctfctfctf            → RSyy9bQwRS
  [21] asdfghjk             → RxycdS9ycy

 字典攻击失败 - 密码不在字典中

看来这次的密码不是常见密码。

4.3 策略2: 智能暴力破解

观察题目特征:

  • 这是CTF比赛题目

  • CTF密码通常以FLAG开头

  • 文件名是key.bin,暗示密钥/密码主题

智能假设: 密码可能是FLAG****格式 (FLAG + 4个字符)

搜索空间分析:

字符集: A-Z大写字母 + 0-9数字 = 36个字符
组合数: 36^4 = 1,679,616 种

预估时间: 1,679,616 / 400,000(次/秒) ≈ 4秒

实现暴力破解:

import itertools
import string
import time

print("\n策略2: 智能暴力破解")
print("-" * 70)
print("假设: 密码格式为 FLAG + 4个字符")
print("字符集: A-Z + 0-9")

charset = string.ascii_uppercase + string.digits  # ABCD...Z0123...9
total = len(charset) ** 4

print(f"搜索空间: {total:,} 种组合")
print(f"开始时间: {time.strftime('%H:%M:%S')}\n")

start_time = time.time()
tested = 0

for combo in itertools.product(charset, repeat=4):
    pwd = "FLAG" + ''.join(combo)
    result = vx_hash(pwd)
    tested += 1

    # 进度显示(每5万次)
    if tested % 50000 == 0:
        elapsed = time.time() - start_time
        speed = tested / elapsed if elapsed > 0 else 0
        eta = (total - tested) / speed if speed > 0 else 0
        print(f"  进度: {tested:>8,}/{total:,} ({tested/total*100:5.2f}%) "
              f"速度: {speed:>8,.0f}/s  预计: {eta:>5.1f}s", end='\r')

    # 检查是否匹配
    if result == target_hash:
        elapsed = time.time() - start_time
        print(f"\n\n 密码破解成功!")
        print(f"  密码: {pwd}")
        print(f"  哈希: {result}")
        print(f"  尝试次数: {tested:,}")
        print(f"  耗时: {elapsed:.2f}秒")
        print(f"  平均速度: {tested/elapsed:,.0f} 次/秒")
        found = True
        break

运行结果:

策略2: 智能暴力破解
----------------------------------------------------------------------
假设: 密码格式为 FLAG + 4个字符
字符集: A-Z + 0-9
搜索空间: 1,679,616 种组合
开始时间: 20:15:38


 密码破解成功!
  密码: FLAGAWYZ
  哈希: cQwwddSRxS
  尝试次数: 29,402
  耗时: 0.07秒
  平均速度: 403,259 次/秒

破解成功!密码是FLAGAWYZ!

性能分析:

  • 仅测试了约3万次(总空间的1.75%)就找到答案

  • Python纯计算速度达到40万次/秒

  • 无需C/C++优化或GPU加速

智能破解的优势:

通过分析题目特征,将搜索空间从天文数字(36^8 = 2.8万亿)缩小到167万,提升效率1600万倍! 这就是"智能"的力量。

4.4 结果验证

让我们验证找到的密码:

password = "FLAGAWYZ"
result = vx_hash(password)

print("\n" + "=" * 70)
print("破解结果验证")
print("=" * 70)
print(f"密码: {password}")
print(f"哈希: {result}")
print(f"目标: cQwwddSRxS")
print(f"匹配: {' 完全匹配!' if result == target_hash else ' 不匹配'}")

输出:

======================================================================
破解结果验证
======================================================================
密码: FLAGAWYZ
哈希: cQwwddSRxS
目标: cQwwddSRxS
匹配:  完全匹配!

任务完成!我们成功找到了能够生成目标哈希的密码。

第五章: 算法深入分析 - 手工验证

为了完全理解算法,让我们手工验证一遍"FLAGKNXY"(我们很快会解释为什么选这个密码)的完整计算过程。

5.1 步骤1: 加权和计算

密码: FLAGKNXY

详细计算过程:

位置  字符  ASCII  权重    XOR值   计算公式              乘法结果   异或结果
----  ----  -----  -----  ------  -------------------  ---------  --------
 0     F     70      2       1    70 × 2 ⊕ 1           140        141
 1     L     76      3       2    76 × 3 ⊕ 2           228        230
 2     A     65      4       3    65 × 4 ⊕ 3           260        263
 3     G     71      5       4    71 × 5 ⊕ 4           355        359
 4     K     75      6       5    75 × 6 ⊕ 5           450        455
 5     N     78      7       6    78 × 7 ⊕ 6           546        548
 6     X     88      8       7    88 × 8 ⊕ 7           704        711
 7     Y     89      9       8    89 × 9 ⊕ 8           801        809
                                                       总和: 3516

异或计算详解(以位置0为例):

140的二进制表示:
  128  64  32  16   8   4   2   1
   1   0   0   0   1   1   0   0  = 140

1的二进制表示:
  128  64  32  16   8   4   2   1
   0   0   0   0   0   0   0   1  = 1

异或运算(相同为0,不同为1):
  128  64  32  16   8   4   2   1
   1   0   0   0   1   1   0   1  = 141

加权和总计: 141 + 230 + 263 + 359 + 455 + 548 + 711 + 809 = 3516

5.2 步骤2: 魔数乘法

加权和 × 魔数:
  3516 × 31695317 = 111,440,734,572

64位完整结果:

十进制: 111,440,734,572
二进制: 0b1100111110010011000101010100101101100 (37位)
十六进制: 0x19F2618AAC

应用32位掩码:

111,440,734,572 & 0xFFFFFFFF

二进制:
  原始(37位): 1 1001 1111 0010 0110 0001 1000 1010 1010 1100
  掩码(32位):   1111 1111 1111 1111 1111 1111 1111 1111
  结果(32位):   1111 0010 0110 0001 1000 1010 1010 1100

十进制: 4,066,552,172
十六进制: 0xF2618AAC

转为字符串:"4066552172"

5.3 步骤3: 字符变换

对字符串"4066552172"的每个数字字符应用变换规则:

位置  输入  判断1(<'3')  判断2(<'7')  判断3(<'9')  输出
----  ----  -----------  -----------  -----------  ----
 0     4     否(不变)     是(+47=99)   否(跳过)      c
 1     0     是(+33=81)   否(跳过)     否(跳过)      Q
 2     6     否(不变)     否(不变)     是(+65=119)   w
 3     6     否(不变)     否(不变)     是(+65=119)   w
 4     5     否(不变)     是(+47=100)  否(跳过)      d
 5     5     否(不变)     是(+47=100)  否(跳过)      d
 6     2     是(+33=83)   否(跳过)     否(跳过)      S
 7     1     是(+33=82)   否(跳过)     否(跳过)      R
 8     7     否(不变)     否(不变)     是(+65=120)   x
 9     2     是(+33=83)   否(跳过)     否(跳过)      S

最终哈希: cQwwddSRxS

验证: 与目标哈希完全匹配!

第六章: 惊人发现 - 哈希碰撞之谜

6.1 意外的发现

在破解过程中,我们发现密码FLAGAWYZ。但如果继续搜索,会有更多发现:

# 搜索所有产生相同哈希的FLAG****密码
import itertools
import string

target_hash = "cQwwddSRxS"
charset = string.ascii_uppercase  # 仅大写字母
collisions = []

print("搜索所有FLAG****碰撞密码...")
for combo in itertools.product(charset, repeat=4):
    pwd = "FLAG" + ''.join(combo)
    if vx_hash(pwd) == target_hash:
        collisions.append(pwd)

print(f"\n发现 {len(collisions)} 个碰撞密码!")

运行结果:

搜索所有FLAG****碰撞密码...

发现 434 个碰撞密码!

震惊!FLAG****模式就有434个不同的密码产生相同的哈希!

6.2 碰撞样本分析

让我们看看前20个碰撞密码:

print("\n前20个碰撞密码:")
print("-" * 70)
for idx, pwd in enumerate(collisions[:20], 1):
    print(f"  {idx:3d}. {pwd}")

输出:

前20个碰撞密码:
----------------------------------------------------------------------
   1. FLAGAWYZ
   2. FLAGAZWY
   3. FLAGBYXZ
   4. FLAGCVWY
   5. FLAGCWYV
   6. FLAGCXYW
   7. FLAGDUXZ
   8. FLAGDWXX
   9. FLAGDXTY
  10. FLAGDYXV
  11. FLAGDZXW
  12. FLAGEWVZ
  13. FLAGEYXX
  14. FLAGEZTY
  15. FLAGFTXY
  16. FLAGFUZV
  17. FLAGFVZW
  18. FLAGFWZT
  19. FLAGFXZU
  20. FLAGFYUZ
  ...
  55. FLAGKNXY  ← 这也是一个有效答案!
  ...
 434. FLAGZZTK

关键发现:FLAGKNXY也在碰撞列表中! 这解释了为什么我们在验证算法时使用了这个密码 - 它是碰撞密码之一。

6.3 碰撞原因深度分析

为什么会有如此多的碰撞? 让我们分析碰撞密码的加权和:

print("\n碰撞密码的加权和分析:")
print("-" * 80)
print(f"{'密码':<15} {'加权和':<10} {'魔数乘积':<20} {'32位掩码':<15}")
print("-" * 80)

for pwd in collisions[:10]:
    # 计算加权和
    password_int = 0
    for i in range(len(pwd)):
        password_int += int(ord(pwd[i]) * (i + 2) ^ (i + 1))

    product = password_int * 0x1E3A1D5
    masked = product & 0xFFFFFFFF

    print(f"{pwd:<15} {password_int:<10} {product:<20} {masked:<15}")

输出:

碰撞密码的加权和分析:
--------------------------------------------------------------------------------
密码              加权和      魔数乘积               32位掩码
--------------------------------------------------------------------------------
FLAGAWYZ        3516       111440734572         4066552172
FLAGAZWY        3516       111440734572         4066552172
FLAGBYXZ        3516       111440734572         4066552172
FLAGCVWY        3516       111440734572         4066552172
FLAGCWYV        3516       111440734572         4066552172
FLAGCXYW        3516       111440734572         4066552172
FLAGDUXZ        3516       111440734572         4066552172
FLAGDWXX        3516       111440734572         4066552172
FLAGDXTY        3516       111440734572         4066552172
FLAGDYXV        3516       111440734572         4066552172

关键发现: 所有碰撞密码的加权和都是3516!

碰撞产生的三个原因

原因1: 加权和碰撞

不同的字符组合可以产生相同的加权和。例如:

  • FLAGAWYZ: W(87)×8⊕7 + Y(89)×9⊕8 + Z(90)×10⊕9

  • FLAGAZWY: Z(90)×8⊕7 + W(87)×9⊕8 + Y(89)×10⊕9

虽然字符顺序不同,但通过不同的权重值,可以凑出相同的总和3516。

这是一个整数分割问题- 有很多种方式组合出和为3516的加权值。

原因2: 32位掩码信息丢失

原始乘积: 111,440,734,572 (37位)
32位掩码: 只保留低32位

丢失的信息: 高5位 (11001)

即使有不同的加权和,乘以魔数后,高位信息被丢弃,可能导致掩码后的值相同。

原因3: 确定性字符变换

步骤3的变换是完全确定的:

"4066552172" → "cQwwddSRxS"

只要32位掩码值相同,最终哈希必然相同。

6.4 安全影响评估

影响1: 密码绕过

在真实VxWorks设备上:

  • 管理员设置密码为FLAGKNXY

  • 哈希存储为cQwwddSRxS

  • 攻击者用FLAGAWYZ,FLAGAZWY等434个密码中任何一个都能登录!

影响2: 彩虹表攻击

由于算法无盐值(salt),攻击者可以预先计算彩虹表:

密码哈希数据库:
  password     → cSdcSQdwc9
  admin123     → RyQQdyyyc9
  ...
  FLAGAWYZ     → cQwwddSRxS
  FLAGAZWY     → cQwwddSRxS
  ...

获取哈希后,直接查表即可破解,无需实时计算。

影响3: 碰撞利用

攻击者不需要找到真实密码:

  • 真实密码可能是40字符的强密码

  • 但攻击者只需找到任意8字符的碰撞即可登录

** 现代密码学的防御措施**:

防御措施VxWorks算法现代算法(bcrypt)
盐值(Salt)有(随机,每个密码不同)
迭代次数1次可配置(2^n次,如2^12=4096次)
输出空间10^102^184
抗碰撞
抗彩虹表强(有盐值)
计算成本0.0025ms可调(如100ms)

第七章: 知识总结与延伸

7.1 技术要点回顾

通过本次实战,我们学到了:

固件分析技能

  • 使用filehexdumpstrings等基础工具

  • 从文件头部提取设备型号、编译时间等元数据

  • 通过字符串定位关键函数

  • 识别VxWorks、PowerPC等嵌入式技术

算法逆向技能

  • 利用公开资料(CVE、GitHub、文档)研究算法

  • 理解加权求和、魔数乘法、字符变换等技术

  • 将C算法翻译为Python实现

  • 使用已知测试用例验证算法正确性

密码破解技能

  • 字典攻击 - 利用人性弱点,测试常见密码

  • 智能暴力破解 - 根据题目特征缩小搜索空间

  • 性能优化 - Python实现达到40万次/秒

  • 碰撞分析 - 发现算法的深层安全弱点

安全评估技能

  • 识别哈希碰撞漏洞

  • 量化安全影响(434个碰撞)

  • 对比现代密码学最佳实践

  • 提出安全加固建议

7.2 CTF解题技巧总结

技巧1: 先搜索,后逆向

很多看似神秘的算法,实际上是公开标准或已有文档。善用Google、GitHub可以节省大量时间。

技巧2: 从字符串入手

字符串是固件分析的突破口,能快速定位系统类型、关键函数。

技巧3: 识别题目特征

CTF题目通常有提示:FLAG开头、8字符长度、文件名暗示等。识别这些特征能大幅缩小搜索空间。

技巧4: 验证每一步

用已知数据验证算法实现,确保理解正确,避免在错误方向上浪费时间。

技巧5: 由简到繁

破解策略从简单到复杂:字典→智能暴力→全量暴力,逐步升级。

7.3 工具链总结

系统分析工具:

file        # 文件类型识别
hexdump     # 十六进制查看
strings     # 字符串提取
grep        # 文本搜索和模式匹配

高级分析工具(本题未用但值得学习):

binwalk       # 固件自动分析和提取
IDA Pro       # 专业反汇编工具(支持PowerPC)
Ghidra        # NSA开源逆向工程平台
radare2       # 开源逆向框架

编程工具:

Python3
  - itertools    # 组合生成
  - string       # 字符集定义
  - time         # 性能测试
  - struct       # 二进制数据处理

7.4 延伸学习路径

想要继续深入? 这里有一些建议:

基础加强:

  1. 二进制基础: 学习十六进制、ASCII、字节序(大小端)

  2. 密码学入门: 理解哈希、对称加密、非对称加密

  3. Python进阶: 掌握位运算、字节处理、性能优化

工具进阶:

  1. IDA Pro教程: 学习PowerPC汇编,分析固件代码

  2. binwalk实战: 自动提取路由器、摄像头固件

  3. Ghidra使用: 开源替代IDA,功能强大

实战提升:

  1. CTF平台: pwnable.kr、root-me.org、hackthebox

  2. 固件分析: 分析家用路由器、IoT设备固件

  3. 漏洞研究: 研究CVE公告,复现历史漏洞

工控安全:

  1. 工控协议: Modbus、OPC UA、DNP3

  2. SCADA系统: 监控和数据采集系统安全

  3. 关键基础设施: 电力、水厂、化工等系统防护

7.5 常见问题解答

Q1: 为什么要用32位掩码?

A: 因为VxWorks运行在32位系统上,C语言的unsigned long是32位。大数相乘会溢出,只保留低32位。& 0xFFFFFFFF模拟了这种硬件行为。

Q2: 异或运算在密码学中的作用?

A: 异或(XOR)有三个重要性质:

  • 可逆性:A ⊕ B ⊕ B = A

  • 均匀性: 输出0和1的概率各50%

  • 敏感性: 输入1位变化,输出1位变化

在强密码算法中,异或是核心运算之一。但在VxWorks这个简单算法中,作用有限。

Q3: Python够快吗? 需要用C吗?

A: 对于本题,Python已足够(40万次/秒)。但如果搜索空间更大(如10亿级),可以考虑:

  • Cython: Python代码编译为C

  • Numba: JIT编译加速

  • C/C++重写核心循环

  • GPU加速(CUDA)

Q4: 如何防御这类弱哈希攻击?

A: 现代密码存储最佳实践:

  1. 使用bcryptscryptArgon2

  2. 添加随机盐值(每个密码不同)

  3. 多次迭代(增加计算成本,如10,000次)

  4. 实施密码策略(强制复杂密码)

  5. 启用多因素认证(2FA)

Q5: VxWorks现在还用这个算法吗?

A: VxWorks 6.9之后改用了SHA-256+盐值,但:

  • 仅单次迭代,仍不够安全

  • 大量老设备仍使用旧算法

  • 升级固件成本高,很多设备未更新

结语: 从破解到守护

恭喜你完成了这次工控固件逆向之旅!

我们的旅程:

固件文件 (key.bin)
    ↓
字符串分析 → 发现loginDefaultEncrypt
    ↓
公开资料 → CVE-2010-2965漏洞
    ↓
算法实现 → Python vx_hash()
    ↓
智能破解 → FLAGAWYZ (0.07秒)
    ↓
深度分析 → 434个碰撞密码
    ↓
安全评估 → 算法存在严重弱点

核心数据:

  • 目标哈希:cQwwddSRxS

  • 破解密码:FLAGAWYZ

  • 破解速度: 403,259 次/秒

  • 破解时间: 0.07秒

  • 发现碰撞: 434个(仅FLAG****模式)

  • 碰撞原因: 加权和相同(3516)

安全启示

这不仅仅是一道CTF题目,而是一个真实存在的工控系统漏洞(CVE-2010-2965)。

现实影响:

  • 全球数百万台VxWorks设备在使用

  • 电力系统、水处理厂、化工厂等关键基础设施

  • 弱密码算法可能被攻击者利用

  • 很多老设备因成本问题未更新固件

我们的责任:

作为安全研究者,我们不仅要发现漏洞,更要:

  • 负责任地披露漏洞

  • 推动使用现代安全算法

  • 提高工控系统的安全意识

  • 守护关键基础设施的安全

继续前进

这只是安全研究的开始,更多挑战等待着你:

下一步建议:

  1. 参加CTF比赛,提升实战能力

  2. 分析更多固件(路由器、摄像头、智能家居)

  3. 深入学习密码学、操作系统、网络协议

  4. 关注工控安全,参与漏洞研究

  5. 加入安全社区,与同行交流

推荐资源:

  • CTF平台: CTFtime.org、pwnable.kr

  • 学习网站: CryptoHack、OverTheWire

  • 会议: DEF CON、Black Hat、S4x

  • 论坛: r/ReverseEngineering、看雪论坛

附录

附录A: 完整Python代码

算法实现 (vx_hash.py):

#!/usr/bin/env python3
"""VxWorks loginDefaultEncrypt 算法实现"""

def vx_hash(password):
    """
    VxWorks loginDefaultEncrypt 哈希函数

    参数:
        password: 密码字符串(8-40字符)

    返回:
        哈希字符串,如果密码长度无效则返回None
    """
    if len(password) < 8 or len(password) > 40:
        return None

    magic = 0x1E3A1D5  # 31695317
    password_int = 0

    # 步骤1: 加权和
    for i in range(len(password)):
        password_int += int(ord(password[i]) * (i + 2) ^ (i + 1))

    # 步骤2: 魔数乘法与掩码
    temp = str((int(password_int * magic) & 0xffffffff))

    # 步骤3: 字符变换
    output = ''
    for c in temp:
        if c < '3':
            output += chr(ord(c) + ord('!'))
        elif c < '6':
            output += chr(ord(c) + ord('/'))
        elif c < '9':
            output += chr(ord(c) + ord('A'))
        else:
            output += c

    return output

if __name__ == "__main__":
    # 验证
    assert vx_hash("FLAGAWYZ") == "cQwwddSRxS"
    print(" 算法验证通过")

破解脚本 (crack.py):

#!/usr/bin/env python3
"""智能密码破解"""

import itertools
import string
from vx_hash import vx_hash

target = "cQwwddSRxS"
charset = string.ascii_uppercase + string.digits

print(f"目标哈希: {target}")
print("搜索FLAG****模式...")

for combo in itertools.product(charset, repeat=4):
    pwd = "FLAG" + ''.join(combo)
    if vx_hash(pwd) == target:
        print(f" 找到密码: {pwd}")
        break

附录B: 碰撞密码完整列表

完整的434个碰撞密码已保存在collision_passwords.txt文件中。

样本展示(前10个):

1. FLAGAWYZ
2. FLAGAZWY
3. FLAGBYXZ
4. FLAGCVWY
5. FLAGCWYV
6. FLAGCXYW
7. FLAGDUXZ
8. FLAGDWXX
9. FLAGDXTY
10. FLAGDYXV
...
55. FLAGKNXY
...
434. FLAGZZTK

附录C: 参考资源

官方文档:

  • Wind River VxWorks Documentation

  • VxWorks Programmer's Guide - loginLib

安全公告:

  • CVE-2010-2965: VxWorks weak password hashing

  • CERT VU#840249: Wind River Systems vulnerability

  • CISA ICS-ALERT-10-214-01

开源项目:

  • GitHub: dchest/historic-password-hashes

  • GitHub: Fluepke/VX-Works-Password-Hash-Cracker

学习资源:

  • CTF-Wiki: 固件分析入门

  • 看雪论坛: 嵌入式安全板块

  • Industrial Cyber: 工控安全新闻

文章信息:

  • 标签:VxWorks固件逆向密码破解工控安全CTF

  • 题目: CTF Reverse Challenge - VxWorks Password Hash

  • 难度: (中级)

版权声明:
本文仅供安全研究和教育目的使用。对VxWorks设备的安全测试应获得合法授权。未经授权访问计算机系统是违法行为。

"安全研究不是为了破坏,而是为了更好地守护"


文章来源: https://www.freebuf.com/articles/others-articles/458536.html
如有侵权请联系:admin#unsafe.sh