2025D3CTF d3rpg-reverse


时间紧迫,只做了一题,和去年的 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,过了两天差不多都忘了)


运行时间 427 天 | 总访问量