2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

WriteUp 2个月前 admin
158 0 0

chal(赛后出flag的零解题)

cython的逆向万变不离其宗,用Cython 二进制库逆向分析全面指南里的方法能逆出个大概。赛后又去再看了一下,(以为只是用来防多解的)self._tips才发现它才是真正的check。真正的密文竟然不是已知数组,而是……

先看题目给的main.py的调用,是直接用chal.chal(flag)对flag进行检查:

import chal

flag = input("flag: ")
chal.chal(flag)

然后看一下chal的各属性:

>>> import chal
>>> dir(chal)
['__builtins__''__doc__''__file__''__loader__''__name__''__package__''__spec__''__test__''chal''os''random']
>>> dir(chal.chal)
['__class__''__delattr__''__dict__''__dir__''__doc__''__eq__''__format__''__ge__''__getattribute__''__getstate__''__gt__''__hash__''__init__''__init_subclass__''__le__''__lt__''__module__''__ne__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__str__''__subclasshook__''__weakref__''_p1''_p2''_p3']
>>> chal.chal._p1
<cyfunction chal._p1 at 0x4000fa0450>
>>> chal.chal._p2
<cyfunction chal._p2 at 0x4000fa0520>
>>> chal.chal._p3
<cyfunction chal._p3 at 0x4000fa05f0>
>>> dir(chal.chal("a"))
['__class__''__delattr__''__dict__''__dir__''__doc__''__eq__''__format__''__ge__''__getattribute__''__getstate__''__gt__''__hash__''__init__''__init_subclass__''__le__''__lt__''__module__''__ne__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__str__''__subclasshook__''__weakref__''_p1''_p2''_p3''_tips''_var1''_var2''_var3']
>>> chal.chal("a")._tips
"Don't peek!!!"
>>> chal.chal("a")._var1
'a'
>>> chal.chal("a")._var2
[1217314114611523018165238171467322882188661214822566255254472216325022213335232106176]
>>> chal.chal("a")._var3
[122431331477362949226211156561427825412]

可以看到,chal实际上是chal模块里的一个类,类里面有_p1_p2_p3三个函数,初始化以后多了_tips_var1_var2_var3四个变量。那main.py里面实际上是用flag初始化了一个chal.chal类,对flag的check也在类的__init__函数中。而__init__函数透露的信息很少,所以只能老老实实看伪代码。

cython伪代码阅读

这里介绍一下如何通过伪代码恢复出原来的Python代码。限于篇幅,以下仅列举一些典型的代码序列。

C级别的全局变量赋值

字符串类型

_Pyx_CreateStringTabAndInitStrings

全局字符串赋值一般在_Pyx_CreateStringTabAndInitStrings中,该函数中使用的字符串定义数组形如:

typedef struct {
    PyObject **p;
    const char *s;
    const Py_ssize_t n;
    const char* encoding;
    const char is_unicode;
    const char is_str;
    const char intern;
} __Pyx_StringTabEntry;

而字符串是通过__Pyx_StringTabEntry的数组进行初始化的,也就是说当我们在该函数中看到以下伪代码时:

__m128i v8;
__int64 v9;
__int64 v10;
__int16 v11;
char v12;

v8 = _mm_unpacklo_epi64(&qword_28A98, "AttributeError");
v9 = 15LL;
v10 = 0LL;
v11 = 0x100;
v12 = 1;

就代表这是一个{&qword_28A98, "AttributeError", 15, 0, 1, 0, 1}__Pyx_StringTabEntry,也就是说qword_28A98中将要初始化一个内容是"AttributeError"的字符串对象的地址,在后续调用中,调用到AttributeError字符串的地方都会用&qword_28A98指代。

整数类型

_pyx_pymod_exec_chal

qword_29170 = PyLong_FromLong(113LL, v9, v244, v245);
if ( qword_29170 )

qword_29170中将存储一个值为113的整数类型的Python对象。

qword_29600 = PyLong_FromString("2654435769"0LL, 0LL);
if ( qword_29600 )

大数会用PyLong_FromString函数来初始化,这里qword_29600中将存储一个值为2654435769的整数类型的Python对象,后续用到2654435769的地方将使用qword_29600

内建函数/变量

_Pyx_InitCachedBuiltins, called by _pyx_pymod_exec_chal

