Featured image of post The Flare-On Challenge 练习记录

The Flare-On Challenge 练习记录

2015 Challenge 3

0x00 查壳与环境识别

使用 DIE (Detect It Easy) 进行初步分析:

DIE 分析结果

分析结果显示程序是由 PyInstaller 打包的。

0x01 解包与反编译

1. PyInstaller 解包

使用 pyinstxtractor.py 对目标文件 elfie.exe (或 elfie) 进行解包:

1
python pyinstxtractor.py elfie

解包后得到 elfie_extracted 文件夹。

2. 字节码分析

进入解包目录,发现存在 .pyc 文件。 在尝试使用反编译工具失败后,发现可以直接查看 .pyc 文件内容。观察到文件头魔数 03 f3 0d 0a 以及目录下的 python27.dll,确认这是 Python 2.7 环境。

直接用文本编辑器查看 .pyc 文件,可以清晰看到 base64b64decode 等关键字及大量混淆变量。这是 Python 2.7 字节码的一个特征:字符串(包括变量名)以未压缩的 ASCII 形式存储。

反编译后的源码(或直接观察)显示程序使用了典型的 exec(base64.b64decode(...)) 混淆方式:

1
2
3
4
5
O0OO0OO00000OOOO0OOOOO0O00O0O0O0 = 'IRGppV0FJM3BRRlNwWGhNNG'
# ... 大量混淆变量定义 ...
O00OO00OOO0OOOO0OOOO0OO00000OOO0 += 'nNocTB'
import base64
exec(base64.b64decode(OOO0OOOOOOOO0000O000O00O0OOOO00O + ...))

0x02 去混淆

为了获取被执行的真实代码,我们不需要手动解密,只需“借力打力”。编写脚本模拟 Loader 的变量拼接过程,但将最后的 exec(...) 替换为文件写入操作。

解包脚本逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# ... (复制原始文件中的所有变量定义) ...

import base64

# 原始代码: exec(base64.b64decode(...))
# 修改为写入文件:
decoded_code = base64.b64decode(OOO0OOOOOOOO0000O000O00O0OOOO00O + ...)

with open(r'elfie.py', 'wb') as f:
    f.write(decoded_code)
print('Decoded stage 2 saved.')

提取出的代码部分如图所示:

提取代码

0x03 逻辑分析

提取出的 elfie.py 是一个基于 PySide (Qt) 的图形界面应用程序。

运行效果:

运行效果

代码中使用了大量的 getattr 和字符串切片(如 'txeTnialPot'[::-1]toPlainText)来隐藏 API 调用,增加了静态分析的难度。

核心验证逻辑: 程序定义了一个继承自 QtGui.QLineEdit 的类,并在 keyPressEvent 中监听回车键。当用户按下回车时,触发验证函数 O000OOOOOO0OOOO00000OO0O0O000OO0

1
2
3
4
5
6
7
8
    def O000OOOOOO0OOOO00000OO0O0O000OO0(self):
        # 获取用户输入: getattr(self, 'toPlainText')()
        O0O0O0000OOO000O00000OOO000OO000 = getattr(self, 'txeTnialPot'[::-1])()
        
        # 核心比对逻辑
        if (O0O0O0000OOO000O00000OOO000OO000 == ''.join((OO00O00OOOO00OO000O00OO0OOOO0000 for OO00O00OOOO00OO000O00OO0OOOO0000 in reversed('moc.no-eralf@OOOOY.sev0000L.eiflE')))):
                self.OO0O0O0O0OO0OO00000OO00O0O0000O0.setWindowTitle('!sseccus taerg'[::-1])
                # ... 显示成功状态 ...

0x04 Flag 获取

分析上述比对逻辑: reversed('moc.no-eralf@OOOOY.sev0000L.eiflE') 会将字符串反转。

直接反转硬编码字符串即可得到 Flag:

1
Elfie.L0000ves.YOOOO@flare-on.com

0x05 附录:现代环境运行兼容性

直接提取的代码依赖于老旧的 PySide (Qt4) 库。在现代 Python 3 环境中,通常使用 PySide2 (Qt5) 或 PySide6 (Qt6)。

为了让提取的代码在现代环境中运行,可以使用以下垫片代码(Shim)进行兼容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
try:
    from PySide import QtGui, QtCore
except ImportError:
    try:
        from PySide2 import QtGui, QtCore, QtWidgets
        # 将 QtWidgets 中的属性映射回 QtGui,以兼容旧代码
        for name in dir(QtWidgets):
            if not hasattr(QtGui, name):
                setattr(QtGui, name, getattr(QtWidgets, name))
    except ImportError:
        from PySide6 import QtGui, QtCore, QtWidgets
        for name in dir(QtWidgets):
            if not hasattr(QtGui, name):
                setattr(QtGui, name, getattr(QtWidgets, name))

此外,代码中还包含两段 Base64 编码的字符串,分别对应验证成功和失败时显示的图片,感兴趣可以解码还原。

2020 Challenge 1: Fidler

挑战概述

挑战以一个简单的点击游戏形式呈现,玩家需要通过点击猫咪赚取硬币,最终达到约1000亿硬币来赢得游戏并揭示flag。游戏使用Pygame编写,并提供了源代码(fidler.py)和可执行文件(fidler.exe)。

