时间紧迫,只做了一题,和去年的 XYCTF EzUnity 类似的游戏封包解包题
经检索,是个 RPG Maker XP 制作的游戏,其中 d3rpg.d3ssad 是游戏所有文件的封包,需要解包该文件得到游戏的所有资源。
尝试使用 https://github.com/uuksu/RPGMakerDecrypter 但是失败,解密逻辑可能被修改。
为了找到二进制中加载该文件的位置,在二进制中搜索导入函数包含 CreateFile
(Windows 打开文件常用函数)的,只在 d3rpg.dll 中找到了 CreateFileA
,在 x32dbg 里面打 API 断点,找到加载该文件的位置。
这个函数返回之后的函数,就是主要的解密逻辑,比对原版的解密逻辑,然后打 patch
From c906cc585560232d09d9f311f4bc9a3c39ce6752 Mon Sep 17 00:00:00 2001
From: oldkingOK <[email protected]>
Date: Mon, 2 Jun 2025 16:22:05 +0800
Subject: [PATCH] d3ctf
---
RPGMakerDecrypter.RGSSAD/Constants.cs | 4 ++--
RPGMakerDecrypter.RGSSAD/RGSSAD.cs | 4 ++--
RPGMakerDecrypter.RGSSAD/RGSSADv1.cs | 8 ++++----
3 files changed, 8 insertions(+), 8 deletions(-)
mode change 100644 => 100755 RPGMakerDecrypter.RGSSAD/Constants.cs
mode change 100644 => 100755 RPGMakerDecrypter.RGSSAD/RGSSAD.cs
mode change 100644 => 100755 RPGMakerDecrypter.RGSSAD/RGSSADv1.cs
diff --git a/RPGMakerDecrypter.RGSSAD/Constants.cs b/RPGMakerDecrypter.RGSSAD/Constants.cs
old mode 100644
new mode 100755
index 953c4ae..186357f
--- a/RPGMakerDecrypter.RGSSAD/Constants.cs
+++ b/RPGMakerDecrypter.RGSSAD/Constants.cs
@@ -20,14 +20,14 @@ namespace RPGMakerDecrypter.RGSSAD
public const string RpgMakerVxProjectFileExtension = "rvproj";
public const string RpgMakerVxAceProjectFileExtension = "rvproj2";
- public static readonly string RGSSADHeader = "RGSSAD";
+ public static readonly string RGSSADHeader = "D3SSAD";
public const int RGASSDv1 = 1;
public const int RGASSDv3 = 3;
public static readonly int[] SupportedRGSSVersions = { RGASSDv1, RGASSDv3 };
- public static readonly uint RGASSADv1Key = 0xDEADCAFE;
+ public static readonly uint RGASSADv1Key = 0xCBAAFBEE;
public const string RPGMakerXpIniFileContents =
"[Game]\r\nLibrary=RGSS104E.dll\r\nScripts=Data\\Scripts.rxdata\r\nTitle=DecryptedProject\r\nRTP1=Standard\r\nRTP2=\r\nRTP3=";
diff --git a/RPGMakerDecrypter.RGSSAD/RGSSAD.cs b/RPGMakerDecrypter.RGSSAD/RGSSAD.cs
old mode 100644
new mode 100755
index da87c91..9703b27
--- a/RPGMakerDecrypter.RGSSAD/RGSSAD.cs
+++ b/RPGMakerDecrypter.RGSSAD/RGSSAD.cs
@@ -146,8 +146,8 @@ namespace RPGMakerDecrypter.RGSSAD
if (j == 4)
{
j = 0;
- tempKey *= 7;
- tempKey += 3;
+ tempKey *= 9;
+ tempKey += 114;
keyBytes = BitConverter.GetBytes(tempKey);
}
diff --git a/RPGMakerDecrypter.RGSSAD/RGSSADv1.cs b/RPGMakerDecrypter.RGSSAD/RGSSADv1.cs
old mode 100644
new mode 100755
index 810ee3a..db8a60a
--- a/RPGMakerDecrypter.RGSSAD/RGSSADv1.cs
+++ b/RPGMakerDecrypter.RGSSAD/RGSSADv1.cs
@@ -59,8 +59,8 @@ namespace RPGMakerDecrypter.RGSSAD
{
var result = value ^ key;
- key *= 7;
- key += 3;
+ key *= 9;
+ key += 114;
return (int)result;
}
@@ -80,8 +80,8 @@ namespace RPGMakerDecrypter.RGSSAD
{
decryptedName[i] = (byte)(encryptedName[i] ^ (key & 0xff));
- key *= 7;
- key += 3;
+ key *= 9;
+ key += 114;
}
var result = Encoding.UTF8.GetString(decryptedName);
--
2.49.0
成功解包,RPGMaker XP 创建新工程,用解包出来的文件覆盖原文件,找到对话,发现是调用了 check 脚本
注意,这里根据 d3rpg.ini,脚本是指定放在 Unknown
中的,需要修改工程的 Game.ini,才能加载出脚本
[Game]
Library=d3rpg.dll
Scripts=Unknown
Title=d3rpg
在 RPGMaker 里面打开脚本编辑器,在 Scene_RPG
中找到 check
#==============================================================================
# ■ Scene_Loads
#------------------------------------------------------------------------------
# 处理游戏内部画面的类。
#==============================================================================
module Scene_RPG
class Secret_Class
DELTA = 0x1919810 | (($de1ta + 1) * 0xf0000000)
def initialize(new_key)
@key = str_to_longs(new_key)
if @key.length < 4
@key.length.upto(4) { |i| @key[i] = 0 }
end
end
def self.str_to_longs(s, include_count = false)
s = s.dup
length = s.length
((4 - s.length % 4) & 3).times { s << "\0" }
unpacked = s.unpack('V*').collect { |n| int32 n }
unpacked << length if include_count
unpacked
end
def str_to_longs(s, include_count = false)
self.class.str_to_longs s, include_count
end
def self.longs_to_str(l, count_included = false)
s = l.pack('V*')
s = s[0...(l[-1])] if count_included
s
end
def longs_to_str(l, count_included = false)
self.class.longs_to_str l, count_included
end
def self.int32(n)
n -= 4_294_967_296 while (n >= 2_147_483_648)
n += 4_294_967_296 while (n <= -2_147_483_648)
n.to_i
end
def int32(n)
self.class.int32 n
end
def mx(z, y, sum, p, e)
int32(
((z >> 5 & 0x07FFFFFF) ^ (y << 2)) +
((y >> 3 & 0x1FFFFFFF) ^ (z << 4))
) ^ int32((sum ^ y) + (@key[(p & 3) ^ e] ^ z))
end
def self.encrypt(key, plaintext)
self.new(key).encrypt(plaintext)
end
def encrypt(plaintext)
return '' if plaintext.length == 0
v = str_to_longs(plaintext, true)
v[1] = 0 if v.length == 1
n = v.length - 1
z = v[n]
y = v[0]
q = (6 + 52 / (n + 1)).floor
sum = $de1ta * DELTA
p = 0
while(0 <= (q -= 1)) do
sum = int32(sum + DELTA)
e = sum >> 2 & 3
n.times do |i|
y = v[i + 1];
z = v[i] = int32(v[i] + mx(z, y, sum, i, e))
p = i
end
p += 1
y = v[0];
z = v[p] = int32(v[p] + mx(z, y, sum, p, e))
end
longs_to_str(v).unpack('a*').pack('m').delete("\n")
end
def self.decrypt(key, ciphertext)
self.new(key).decrypt(ciphertext)
end
end
end
def validate_flag(input_flag)
c_flag = input_flag + "\0"
result = $check_flag.call(c_flag)
result == 1
end
def check
flag = $game_party.actors[0].name
key = Scene_RPG::Secret_Class.new('rpgmakerxp_D3CTF')
cyphertext = key.encrypt(flag)
if validate_flag(cyphertext)
$game_variables[1] = 100
else
$game_variables[1] = 0
end
end
def check1
flag = $game_party.actors[0].name
if flag == "ImPsw"
$game_variables[2] = 100
else
$game_variables[2] = 0
end
end
这里的 $de1ta
是在 Scene_Game
中定义的,用于反调试,有调试就是 1,没调试就是 0。密文在 secret_dll.dll,XXTEA 解密即可。
# By Doctxing
import struct
import base64
def int32(n):
n = n & 0xFFFFFFFF
return n if n < 0x80000000 else n - 0x100000000
def str_to_longs(s, include_count=False):
s = s.encode() if isinstance(s, str) else s
length = len(s)
padding = (4 - length % 4) & 3
s += b'\x00' * padding
longs = list(struct.unpack('<%dI' % (len(s) // 4), s))
if include_count:
longs.append(length)
return [int32(n) for n in longs]
def longs_to_str(longs, count_included=False):
data = struct.pack('<%dI' % len(longs), *[n & 0xFFFFFFFF for n in longs])
if count_included:
return data[:longs[-1]]
return data
class SecretClass:
def __init__(self, key):
self.key = str_to_longs(key)
if len(self.key) < 4:
self.key += [0] * (4 - len(self.key))
def mx(self, z, y, sum_, p, e):
a = ((z >> 5 & 0x07FFFFFF) ^ (y << 2)) + ((y >> 3 & 0x1FFFFFFF) ^ (z << 4))
b = (sum_ ^ y) + (self.key[(p & 3) ^ e] ^ z)
return int32(a ^ b)
def encrypt(self, plaintext):
if not plaintext:
return ""
v = str_to_longs(plaintext, include_count=True)
if len(v) == 1:
v.append(0)
n = len(v) - 1
z = v[n]
y = v[0]
q = (6 + 52 // (n + 1))
DELTA = 0x1919810 | ((1) * 0xf0000000) # $de1ta = 0 -> +1
sum_ = 0
while q > 0:
sum_ = int32(sum_ + DELTA)
e = (sum_ >> 2) & 3
for i in range(n):
y = v[i + 1]
v[i] = int32(v[i] + self.mx(z, y, sum_, i, e))
z = v[i]
y = v[0]
v[n] = int32(v[n] + self.mx(z, y, sum_, n, e))
z = v[n]
q -= 1
return base64.b64encode(longs_to_str(v)).decode().replace('\n', '')
def decrypt(self, ciphertext):
if not ciphertext:
return ""
try:
data = base64.b64decode(ciphertext)
except Exception:
raise ValueError("Invalid Base64 input")
v = str_to_longs(data)
if len(v) < 2:
raise ValueError("Invalid encrypted data length")
n = len(v) - 1
DELTA = 0x1919810 | ((1) * 0xf0000000)
q = 6 + 52 // (n + 1)
sum_ = int32(q * DELTA)
y = v[0]
while q > 0:
e = (sum_ >> 2) & 3
for i in range(n, 0, -1):
z = v[i - 1]
v[i] = int32(v[i] - self.mx(z, y, sum_, i, e))
y = v[i]
z = v[n]
v[0] = int32(v[0] - self.mx(z, y, sum_, 0, e))
y = v[0]
sum_ = int32(sum_ - DELTA)
q -= 1
return longs_to_str(v, count_included=True).decode(errors='ignore')
if __name__ == "__main__":
key = "rpgmakerxp_D3CTF"
cipher = "LhVvfepywFIsHb8G8kNdu49J3k0="
decrypted_text = SecretClass(key).decrypt(cipher)
print("Decrypted text:", decrypted_text)
# Y0u_R_RPG_M4st3r
果然做完题目要及时写 WP,过了两天差不多都忘了)