Mas0n
to be reverse engineer🐧
翻车鱼

D3CTF2022

D3CTF2022

这场比赛最大的感受大概是累……逆向算是一题一个语言架构,对于我来说是个不小的挑战。

因为学的比较多所以赛后整理了一下,stm32、uwp都比较偏门,资料也比较少,索性题目本身逻辑比较简单,入门ing…

D3MUG

u3d手游题

il2cppdumper跑一遍,导入符号后简单分析

按键回调事件:NoteObject__OnClicked

  • 无效事件:GameManager__NoteMissed
  • 击中事件:GameManager__NoteHit

地图加载:GameManager__loadBeatmap_d__8__MoveNext

读取地图数据:BeatScroller__ParseBeatTampoMap

  • 生成点位:BeatScroller__GenerateNote

assetstudio查看资源文件,有多张地图

但我们只加载了一张图:Beatmaps/ChromeVOX

https://cdn.shi1011.cn/2022/03/442a9a192ec6c1eb64b110c3978de02a.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

结合之前地图解析函数,分析数据

  • hitpoints
    • 格式:int,int (出现在第n列, 出现在n毫秒)
    • 示例: 4,0
  • timepoints
    • 格式:int: float
    • 示例:0: 292.6829268292683

寻找到最后的check点ScoreScene__Start

ScoreSceneMusicController控制下加载

https://cdn.shi1011.cn/2022/03/3ed401d0644290eb96002361bd9083b9.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

ScoreScene__get调用了libd3mug3.so中的get

https://cdn.shi1011.cn/2022/03/1d12e6dbbf1d66ce34b4e120e750fa79.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

GameManager__NoteHit调用了GameManager__update

实际上调用的是libd3mug3.so中的update,看起来是修改过的MT19937

分析check逻辑

调用get得到的数组,强转为string,开头为D3CTF则成功

https://cdn.shi1011.cn/2022/03/def4f72a900c4fabd104fd8a3ccf2ec8.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

尝试hook update

能发现不管是hit还是missed,值都是0

分析调用链,其中的值来自于MusicController

https://cdn.shi1011.cn/2022/03/07609f968ab141a9c6ee9c929cf60875.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

大概是arm64不支持,所以没获取到时间,最后会显示arch is not support

https://cdn.shi1011.cn/2022/03/0a024e2b8c6bd7d6cb794f50ad3bbf81.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

查阅了下:Unity – Scripting API: Time.fixedDeltaTime (unity3d.com)

MonoBehaviour-FixedUpdate() – Unity 脚本 API (unity3d.com)

推测_CurrentTime_k__BackingField就是音乐播放的时间

回到地图给的指示:Play this and AP to get FLAG.

踩时间点通关,利用hitpoints去跑一遍即可

const hitdata = ``; // beatmaps/chromevox/hitpoints

const timeList = hitdata.split("\n").map((v, idx, arr) => {
    return parseInt(v.split(",")[1], 10);
});

function awaitHook(){
    
    var ptr = Module.findBaseAddress("libd3mug.so");
    console.log(ptr);

    const update = new NativeFunction(ptr.add(0x0000780), "pointer", ["char"]);
    const instance = ptr.add(0x02D18);
    // running
    instance.writePointer(new NativePointer(0)); // init.
    for (const t of timeList) {
        update(t);
    }
    console.log(instance.readPointer().readCString());

}
setImmediate(function(){
    setTimeout(awaitHook,10);
})

d3arm

特征对应项目

ARMmbed/mbed-os

给了一个bin文件,先将bin转换为hex

BINARY to Motorola S-Record Converter Utility (keil.com)

srec_cat.exe 815b45cf84.bin -Binary -o test_hex_out.hex -intel -Output-Block-Size=16 -Disable_Sequence_Warnings

IDA强制arm little endian载入

使用IDA pro逆向ARM M系核心的Bin固件

根据ARM M的启动流程,计算得到基址0x8000000和stack基址0x20010000

手动创建stack segment 0x20010000 ~ 0x20030000

调用链0x800951C -> 0x8009620 -> 0x80091A0

https://cdn.shi1011.cn/2022/03/8412dc55abfcdc8ba08ecba006117fff.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

执行流程

https://cdn.shi1011.cn/2022/03/249d62a34f97a045f2a0263ad355a443.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

check点

https://cdn.shi1011.cn/2022/03/cbe16f134a0b3d66f3f5c6449c610958.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

难度需要选择为hard

https://cdn.shi1011.cn/2022/03/8bfb54c05f742e41cf84c6122ffbe636.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

游戏逻辑