(在某些优化下也会直接嵌入 _pyx_pymod_exec_chal

qword_296B8 = _Pyx_GetBuiltinName(qword_28A98);
if ( !qword_296B8 )
  return 0xFFFFFFFFLL;

qword_28A98就是前面的"AttributeError",这里是通过名字找到AttributeError对象,并赋值给qword_296B8,后续用到AttributeError对象的地方将使用qword_296B8

常量

_Pyx_InitCachedConstants, called by _pyx_pymod_exec_chal

(在某些优化下也会直接嵌入 _pyx_pymod_exec_chal

qword_29630 = PyTuple_Pack(2LL, qword_29600, qword_29618);
if ( !qword_29630 )
  return 0xFFFFFFFFLL;

qword_29600是前面的值为2654435769的整数类型的Python对象,同理qword_29618就是值为3337565984的整数类型的Python对象。这里将这两个对象打包成了一个长度为2的元组(2654435769, 3337565984),并赋值给qword_29630变量,后续用到这个元组的地方将使用qword_29630

函数声明及定义

声明在_Pyx_InitCachedConstants, called by _pyx_pymod_exec_chal

(在某些优化下也会直接嵌入 _pyx_pymod_exec_chal

定义在_pyx_pymod_exec_chal

这里变量太多直接上手动恢复后的符号。

// *** _Pyx_InitCachedConstants ***
// 元组赋值
v1 = PyTuple_Pack(7LL, self, x1, x2, tmp, low, high, ans);
if ( !v1 )
  return 0xFFFFFFFFLL;
// 函数定义
qword_29688 = _Pyx_PyCode_New_constprop_0(
  37, qword_28A80, qword_28A78, qword_28A78, v1, qword_28A78, 
  qword_28A78, chal_py, p1, 19, qword_28A80
);
if ( !qword_29688 )
  return 0xFFFFFFFFLL;

_Pyx_PyCode_New_constprop_0用于创建一个PyCodeObject,其参数就是PyCodeObject的各属性,具体可参考各版本cpython源码中对PyCodeObject的定义,这里就是以v1元组为参数+局部变量名(前3个为参数),原Python函数第一行在文件中的第19行(qword_28A78()qword_28A80"",无内容不用关注)创建了一个名为_p1的函数PyCodeObject,相当于是函数声明(因为co_code字段是空的,没有指定具体行为)。

// *** _pyx_pymod_exec_chal ***
v559 = _Pyx_CyFunction_New_constprop_0(&_pyx_mdef_4chal_4chal_3_p1, chal__p1, chal, _pyx_mstate_global_static, qword_29688);                                
v560 = PyObject_SetItem(v6, p1, v559) >> 31// self._p1 = v559

cython中一般使用PyMethodDef进行指定:

ctypedef struct PyMethodDef:
    const char* ml_name
    PyCFunction ml_meth
    int ml_flags
    const char* ml_doc

伪代码中的_pyx_mdef_4chal_4chal_3_p1就是一个PyMethodDef

.data:00000000000289C0 AD 41 02 00 00 00 00 00           __pyx_mdef_4chal_4chal_3_p1 dq offset aChalChalP1+0Ah
.data:00000000000289C0                                                                 ; DATA XREF: __pyx_pymod_exec_chal+236B↑o
.data:00000000000289C0                                                                 ; "_p1"
.data:00000000000289C8 70 F7 00 00 00 00 00 00           dq offset __pyx_pw_4chal_4chal_3_p1
.data:00000000000289D0 82 00 00 00                       dd 82h
.data:00000000000289D4 00                                db 0

_p1的函数体实际上在__pyx_pw_4chal_4chal_3_p1中。

import

_pyx_pymod_exec_chal

v539 = _Pyx_ImportDottedModule_constprop_0(random);
if ( PyDict_SetItem(_pyx_mstate_global_static, random, v539) < 0 )
{

导入random模块,同import random

Python代码的转译

对象变量赋值

v22 = PyObject_SetAttr(self, var1, s);

直接用了PyObject_SetAttr函数,其实反编译过来就是self._var1 = s

数组赋值

v23 = PyList_New(32LL); // 1. 创建新的列表对象
v24 = pyx_int_121; // 2. v24 = 121
if ( *pyx_int_121 != -1 ) // 3. 错误处理
  ++*pyx_int_121;
v25 = v23[3]; // 4. 列表对象(v23)的[3]是数据部分
*v25 = v24; // 5. 列表第一个元素为v24即121
// ... 重复以上2、3、5步处理对列表赋值
v58 = PyObject_SetAttr(self, var2, v23);

最后将成型的列表v23赋给self._var2,这里就是self._var2 = v23 = [121, ...]

函数调用

以下是一段如果参数为变量对Python函数的完整调用,没有标注的都是框架代码及错误处理。

    v150 = *(*(self + 8) + 144LL);
// v2 = self._p3
    if ( v150 )
      v2 = v150(self, p3);
    else
      v2 = PyObject_GetAttr(self, p3);
    if ( !v2 )
    {
      v101 = 0LL;
      v1 = 0LL;
      v23 = 0LL;
      v122 = 4493LL;
      v123 = 10LL;
      goto LABEL_211;
    }
    if ( *(v2 + 8) == &PyMethod_Type && (v151 = *(v2 + 24)) != 0LL )
    {
      v1 = *(v2 + 16);
      if ( *v151 != -1 )
        ++*v151;
      if ( *v1 != -1 )
        ++*v1;
      if ( *v2 >= 0 )
      {
        v152 = *v2 - 1LL;
        *v2 = v152;
        if ( !v152 )
          _Py_Dealloc(v2);
      }
// 参数是("Don't hook!!!")
      v192 = _mm_loadh_ps(&Don_thook___);
// 函数调用,执行self._p3("Don't hook!!!"),返回值给v23
      v23 = _Pyx_PyObject_FastCallDict_constprop_0(v1, &v192, 2LL);
      if ( *v151 >= 0 )
      {
        v153 = *v151 - 1LL;
        *v151 = v153;
        if ( !v153 )
          _Py_Dealloc(v151);
      }
    }
// 不同的调用方式,和上面的if同层
    else
    {
      v1 = v2;
      v192.m128_u64[0] = 0LL;
      v192.m128_u64[1] = Don_thook___;
      v23 = _Pyx_PyObject_FastCallDict_constprop_0(v2, v187, 1LL);
    }
    if ( !v23 )
    {
      v101 = 0LL;
      v2 = 0LL;
      v122 = 4513LL;
      v123 = 10LL;
      goto LABEL_211;
    }
    if ( *v1 >= 0 )
    {
      v154 = *v1 - 1LL;
      *v1 = v154;
      if ( !v154 )
        _Py_Dealloc(v1);
    }

如果参数都为常量则简单得多:

v404 = _Pyx_PyObject_GetAttrStr(v403, to_bytes);
v406 = _Pyx_PyObject_Call_constprop_0(v404, t_2_big); // t_2_big是元组(2, 'big'),在_Pyx_InitCachedConstants中有初始化

等同于Python中的:

v406 = v403.to_bytes(2'big')

对比

(手动删除了框架代码和错误处理)

// v2 = self._var2
    v155 = *(*(self + 8) + 144LL);
    if ( v155 )
      v2 = v155(self, var2);
    else
      v2 = PyObject_GetAttr(self, var2);
/* 上文返回值v23和v2对比,对比常量:
https://github.com/python/cpython/blob/3.12/Include/object.h#L862
#define Py_LT 0
#define Py_LE 1
#define Py_EQ 2
#define Py_NE 3
#define Py_GT 4
#define Py_GE 5
*/

    v1 = PyObject_RichCompare(v23, v2, 2LL);
// 确定是否IsTrue
    LOBYTE(IsTrue) = v1 == &Py_TrueStruct;
    if ( v1 == &Py_TrueStruct || v1 == &Py_FalseStruct || v1 == &Py_NoneStruct )
    {
      IsTrue = IsTrue;
    }
    else
    {
      IsTrue = PyObject_IsTrue(v1);
    }
// 如果not IsTrue,即 v23 != self._var2
    if ( !IsTrue )

取数组长度

(手动删除了框架代码和错误处理)

v2 = PyObject_GetAttr(self, var1);
v91 = PyObject_Size(v2);
v93 = PyLong_FromSsize_t(v91);

这一段实际上等同于Python中的

v93 = len(self._var1)

运算

// v3 = 2654435769 - v93
v3 = PyNumber_Subtract(pyx_int_2654435769, v93);
// v294 = v293 ^ v91
v294 = (__m128 *)PyNumber_Xor(v293, v91);
// v367 = v1229 + v365
v367 = (__int64 *)PyNumber_Add(v1229, v365);
// InPlace字样,这里等同于v1213 = (v483 &= 4294967295)
v1213 = (__int64 *)PyNumber_InPlaceAnd(v483, pyx_int_4294967295);
// m128_u64 = v1213 >> 4
m128_u64 = (_QWORD *)_Pyx_PyInt_RshiftObjC_constprop_0(v1213, pyx_int_4, 4LL);
// v537 = v1225 & 3
v537 = (_QWORD *)_Pyx_PyInt_AndObjC_constprop_0(v1225, pyx_int_3, 3LL, 0LL);
// ItemInt_List_Fast_constprop_0 = v537 + 4
ItemInt_List_Fast_constprop_0 = (__m128 *)_Pyx_PyInt_AddObjC_constprop_0(v537, pyx_int_4, 4LL, 0LL);
// ...

hook辅助

使用x86_64机器上的Python 3.12导入chal,即可对其函数进行hook。如我们需要hookself._p1

>>> import chal
>>> setattr(chal.chal, "ori_p1", chal.chal.__dict__["_p1"]) # 保存原来的_p1
>>> def hook_p1(self, a, b): # 写hook函数,打印参数和返回值
...     print(a, b)
...     ret = self.ori_p1(a, b)
...     print(ret)
...     return ret
... 
>>> setattr(chal.chal, "_p1", hook_p1) # 挂上去
>>> chal.chal("a"# 调用后即可看到每次调用self._p1时的参数和返回值,且不影响函数原来的功能
52232 48895
-27712
52883 49898
-25833
64660 29775
-28948
# ... 数据省略

hook出中间数据以后可以帮助我们确认或纠正在静态手工反编译过程中的一些细节,如这里hook出self._p1的中间数据后,如果我们恢复出的源代码中的self._p1的中间数据与此相同,那么可以保证流程走到self._p1时其参数和返回值一定是正确的;反之则说明恢复的源代码有误。

在本题中,hookself._p1可以确认输入数据流的加密是否正确;hookself._p2可以确认随机数是否相同,从而确认随机数种子及使用random模块中函数的次数是否一致;hookself._p3可以拿到被self._p3改变但未被self.__init__改变的self.tips,确认比对的细节。

解题思路

总之使用上面的方法,可以恢复出题目源代码(注释中写了一些关键点和伪代码中对应的变量名):

import os
import random

class chal():
    def __init__(self, s):
        # 变量的开头下划线都没写,懒(
        self.var1 = s
        self.var2 = [1217314114611523018165238171467322882188661214822566255254472216325022213335232106176]
        self.var3 = [122431331477362949226211156561427825412]
        random.seed(sum([2654435769-len(s)] + [ord(x) for x in str(len(s))]))
        if self._p3("Don't hook!!!") == self.var2: # 假的check,别信
            print("yes")
        elif self.tips == 0# 真的check!
            # 有个self._var2 + self._var3,但没有使用
            print("yes")
        self.tips = "Don't peek!!!"

    def _p1(self, a, b): # a * b % 65537
        # print(a, b)
        c = a * b
        cl = c & 0xFFFF
        ch = (c >> 16) & 0xFFFF
        if cl - ch < 0:
            ret = cl - ch + 1
        else:
            ret = cl - ch
        # print(ret)
        return ret

    def _p2(self, i): # sm4 subbytes
        # print(i)
        l = [2141442332542042256118322182201944025144543103154118421904195170681938731346153156668024414523915212251841167237207172982281792816920182321491282231482501171436316671716725224311523186131896025230133791681041071291781131002181392482351575112861575330361494998820916237341245913312013521207087159211398276542231160196200158234191138210641995618116324724220624997211612241749316415552268517314750482451401772272924622646130102202961924135171138378111213219556922225314247325510611410910891811412717514618722118812717217926531169021610193491361652051231894511620818184229180176137105151741215011912610118524191971101981322424012523658220773212123895622152035772]
        ret = int.from_bytes(bytes(map(lambda x: l[x], i.to_bytes(4'little'))), 'little')
        # print(ret)
        return ret

    def _p3(self):
        # 开头的lambda是个pkcs5padding,但是输入整块的数据就不重要了(块长度是16
        # s = pkcs5padding(self.var1).encode()
        s = self.var1.encode()
        L = [1737131631411801931562119865218132161481051659625012116823944979120101211167240751364370115203220341601882226116911795134174167]
        tmpl = []
        self.tips = 0
        for i in range(len(s)):
            tmpl.append(L[i%len(L)] ^ s[i])
        rslt = b''
        for i in range(0, len(tmpl), 8):
            longs = tmpl[i:i+8]
            randl = []
            # IDEA的轮密钥
            for _ in range(52):
                randl.append(random.getrandbits(16))
            ll = [] # v141
            for j in range(0, len(longs), 2):
                ll.append(int.from_bytes(longs[j:j+2]))
            assert len(ll) == 4
            v1227, v1229, v1230, v1222 = ll
            for v1232 in range(8):
                v1225 = random.randint(0x9e3779b90xc6ef3720)
                # TEA 密钥
                rilv1224 = []
                for _ in range(8):
                    rilv1224.append(random.randint(0x56AA33500xa3b1bac6))
                # XTEA 密钥
                rilv1228 = []
                for _ in range(8):
                    rilv1228.append(random.randint(0x677D91970xb27022dc))
                # 半轮魔改IDEA
                lv1233 = []
                lv1233.append(self._p1(v1227, randl[6 * v1232 + 0]) & 0xFFFF)
                lv1233.append((v1229 + randl[6 * v1232 + 1]) & 0xFFFF)
                lv1233.append((v1230 + randl[6 * v1232 + 2]) & 0xFFFF)
                lv1233.append(self._p1(v1222, randl[6 * v1232 + 3]) & 0xFFFF)
                lv395 = []
                for j in range(4):
                    lv395.append(lv1233[j].to_bytes(2'big'))
                tmps = b''.join(lv395)
                lv424 = []
                for j in range(0, len(tmps), 4):
                    lv424.append(int.from_bytes(tmps[j:j+4], 'little'))
                assert len(lv424) == 2
                # 魔改TEA
                l, r = lv424 # v1213, v1215
                l += ((r << 4) + self._p2(rilv1224[0])) ^ (r + v1225) ^ ((r >> 5) + self._p2(rilv1224[1]))
                l &= 4294967295
                r += ((l << 5) + self._p2(rilv1224[2])) ^ (l + v1225) ^ ((l >> 4) + self._p2(rilv1224[3]))
                r &= 4294967295
                # 魔改XTEA
                l += (((r << 5) ^ (r >> 4)) + r) ^ (v1225 + self._p2(rilv1228[4 + (v1225 & 3)]))
                l &= 4294967295
                r += (((l << 4) ^ (l >> 5)) + l) ^ (v1225 + self._p2(rilv1228[4 + ((v1225 >> 11) & 3)]))
                r &= 4294967295
                # 奇数轮更改v1225(TEA/XTEA中的sum)
                if v1232 & 1 == 1:
                    v1225 = random.getrandbits(32)
                # 魔改TEA
                l += (((r << 3) ^ (r >> 6)) + r) ^ (v1225 + self._p2(rilv1228[v1225 & 3]))
                l &= 4294967295
                r += (((l << 4) ^ (l >> 5)) + l) ^ (v1225 + self._p2(rilv1228[(v1225 >> 11) & 3]))
                r &= 4294967295
                # 魔改XTEA
                l += ((r << 4) + self._p2(rilv1224[4])) ^ (r + v1225) ^ ((r >> 5) + self._p2(rilv1224[5]))
                l &= 4294967295
                r += ((l << 2) + self._p2(rilv1224[6])) ^ (l + v1225) ^ ((l >> 7) + self._p2(rilv1224[7]))
                r &= 4294967295
                tmps = b''.join([x.to_bytes(4'little'for x in [l, r]])
                lv770 = []
                for j in range(0, len(tmps), 2):
                    lv770.append(int.from_bytes(tmps[j:j+2], 'big'))
                assert len(lv770) == 4
                # 另外半轮魔改IDEA
                a, b, c, d = lv770 # v1242, v1241, v1240, v1239
                v1196 = self._p1((a ^ c) & 0xFFFF, randl[v1232 * 6 + 4])
                v835 = self._p1(((b ^ d) + v1196) & 0xFFFF, randl[v1232 * 6 + 5]) & 0xFFFF
                v1198 = (v1196 + v835) & 0xFFFF
                v840 = a ^ v835
                v841 = d ^ v1198
                v842 = b ^ v1198
                v91 = c ^ v835
                # 奇数轮和偶数轮交接给下一轮的变量不同
                if v1232 & 1 != 0:
                    v1230 = v91
                    v1229 = v842
                else:
                    v1230 = v842
                    v1229 = v91
                v1222 = v841
                v1227 = v840
            # 最后半轮魔改IDEA
            v1227 = self._p1(v1227 & 0xFFFF, randl[48]) & 0xFFFF
            v1229 = (v1229 + randl[49]) & 0xFFFF
            v1230 = (v1230 + randl[50]) & 0xFFFF
            v1222 = self._p1(v1222 & 0xFFFF, randl[51]) & 0xFFFF
            ll = [v1227, v1229, v1230, v1222]
            lv918 = []
            for j in range(len(ll)):
                lv918.append(ll[j].to_bytes(2'big'))
            tmps = b''.join(lv918)
            print(list(tmps))
            rslt += tmps
            # 真正的比对在这里(
            self.tips += sum([tmps[j] ^ random.getrandbits(8for j in range(len(tmps))])
        print(self.tips, list(rslt))
        # 没用的返回值,真随机
        return [x ^ y for x, y in zip(os.urandom(len(rslt)), rslt)]

if __name__ == '__main__':
    c = chal("a"*48# 测试

其中关键点在于,其对比不是用self._var2self._var3进行对比,而是用当前轮的random.getrandbits(8)进行对比,xor为0表示相同,只要最后self._tips == 0就算成功。所以,比对数据实际上是一组伪随机数被这个思维误区限制,当时一直没有解出来,直到现在解出才发现其实就差最后一步了。

根据以上手工恢复出的源码写解题脚本:

import random

# 假密文!!!
dst = [1217314114611523018165238171467322882188661214822566255254472216325022213335232106176]
dst += [122431331477362949226211156561427825412]
random.seed(sum([2654435769-len(dst)] + [ord(x) for x in str(len(dst))]))

r_randl = []
r8_v1225 = []
r8_rilv1224 = []
r8_rilv1228 = []
r8_rand8b = []
for _ in range(0, len(dst), 8):
    r_randl.append([random.getrandbits(16for _ in range(52)])
    tmpl = []
    for i in range(8):
        tmpl.append(random.randint(0x9e3779b90xc6ef3720))
        r8_rilv1224.append([random.randint(0x56AA33500xa3b1bac6for _ in range(8)])
        r8_rilv1228.append([random.randint(0x677D91970xb27022dcfor _ in range(8)])
        if i & 1 == 1:
            tmpl.append(random.getrandbits(32))
    r8_v1225 += tmpl[::-1]
    r8_rand8b.append([random.getrandbits(8for _ in range(8)])
v1225_iter = iter(r8_v1225)
# 真密文
dst = sum(r8_rand8b, [])

def _p1(a, b):
    c = a * b
    cl = c & 0xFFFF
    ch = (c >> 16) & 0xFFFF
    if cl - ch < 0:
        return cl - ch + 1
    else:
        return cl - ch

def _p2(i):
    l = [2141442332542042256118322182201944025144543103154118421904195170681938731346153156668024414523915212251841167237207172982281792816920182321491282231482501171436316671716725224311523186131896025230133791681041071291781131002181392482351575112861575330361494998820916237341245913312013521207087159211398276542231160196200158234191138210641995618116324724220624997211612241749316415552268517314750482451401772272924622646130102202961924135171138378111213219556922225314247325510611410910891811412717514618722118812717217926531169021610193491361652051231894511620818184229180176137105151741215011912610118524191971101981322424012523658220773212123895622152035772]
    return int.from_bytes(bytes(map(lambda x: l[x], i.to_bytes(4'little'))), 'little')

def p1_rev(ret, b):
    for i in range(0x10000):
        if ret == (_p1(i, b) & 0xFFFF):
            return i

res = b''
for xi in range(0, len(dst), 8):
    randl = r_randl[xi//8]
    l8 = dst[xi:xi+8]
    l4 = [int.from_bytes(bytes(l8[i:i+2]), 'big'for i in range(0, len(l8), 2)]
    v1227, v1229, v1230, v1222 = l4
    v1227 = p1_rev(v1227, randl[48]) & 0xFFFF
    v1229 = (v1229 - randl[49]) & 0xFFFF
    v1230 = (v1230 - randl[50]) & 0xFFFF
    v1222 = p1_rev(v1222, randl[51]) & 0xFFFF
    for i in range(8)[::-1]:
        rilv1224 = r8_rilv1224[xi + i]
        rilv1228 = r8_rilv1228[xi + i]
        v841 = v1222
        v840 = v1227
        if i & 1 != 0:
            v91 = v1230
            v842 = v1229
        else:
            v842 = v1230
            v91 = v1229
        a_xor_c = v840 ^ v91
        b_xor_d = v841 ^ v842
        v1196 = _p1(a_xor_c, randl[i * 6 + 4]) & 0xFFFF
        v835 = _p1((b_xor_d + v1196) & 0xFFFF, randl[i * 6 + 5]) & 0xFFFF
        v1198 = (v1196 + v835) & 0xFFFF
        a = v840 ^ v835
        b = v842 ^ v1198
        c = v91 ^ v835
        d = v841 ^ v1198
        l4 = [a, b, c, d]
        s = b''.join([x.to_bytes(2'big'for x in l4])
        l, r = [int.from_bytes(s[j:j+4], 'little'for j in range(0, len(s), 4)]
        v1225 = next(v1225_iter)
        r -= ((l << 2) + _p2(rilv1224[6])) ^ (l + v1225) ^ ((l >> 7) + _p2(rilv1224[7]))
        r &= 4294967295
        l -= ((r << 4) + _p2(rilv1224[4])) ^ (r + v1225) ^ ((r >> 5) + _p2(rilv1224[5]))
        l &= 4294967295
        r -= (((l << 4) ^ (l >> 5)) + l) ^ (v1225 + _p2(rilv1228[(v1225 >> 11) & 3]))
        r &= 4294967295
        l -= (((r << 3) ^ (r >> 6)) + r) ^ (v1225 + _p2(rilv1228[v1225 & 3]))
        l &= 4294967295
        if i & 1 == 1:
            v1225 = next(v1225_iter)
        r -= (((l << 4) ^ (l >> 5)) + l) ^ (v1225 + _p2(rilv1228[4 + ((v1225 >> 11) & 3)]))
        r &= 4294967295
        l -= (((r << 5) ^ (r >> 4)) + r) ^ (v1225 + _p2(rilv1228[4 + (v1225 & 3)]))
        l &= 4294967295
        r -= ((l << 5) + _p2(rilv1224[2])) ^ (l + v1225) ^ ((l >> 4) + _p2(rilv1224[3]))
        r &= 4294967295
        l -= ((r << 4) + _p2(rilv1224[0])) ^ (r + v1225) ^ ((r >> 5) + _p2(rilv1224[1]))
        l &= 4294967295
        s = b''.join([x.to_bytes(4'little'for x in [l, r]])
        v1227, v1229, v1230, v1222 = [int.from_bytes(s[j:j+2], 'big'for j in range(0, len(s), 2)]
        v1227 = p1_rev(v1227, randl[6 * i + 0]) & 0xFFFF
        v1229 = (v1229 - randl[6 * i + 1]) & 0xFFFF
        v1230 = (v1230 - randl[6 * i + 2]) & 0xFFFF
        v1222 = p1_rev(v1222, randl[6 * i + 3]) & 0xFFFF
    res += b''.join([x.to_bytes(2'big'for x in [v1227, v1229, v1230, v1222]])

L = [1737131631411801931562119865218132161481051659625012116823944979120101211167240751364370115203220341601882226116911795134174167]
flag = []
for i in range(len(res)):
    flag.append(res[i] ^ L[i%len(L)])
print(bytes(flag))
# b'DASCTF{c6090fd29eaf2ae1d111289e3f3c0c7a3819dcc1}'

docCrack

docm解压可以在./word路径中看到vbaProject.bin,用pcode2code vbaProject.bin -o res可以得到vb代码,找到主要部分:

2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

读取flag并逐个异或7。

2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

将yyjskEVAnLNVYXPjnqeGNhKNvXlbSIslOoqViLKkb解码成temp.exe并运行temp.exe以检查flag,如果输出是”good”说明flag正确。

写脚本将yyjskEVAnLNVYXPjnqeGNhKNvXlbSIslOoqViLKkb解码:

# ... 从vb脚本搬过来,省略
yyjskEVAnLNVYXPjnqeGNhKNvXlbSIslOoqViLKkb = BumFQkMgMUNykMNviBlfdZHOyVThmijmcNTUNaXSNHYdNzXKRqhYiKlaRzCfBlcoN + dWPtWzWvKrZRFrsAWZMGNjZQbCrgAImKXVUkOykXWeRltpUU + AMaKeZzlhAtdNANKAKwMNbKEKUWuQVZQbbCJIUog + BvEKpalonhsRIgbPkYPYbsbQGzIzvPitapncgtGKIo + yBILPYnXCUApVHExOtpKlnfTkVfexwgrFQOFIveA + pqdgalQAZJKIDySPundFqdITahrgAYveJXfZCOUHWnUDKXZwZU + pErQJcjFIvYQeIehtTPMaOgEwFvvjnaTkabtJDvpHbWG

import base64
# 测试套了两层base64
res = base64.b64decode(yyjskEVAnLNVYXPjnqeGNhKNvXlbSIslOoqViLKkb)
res = base64.b64decode(res)
with open("temp.exe""wb"as f:
    f.write(res)

反编译后跟踪字符串”good”找到输出函数0x140011860:

  v9[0] = 4288;
// ... v9赋值,省略
  v9[53] = 7808;
  if ( a1 == 2 )
  {
    for ( j = 0; j < (int)j_strlen(*(const char **)(a2 + 8)) && (unsigned __int64)j < 0x36; ++j )
      v9[j + 64] = *(char *)(*(_QWORD *)(a2 + 8) + j) << 6;
    for ( j = 0; (unsigned __int64)j < 0x36; ++j )
    {
      if ( v9[j] != v9[j + 64] )
      {
        printf("bad");
        v4 = 0i64;
        goto LABEL_16;
      }
    }
    printf("good");
    v4 = 0i64;

可以看到是逐位左移6。

写脚本解密即可:

# v9
dst = [428844805376435253124160793651846464652856323456742456326336652867206144627274886656729674242432243224325632441634567168652874886272563235206208563247366528640074883520563251843456748872963200627274242432243224327808]
for i in range(len(dst)):
    dst[i] = (dst[i] >> 6) ^ 7
print(bytes(dst))
# b'DASCTF{Vba_1s_dangerous!!!_B1ware_0f_Macr0_V1ru5es!!!}'

你这主函数保真么

通过字符串定位到输入和检查的函数

// input
puts("Please input your flag~");
scanf("%s", flag);
len = strlen(flag);
if ( len != 33 )
{
  puts("Len error!");
  exit(0);
}
// check
for ( i = 0; i <= 32; ++i )
{
  if ( std::abs(check[i] - in[i]) > (long double)(double)0.0001 )
  {
    puts("Wrong!!");
    exit(0);
  }
}
puts("Right!!");

check数组是已知数组,in是输入后经过变换的数组。

交叉查找flag数组的引用可以看到一个rot13:

rot13_encrypt(flag);

和一个encrypt:

for ( i = 0strlen(flag) > i; ++i )
{
  v2 = flag[i];
  *std::vector<int>::operator[]((std::vector<int> *const)i, v11) = v2;
}
encrypt((std::vector<double> *)((char *)__n + 1), &input);

encrypt里面是一个一维DCT:

std::vector<double> *__cdecl encrypt(std::vector<double> *retstr, const std::vector<int> *input)
{
  // ...
  for ( i = 0; i < size; ++i )
  {
    for ( j = 0; j < size; ++j )
    {
      v5 = (double)*std::vector<int>::operator[]((const std::vector<int> *const)j, v10);
      v2 = cos(((long double)j + 0.5) * ((long double)i * 3.141592653589793) / (long double)size);
      v6 = v2 * v5;
      v3 = std::vector<double>::operator[]((std::vector<double> *const)i, v11);
      *v3 = *v3 + v6;
    }
    if ( i )
      v4 = sqrt(2.0 / (long double)size);
    else
      v4 = sqrt(1.0 / (long double)size);
    v7 = v4;
    eax9 = std::vector<double>::operator[]((std::vector<double> *const)i, v12);
    *eax9 = *eax9 * v7;
  }
}

所以得到flag的过程就是把比对数组经过一维DCT的逆变换(IDCT)然后rot13。IDCT从github上搜了一个cpp的改成Python,+rot13一起写脚本:

from math import sqrt, cos, pi

l = [513.355-37.79868.7316-10.7832-1.3097-20.57796.98641-29.298915.942221.413829.4754-2.77161-6.58794-4.22332-7.207718.83506-4.38138-19.389818.34536.88259-14.765214.610224.7414-11.6222-9.75475999999999912.242413.4343-34.9307-35.735-20.084839.68921.87926.8296]
n = len(l)

# IDCT from https://github.com/lquatrin/CGT1-1D_DCT_IDCT/blob/master/CG_T1/src/MyDCTS.cpp
flag = []
for i in range(n):
    x = 0
    for j in range(n):
        if j != 0:
            x += l[j] * cos(((((2.0*i)+1.0)*j)*pi)/(2.0*n))
        else:
            x += l[j] * (1.0 / sqrt(2)) * cos(((((2.0*i)+1.0)*j)*pi)/(2.0*n))
    flag.append(round(sqrt(2.0 / n) * x))

# rot13
for i in range(n):
    if flag[i] in range(ord('A'), ord('Z')+1):
        base = ord('A')
    elif flag[i] in range(ord('a'), ord('z')+1):
        base = ord('a')
    else:
        continue
    flag[i] = (flag[i]-base+13) % 26 + base
print(bytes(flag))
# b'DASCTF{Wh0_1s_Ma1n_@nd_FunnY_Dct}'

pic

爆破RC4密钥题,需要我们输入长度为5的密钥,main_NewCipher是个RC4的init,用我们输入的主密钥初始化s盒。

后续部分动态调试看汇编,过掉反调试后随便输入5字节(这里输入"abcde",便于定位顺序)

断点下在0x49DB36,可以看到r9是输入的flag.png图片的第1个字节x85,而ebx是我们测试输入的key[1]x62),可以知道密文先xor了key[1]

.text:000000000049DB31
.text:000000000049DB31 loc_49DB31:
.text:000000000049DB31 movzx   r9d, byte ptr [rsi+rbx]
.text:000000000049DB36 xor     r9d, edx
.text:000000000049DB39 mov     [rax+rbx], r9b
.text:000000000049DB3D inc     rbx

然后动态跟loc_49DB5F部分:

.text:000000000049DB5F movzx   r11d, byte ptr [rbx+rax]
.text:000000000049DB64 inc     r9d             ; int
.text:000000000049DB67 movzx   r12d, r9b
.text:000000000049DB6B mov     r13d, [rdx+r12*4]
.text:000000000049DB6F add     r10d, r13d      ; int
.text:000000000049DB72 movzx   r15d, r10b
.text:000000000049DB76 mov     esi, r11d
.text:000000000049DB79 mov     r11d, [rdx+r15*4]
.text:000000000049DB7D mov     [rdx+r12*4], r11d
.text:000000000049DB81 mov     [rdx+r15*4], r13d
.text:000000000049DB85 mov     r11d, [rdx+r12*4]
.text:000000000049DB89 add     r11d, r13d
.text:000000000049DB8C movzx   r11d, r11b      ; int
.text:000000000049DB90 xor     esi, [rdx+r11*4]
.text:000000000049DB94 xor     esi, 11h
.text:000000000049DB97 mov     [rax+rbx], sil
.text:000000000049DB9B inc     rbx
.text:000000000049DB9E xchg    ax, ax

r11xE7,刚好是0x85 ^ 0x62,说明是上一步xor key[1]后的结果,mov r13d, [rdx+r12*4]mov r11d, [rdx+r15*4]两条指令对应RC4中加密流程的sbox[i]sbox[j],后续的交换、相加和取值也证实了跟RC4加密流程相同,到0x49DB90时[rdx+r11*4]就是sbox[(sbox[i] + sbox[j]) % 256]了,然后再xor了0x11。

所以这里是个魔改的rc4,数据流除了xor密钥流以外还会xorkey[1] ^ 0x11

已知加密前是一张png,那么算法搞出来以后直接爆破密钥(只有5字节,而且因为是输入必然是可见字符),如果明文开头和png文件头相同那么就有可能是正确的key。

写爆破脚本,但是Python太慢了没爆出来,也没有限定字符范围,所以换了c写(爆了15分钟爆出来):

#include <stdio.h>
#include <string.h>

unsigned char S_Box[256] = {0};
// RC4 initial
void Init(unsigned char * key, int keyLen) {
    unsigned char T[256] = {0};
    for (int i = 0; i < 256; i++) {
        S_Box[i] = i;
        T[i] = key[i%keyLen];
    }
    int j = 0;
    for (int i = 0; i < 256; i++) {
        j = (j+S_Box[i]+T[i]) % 256;
        unsigned char tmp = S_Box[i];
        S_Box[i] = S_Box[j];
        S_Box[j] = tmp;
    }
    return;
}

void RC4(unsigned char * key, int keyLen, unsigned char * data, int dataLen) {
    Init(key, keyLen);
    int i = 0, j = 0;
    for (int k = 0; k < dataLen; k++) {
        i = (i+1) % 256;
        j = (j+S_Box[i]) % 256;
        unsigned char tmp = S_Box[i];
        S_Box[i] = S_Box[j];
        S_Box[j] = tmp;
        data[k] ^= S_Box[(S_Box[i]+S_Box[j])%256] ^ key[1] ^ 0x11;
    }
}

unsigned char magic[8] = {13780787113102610}; // png文件头
int isFind(unsigned char * data) {
    for (int i = 0; i < 8; i++) {
        if (data[i] != magic[i]) {
            return 0;
        }
    }
    return 1;
}

int main() {
    int png_size = 552205;
    unsigned char png[552205+1] = {0};
    FILE * fp = fopen("flag.png""rb");
    fread(png, sizeof(char), png_size, fp);
    fclose(fp);
    unsigned char key[5+1] = {0};
    unsigned char data[552205+1] = {0};
    unsigned char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int charset_size = 62;
    unsigned char find = 0;
    for (int i0 = 0; i0 < charset_size; i0++) {
        for (int i1 = 0; i1 < charset_size; i1++) {
            for (int i2 = 0; i2 < charset_size; i2++) {
                for (int i3 = 0; i3 < charset_size; i3++) {
                    for (int i4 = 0; i4 < charset_size; i4++) {
                        key[0] = charset[i0];
                        key[1] = charset[i1];
                        key[2] = charset[i2];
                        key[3] = charset[i3];
                        key[4] = charset[i4];
                        memcpy(data, png, png_size);
                        RC4(key, 5, data, png_size);
                        if (isFind(data)) {
                            find = 1;
                            printf("find key: %sn", key);
                            fp = fopen("res.png""wb");
                            fwrite(data, sizeof(char), png_size, fp);
                            fclose(fp);
                            break;
                        }
                    }
                    if (find) break;
                }
                if (find) break;
            }
            if (find) break;
        }
        if (find) break;
    }
    return 0;
}

同时可以看到目录下有爆出来的图片,就是flag:

2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

sedRust_happyVm

是一个不太传统的vm题,通过代码可以大概恢复出vm的结构体:

00000000 vm struc ; (sizeof=0x420, align=0x8, mappedto_64)
00000000   l1 db 1024 dup(?)
00000400   l2 dw 8 dup(?)
00000410   l3 db 8 dup(?)
00000418   r1 db ?
00000419   r2 db ?
0000041A   r3 db ?
0000041B   r4 db ?
0000041C   check_flag db ?
0000041D   db ? ; undefined
0000041E   db ? ; undefined
0000041F   db ? ; undefined
00000420 vm ends

通过动态调试可以知道,函数0x42DE40是输入,输入以后进行了flag格式的判断(DASCTF{}),其中校验末尾大括号的地方可以看出flag总长度是40:

  if ( *(input + 39) != 125 )
    goto LABEL_251;

下一个do-while循环实际上是将3个字节一组(共24bits,3*8)转换成4个字节(4*6,每字节6bits,前面补零):

  do                                            // 3*8 -> 4*6
  {
    i0 = i_add_2 - 2;
    if ( i_add_2 - 2 >= v97 )
    {
      v189 = &off_443350;
      goto LABEL_261;
    }
    i0 = i_add_2 - 1;
    if ( i_add_2 - 1 >= v97 )
    {
      v189 = &off_443368;
      goto LABEL_261;
    }
    if ( i_add_2 >= v97 )
    {
      i0 = i_add_2;
      v189 = &off_443380;
LABEL_261:
      panic(i0, v97, v189);
    }
    inp_i_0 = *(input_inner_32 + i_add_2 - 2);
    inp_i_1 = *(input_inner_32 + i_add_2 - 1);
    inp_i_2 = *(input_inner_32 + i_add_2);
    if ( v102 == v101 )
    {
      sub_40A440(lpMem);
      v102 = lpMem[3];
      v101 = lpMem[0];
      v98 = lpMem[1];
      v100 = lpMem[2];
    }
    v109 = &v100[v102];
    v110 = 0i64;
    if ( v109 >= v101 )
      v110 = v101;
    *(v98 + v109 - v110) = inp_i_0 >> 2;
    v111 = (v102 + 1);
    lpMem[3] = v111;
    v101 = lpMem[0];
    if ( v111 == lpMem[0] )
    {
      sub_40A440(lpMem);
      v101 = lpMem[0];
      v111 = lpMem[3];
    }
    v112 = ((inp_i_1 >> 4) | (16 * inp_i_0)) & 0x3F;
    v98 = lpMem[1];
    v100 = lpMem[2];
    v113 = 0i64;
    if ( lpMem[2] + v111 >= v101 )
      v113 = v101;
    *(lpMem[1] + lpMem[2] + v111 - v113) = v112;
    v114 = v111 + 1;
    lpMem[3] = v114;
    if ( v114 == v101 )
    {
      sub_40A440(lpMem);
      v114 = lpMem[3];
      v101 = lpMem[0];
      v98 = lpMem[1];
      v100 = lpMem[2];
    }
    v115 = ((inp_i_2 >> 6) | (4 * inp_i_1)) & 0x3F;
    v116 = 0i64;
    if ( &v100[v114] >= v101 )
      v116 = v101;
    *(v98 + &v100[v114] - v116) = v115;
    v117 = v114 + 1;
    lpMem[3] = v117;
    if ( v117 == v101 )
    {
      sub_40A440(lpMem);
      v117 = lpMem[3];
      v101 = lpMem[0];
      v98 = lpMem[1];
      v100 = lpMem[2];
    }
    v103 = inp_i_2 & 0x3F;
    v104 = 0i64;
    if ( &v100[v117] >= v101 )
      v104 = v101;
    *(v98 + &v100[v117] - v104) = v103;
    v102 = (v117 + 1);
    lpMem[3] = v102;
    i_add_2 += 3i64;
  }
  while ( i_add_2 != 35 );

这里处理的数据是flag格式内的核心flag(input_inner_32),将32字节(实际上是33,末尾x00)转换成了44字节,后续的校验都是针对这44字节进行。

后面以2字节为一组,进行了22次类似操作:

enc(&Src, (v124 << 8) + (inp_32to44[v120 - v122 + 1] << 16) - 0x4EFFFFE80x3000201u);

其中第一个参数Src是存vm过程数据的结构体,第二个参数是两个字节和一个常量组合的操作(如这里是0xb1000018,将两字节填进去),第三个参数是顺序调整,经过顺序调整后会将常量放在前两个字节(大端序),而被校验的两个字节放在后面,用Python还原即:

# 提取出的22次enc的参数,用0000暂代被校验的两字节
l = [(0xb10000180x03000201), (0xa40900000x03020100), (0x002aa6000x02010003), (0x001b009e0x02000103), (0x005700960x02000103), (0x00ad005d0x02000103), (0xae7500000x02030100), (0x0065ac000x01020300), (0x00008c090x01000203), (0x000076a00x01000203), (0x472c00000x02030100), (0x100000010x00030201), (0x007c000f0x00020301), (0x00ba00470x00020301), (0x009530000x01020003), (0x74009b000x03010200), (0x2d00003f0x03000102), (0x00009a2d0x01000203), (0x000031870x01000302), (0x0000ba430x00010302), (0x00002c700x01000302), (0x56004c000x03010200)]
for t in l:
    rslt = 0
    for i in range(4):
        x = (t[1] >> (i * 8)) & 0xFF
        rslt |= ((t[0] >> (x * 8)) & 0xFF) << (i * 8)
    print(hex(rslt))

函数0x40AA60(f1)和函数0x40A800(f2)都是vm运行的过程,其中f2还包括校验部分,由运行主函数中的这一部分可知,当Src.check_flag为0时输入即为flag。

  if ( !Src.check_flag )
  {
    lpMem[0] = &off_443340; // "You Get FLAG!n"
    lpMem[1] = 1;
    lpMem[2] = &off_442D60;
    *&lpMem[3] = 0i64;
    result = sub_42E480(lpMem);
  }

唯一改变Src.check_flag值的地方在f2中:

        if ( vm->r1 )
          ++vm->check_flag;

经过动态调试(+污点分析)可知,该段代码会被执行44次,可以猜测每个字节的校验结果被存到了vm->r1中,只要其不为0,那么vm->check_flag就不是0,那么就不是flag。

所以我们的目标是让每个字节跑完后的vm->r1都为0。而实际动态调试可以发现,在每次执行该段代码之前,程序会走这一段:

        LOBYTE(v3) = vm->r2;
        vm->r1 ^= v3;
        return v3;

也就是说要让vm->r1 == 0,那么执行这段代码前的vm->r1要等于vm->r2需要注意的是,一个字节的检验流程是走两次该xor代码,然后走一次前面vm->r1的判定。

用脚本把每次跑xor代码的数据抓出来(断点打在0x40A937):

'''
movzx   eax, byte ptr [rsi+419h]
xor     [rsi+418h], al
'''

with open("vm_r""w"as f:
    f.write("")
while True:
    wait_for_next_event(WFNE_CONT, 3)
    rsi = get_reg_value("rsi")
    with open("vm_r""a"as f:
        s = get_bytes(rsi+0x4182)
        get = "0x" + hex(s[0])[2:].rjust(2"0") + " ^ 0x" + hex(s[1])[2:].rjust(2"0") + "n"
        f.write(get)

如下图结果,测了几组可以发现:

  • 无论输入是什么(以测试第3个字节时的第5-6行数据为例),红框位置和黄框位置的数据是不变的,其中黄框数据为前面构造函数enc的第二个参数时的常量中的其中一个字节(如这里的0x09来自0xa4090000)。
  • 蓝框位置数据恒为输入数据经过前面“3字节变4字节”转换后的结果。
  • 绿框位置数据是红框位置数据和蓝框位置数据xor的结果。

2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

即算法可以简化为:

r1 = f(x)
r1 ^= y
r1 ^= C

其中f(x)为密钥流对应字节,y为flag经过“3字节变4字节”转换后的结果,C为参数常量。按照上文分析,f(x)C都是已知,且我们需要让r1 == 0,那么y实际上等于f(x) ^ C

所以在动态调试中把f(x)抓出来跟前面的常量异或、再进行变换即可。

动态调试脚本(断点打在0x40A937):

l = []
for i in range(44*2):
    wait_for_next_event(WFNE_CONT, 3)
    if i & 1 == 0:
        rsi = get_reg_value("rsi")
        s = get_wide_byte(rsi+0x418)
        l.append(s)
print(l)
# [0, 130, 17, 146, 168, 57, 130, 40, 154, 97, 88, 139, 162, 67, 104, 137, 4, 143, 176, 67, 73, 58, 24, 57, 114, 12, 186, 118, 152, 19, 139, 70, 51, 43, 37, 162, 139, 39, 183, 97, 124, 63, 88, 86]

解题脚本:

# from debugging
key1 = [0130171461685713040154978813916267104137414317667735824571141218611815219139705143371621393918397124638886]
# enc's args
key2 = []
l = [(0xb10000180x03000201), (0xa40900000x03020100), (0x002aa6000x02010003), (0x001b009e0x02000103), (0x005700960x02000103), (0x00ad005d0x02000103), (0xae7500000x02030100), (0x0065ac000x01020300), (0x00008c090x01000203), (0x000076a00x01000203), (0x472c00000x02030100), (0x100000010x00030201), (0x007c000f0x00020301), (0x00ba00470x00020301), (0x009530000x01020003), (0x74009b000x03010200), (0x2d00003f0x03000102), (0x00009a2d0x01000203), (0x000031870x01000302), (0x0000ba430x00010302), (0x00002c700x01000302), (0x56004c000x03010200)]
for t in l:
    for i in range(4):
        x = (t[1] >> (i * 8)) & 0xFF
        if i in [23]:
            key2.append((t[0] >> (x * 8)) & 0xFF)
# get flag's data
flag_data = []
for i in range(len(key1)):
    flag_data.append(key1[i] ^ key2[i])
flag = []
for i in range(0, len(flag_data), 4):
    b = ""
    for j in range(4):
        b += bin(flag_data[i+j])[2:].rjust(6'0')
    flag += [int(b[i:i+8], 2for i in range(0248)]
print(b"DASCTF{" + bytes(flag)[:-1] + b"}")
# b'DASCTF{c669733af3ce4459b88016420b81cb15}'

原文始发于微信公众号(山石网科安全技术研究院):2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇

版权声明:admin 发表于 2024年9月5日 上午11:18。
转载请注明:2024年羊城杯粤港澳大湾区网络安全大赛WP-Reverse篇 | CTF导航

相关文章