密码保护

游戏启动时会显示密码输入界面,标题为“This program is protected by Flare-On TURBO Nuke v55.7”。

密码界面

密码检查函数 password_check(input) 的逻辑如下:

1
2
3
4
def password_check(input):
    altered_key = 'hiptu'
    key = ''.join([chr(ord(x) - 1) for x in altered_key])
    return input == key
  • altered_key = 'hiptu'
  • 对每个字符的ASCII值减1:h(104)-1=g(103), i(105)-1=h(104), p(112)-1=o(111), t(116)-1=s(115), u(117)-1=t(116)
  • 因此,正确密码为 'ghost'

输入错误密码会显示“FBI警告”界面,包含幽默的威胁文本。

FBI 警告

游戏机制

  • 界面:640x480的窗口,显示猫咪图像(kittyelaine.png)、硬币计数、自动点击器计数。 游戏界面
  • 玩法
    • 点击猫咪按钮,每次+1硬币。
    • 购买自动点击器(每10硬币一个),每秒自动增加相应数量的硬币。
  • 目标:赚取约1000亿硬币(100 Billion coins)。

胜利条件与Flag解码

game_screen() 函数中,检查胜利条件:

1
2
3
4
5
6
target_amount = (2**36) + (2**35)  # 2^36 + 2^35 = 68719476736 + 34359738368 = 103079215104
if current_coins > (target_amount - 2**20):  # 2^20 = 1048576
    while current_coins >= (target_amount + 2**20):
        current_coins -= 2**20
    victory_screen(int(current_coins / 10**8))  # token = current_coins // 100000000
    return
  • 当硬币超过 target_amount - 2**20 时触发胜利。
  • 实际胜利硬币数需 >= target_amount + 2**20 ≈ 103080263680。
  • token = 103080263680 // 100000000 = 1030(向下取整)。

Flag通过 decode_flag(token) 解码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def decode_flag(frob):
    last_value = frob  # 初始为1030
    encoded_flag = [1135, 1038, 1126, 1028, 1117, 1071, 1094, 1077, 1121, 1087, 1110, 1092, 1072, 1095, 1090, 1027,
                    1127, 1040, 1137, 1030, 1127, 1099, 1062, 1101, 1123, 1027, 1136, 1054]
    decoded_flag = []
    for i in range(len(encoded_flag)):
        c = encoded_flag[i]
        val = (c - ((i%2)*1 + (i%3)*2)) ^ last_value
        decoded_flag.append(val)
        last_value = c
    return ''.join([chr(x) for x in decoded_flag])
  • 解码公式:val = (c - ((i%2)*1 + (i%3)*2)) ^ last_value
  • last_value 初始为frob(1030),每次循环后更新为c。

运行解码脚本得到flag:idle_with_kitty@flare-on.com

2020 Challenge 2: garbage

首先打开显示

初始界面

查DIE发现是upx壳,但是直接upx -d会报错

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fanshanng@Ubuntu:/mnt/d/Desktop/challenge/attachments_383/2 - garbage2/2 - garbage$ upx -d garbage.exe
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2025
UPX 5.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jul 20th 2025

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: garbage.exe: OverlayException: invalid overlay size; file is possibly corrupt

Unpacked 1 file: 0 ok, 1 error.

invalid overlay size; file is possibly corrupt没找到是什么原因,于是去翻看源代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void Packer::checkOverlay(unsigned overlay)
{
    if ((int)overlay < 0 || (off_t)overlay > file_size)
        throw OverlayException("invalid overlay size; file is possibly corrupt");
    if (overlay == 0)
        return;
    info("Found overlay: %d bytes", overlay);
    if (opt->overlay == opt->SKIP_OVERLAY)
        throw OverlayException("file has overlay -- skipped; try '--overlay=copy'");
}

会发现错误原因overlay > file_size是文件头信息显示文件比实际大小大。这一点在 CFF Explorer 中也能看到:

CFF Explorer

文件中缺少 41472−40740 = 732 字节。 于是我打开了010editor,在结尾补全了732个字节

补全字节

补全后就可以顺利脱壳了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fanshanng@Ubuntu:/mnt/d/Desktop/challenge/attachments_383/2 - garbage2/2 - garbage$ upx -d garbage.exe
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2025
UPX 5.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jul 20th 2025

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
     80470 <-     42582   52.92%    win32/pe     garbage.exe

Unpacked 1 file.

但是脱完壳后的程序还是不能直接运行

运行错误

这里应该是产生了无效的资源数据,在Resource Hacker可以清晰看到

无效资源

右键点击资源将其删除

删除资源

但是又会遇到另一个错误

导入错误

CFF Explorer 中查看最新文件的导入目录,可以看到模块名称缺失:

导入模块缺失

点击其中任何一个都会显示这些 DLL 中的函数:

DLL 函数

看到这些函数名,我就可以确定第一个函数是kernel32.dll,第二个函数是shell32.dll

修复导入

然后就可以看到输出的flag了

输出flag

参考文献:

前途似海,来日方长。


<