https://cdn.shi1011.cn/2022/03/646236cf7fa7b99a07d6bacaa9e4e1cd.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

链表结构

struct list
{
  _DWORD X;
  _DWORD Y;
  list *next;
};

关注函数set_flaginsertNode

可疑异或

https://cdn.shi1011.cn/2022/03/4736926d6d7466ec03962cd3fbd6ec5a.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

异或值来源

https://cdn.shi1011.cn/2022/03/910792d3bb50e0ed60dfe2ed8ce70778.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

思路:通关得到flag(

# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: d3dctf_1.py
@Time: 2022/3/4 23:41
@Desc: It's all about getting better.
"""

check_arr = [0x00000020, 0x0000006D, 0x00000050, 0x00000030, 0x00000038, 0x00000048, 0x00000026, 0x00000067, 0x00000057, 0x0000007C, 0x0000006C, 0x00000005, 0x00000020, 0x0000003A, 0x00000052, 0x00000072, 0x0000003C, 0x0000000B, 0x00000025, 0x0000006D, 0x00000052, 0x00000074, 0x0000006A, 0x00000055, 0x00000070, 0x0000006A, 0x00000005, 0x00000076, 0x0000003A, 0x00000004, 0x0000007D, 0x0000006D, 0x00000007, 0x00000077, 0x0000006E, 0x00000057, 0x00000077, 0x0000006F, 0x00000004, 0x00000020, 0x00000038, 0x0000004E]
flag = bytearray(b'\x00' * 42)
xorkey = 68
flag[0] = xorkey ^ check_arr[0]

for i in range(1, 42):
    v1 = i % 3
    xorkey = (0x335E44 >> (8 * v1)) & 0xff
    flag[i] = xorkey ^ check_arr[i]
print(flag)

d3hotel

u3d + wasm

命名风格挺像n1(狗头)

BuildWebGL.wasm.brBuildWebGL.data.br解压后拿到BuildWebGL.wasmBuildWebGL.data

assetstudio解出global-metadata.dat

顺道看到了两个luac

https://cdn.shi1011.cn/2022/03/77280505a05941194e0511b27b93e757.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

extract出来unluac跑一遍

main.lua

local L0_1, L1_1, L2_1, L3_1, L4_1, L5_1, L6_1, L7_1, L8_1, L9_1, L10_1, L11_1
matrix = require("matrix")

m = matrix(5, 5, 0)


L2_1 = {}
L2_1[1] = 6422944
L2_1[2] = -7719232
L2_1[3] = 41640292
L2_1[4] = -1428488
L2_1[5] = -36954388
L3_1 = {}
L3_1[1] = 43676120
L3_1[2] = -26534136
L3_1[3] = -31608964
L3_1[4] = -20570796
L3_1[5] = 22753040
L4_1 = {}
L5_1 = -6066184
L6_1 = 30440152
L7_1 = 5229916
L8_1 = -16857572
L9_1 = -16335464
L4_1[1] = L5_1
L4_1[2] = L6_1
L4_1[3] = L7_1
L4_1[4] = L8_1
L4_1[5] = L9_1
L5_1 = {}
L6_1 = -8185648
L7_1 = -17254720
L8_1 = -22800152
L9_1 = -8484728
L10_1 = 44642816
L5_1[1] = L6_1
L5_1[2] = L7_1
L5_1[3] = L8_1
L5_1[4] = L9_1
L5_1[5] = L10_1
L6_1 = {}
L6_1[1] = -35858512
L6_1[2] = 10913104
L6_1[3] = -4165844
L6_1[4] = 37696936
L6_1[5] = -10061980
L1_1 = {}

L1_1[1] = L2_1
L1_1[2] = L3_1
L1_1[3] = L4_1
L1_1[4] = L5_1
L1_1[5] = L6_1

n = matrix(L1_1)

function L0_1(A0_2)
  local L1_2, L2_2, L3_2, L4_2, L5_2, L6_2, L7_2, L8_2, L9_2, L10_2, L11_2, L12_2
  L1_2 = #A0_2
  if L1_2 ~= 25 then
    L1_2 = 0
    return L1_2
  end

  for i = 1, 5, 1 do

    for j = 1, 5, 1 do
      m[i][j] = string.byte(A0_2, (i - 1) * 5 + j)
	  
    end
  end
  L4_2 = matrix.det(m)
  L2_2 = string.format("%.5f", L4_2)

  det = tonumber(L2_2)

  L1_2 = matrix(5, "I") * det

  L2_2 = m * n
  if L1_2 == L2_2 then
    return 1
  else
    return 0
  end
end
f = L0_1

matrix.lua

lua-matrix/matrix.lua

找了队里密码师傅解了下

n = [[6422944, -7719232, 41640292, -1428488, -36954388],
     [43676120, -26534136, -31608964, -20570796, 22753040],
     [-6066184, 30440152, 5229916, -16857572, -16335464],
     [-8185648, -17254720, -22800152, -8484728, 44642816],
     [-35858512, 10913104, -4165844, 37696936, -10061980]]
n = matrix(ZZ,n)
b = -2337879088*n^(-1)
for i in b:
    for j in i:
        print(chr(j),end='')
# d3ctf{W3ba5m_1s_Awe5oom3}

交不上,偷懒失败……

il2cppDumper跑一遍

{
      "Address": 7581,
      "Name": "D3Checker$$Check",
      "Signature": "void D3Checker__Check (D3Checker_o* __this, const MethodInfo* method);",
      "TypeSignature": "vii"
}

offset为0

void zh(int param0, int param1, int param2) {
    *(unsigned int*)(param0 * 4 + (int)&gvar_40000000)(param2, param1);
}
var dynCall_vii = Module["dynCall_vii"] = function() {
        return (dynCall_vii = Module["dynCall_vii"] = Module["asm"]["zh"]).apply(null, arguments)
    }

webassembly tables确定函数位置,分析下

https://cdn.shi1011.cn/2022/03/20a6a6eb24d5b4979b22b62bf2e54dfc.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

挺坑……

# -*- coding:utf-8 -*-
"""
@Author: Mas0n
@File: d3ctf_2.py
@Time: 2022/3/5 21:38
@Desc: It's all about getting better.
"""

fake = bytearray(b'd3ctf{W3ba5m_1s_Awe5oom3}')
flag = fake.copy()
flag[9] -= 33
print(flag)

D3Re

UWP逆向,分析了一部分,后面被队里师傅直接干完了。

.NET Native and Compilation – UWP applications | Microsoft Docs

dump了下内存

https://cdn.shi1011.cn/2022/03/ae99e74bcd66b724a594d2769556a4b8.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

Francesco149/uwpspy

Francesco149/uwpinject

ce trace

https://cdn.shi1011.cn/2022/03/22c187e4687a18de6709264fe6a79d1e.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

check点0x011BA80

https://cdn.shi1011.cn/2022/03/c08c70757dad10a9ed0721dfb21efd3f.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

正确格式:d^3ctf{11111111-2222-2222-1111-333321111111}

https://cdn.shi1011.cn/2022/03/472dcc4127c32393c746badc0039563e.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

字符串结构长这样

https://cdn.shi1011.cn/2022/03/d07a829f26593fb022afdffcf26049c7.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

以下结合队里师傅的分析:

BigInteger.cs

  • 0x010C730-
  • 0x010C190+
  • 0x010EF90ModPow
  • 0x010CB90*
  • 0x010E370%

flag为16进制,转为16字节

将前8字节位移9得到data1

data1 = 0
for i in range(8):
    data1 = (data1 << 9) + flag[i]

第一部分check

m = 904559654629185507076703
n = 757726435240880506850652
# (flag_part * n) % m == 820856551661154796608770
flag_part = 820856551661154796608770*n^(-1) % m
print(flag_part)
_unpack = lambda v: bytearray([(v >> 9*(7-i)) & 0xff for i in range(8)])
print(_unpack(flag_part).hex())
# 5d799a23ed488340

-分隔的每一段都需要逆序,得到前半段flag d^3ctf{239a795d-48ed-4083

而后就是EncryptStringToBytes_Aes对整个flag加密

dump出key、iv、data,解密

from Crypto.Cipher import AES

data = bytearray([0x8F, 0x7C, 0x12, 0x6B, 0x07, 0xD4, 0x98, 0x77, 0x3B, 0x5C, 0x62, 0x0B, 0xAC, 0x96, 0x13, 0x96])

key = b''.fromhex('C51A4B2841E627D47D72C34079BE1F6C')
iv = b''.fromhex('9CA57A2B88214607345DD2A3A0591EFF')
ctx = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
flag = ctx.decrypt(data)
print(flag.hex())
# 5d799a23ed48834090d3e7528ca0776b

拼起来得到d^3ctf{239a795d-48ed-4083-90d3-e7528ca0776b}

本文链接:https://blog.shi1011.cn/ctf/2223
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

翻车鱼

D3CTF2022
这场比赛最大的感受大概是累……逆向算是一题一个语言架构,对于我来说是个不小的挑战。 因为学的比较多所以赛后整理了一下,stm32、uwp都比较偏门,资料也比较少,索性题目本身逻辑比较…
扫描二维码继续阅读
2022-03-07