队伍成绩:学生组第一
这比赛确实没啥难度,毕竟只能省里的打,属于是圈地自萌了,和同期的祥云杯完全不是一个难度的。

lcs

HMGK-being_hacked[一血]

这题刚开始没啥头绪,想找tls流量结果全是正常的网站访问流量,然后按流量排序发现有一堆icmp流量,这在正常流量包中并不常见。

图 1

那就筛选一下icmp请求包,因为一般返回包没啥东西:icmp.type == 8

然后就发现每个包的长度都不太一样。

图 2

然后102正好是f的ascii码,但是后面几个就对应不上了。

这时候发现icmp请求包里面的seq并不是顺序发送的,于是尝试按seq排序。

图 3

结果发现不止一个seq==1,比对了下id==1的才是f,那就筛选icmp.type == 8 && icmp.ident == 1

图 4

提取出来就是102 108 97 103 123 50 51 51 51 51 45 115 111 45 101 97 115 121 45 105 99 109 112 125,转成文本就是flag{23333-so-easy-icmp}

flag{23333-so-easy-icmp}

HNGK-LAD Cal[三血]

做这题的时候其实对于plc是一无所知的,于是就下了个STEP 7 Micro/WIN SMART,然后手动把梯形图给重新画了一遍。

图 5

然后发现这软件很贴心的有每个运算的中文说明。

图 6

于是就一个一个运算用js模拟过去。

图 7

图 8

碰到未知的变量就开始倒着推。

图 9

然后V13=40BCD编码是01000000,所以flag就出来了。

flag{V8_80_V11_5_V1_01000000}

Reverse

HNGK-数独[二血]

图 10

这题主要就是for循环解析输入,然后4110e6里面处理输入去填入数独,看看竖着、横着、3*3的是不是都满足和为45==1+...+9

图 11

但是这题有一点需要注意,在for循环解析输入的时候,实际上并没有走v10[i] = Str[v11] - 48这一句,因为上面有个try/except操作,然后直接下一句就访问异常内存地址触发了,所以需要去看except的逻辑,ida这里没反编译出来,得看汇编。

图 12

实际上也不复杂,就是在off_41A000[4*9]里面循环查找输入字符的下标(起始值是1).

所以把41A028的数独提取出来在线解一下。

图 14

图 13

所以输入的下标就是5619238183457621978469254539786692871328563671281793452

那么写个脚本去找每个下标对应的字符就行。

a = [i-ord('0') for i in b"5619238183457621978469254539786692871328563671281793452"]
mask = [
    "{hXxAjYka",
    "cX+8>h#<Z",
    "9g<dqAQNx",
    "Zp4Ccq3QL",
    "jorvP+Ox3",
    "oPOBiHD5r",
    ";gHuRa@yI",
    "fmO2=K9br",
    "ZMeO6Qz2K",
]
for i, x in enumerate(a):
    print(mask[i%9][x-1], end="")

图 15

图 16

flag{Ah9LoOyf2X8q3+P;rzk8ALoiu=ea#Nq+rgbz{+gQPHHKz{XNZOrH26h}

HNGK-py字节码[一血]

这题其实没啥好说的,因为没有很冷门的字节码,都是挺常见的写法,所以边写正向脚本边python -m dis task.py比对就行。

图 17

然后就写了个和题目给的字节码一模一样的正向脚本出来。


a = 17
b = 13

def rand():
    global seed
    seed = (a * seed + b) % 128
    return seed
print('please input your flag:')
flag = str(input())

assert len(flag) >= 20

seed = ord(flag[19])
enc = [102, 3, 46, 0, 78, 102, 103, 57, 116, 63, 110, 127, 121, 59, 57, 33, 49, 11, 110, 18, 6]
data = [102, 50, 35, 35, 35, 17, 67, 35, 69, 35, 51, 34, 35, 69, 35, 69, 35, 51, 34, 35, 153]
for i in range(len(flag)):

    tmp = data[i] ^ i ^ (rand() % 128)
    data[i + 1] = ord(flag[i]) ^ tmp

    if data[i + 1] != enc[i + 1]:
        print('error!')
        exit(0)
print('flag is %s' % flag)

然后这逻辑也不难,主要就是拿flag[19]当随机数种子,然后每轮用data[i]去异或下标和随机结果,再异或flag就得到了加密结果和新的data[i+1]

那就写个爆破种子的脚本。

a = 17
b = 13

def rand():
    global seed
    seed = (a * seed + b) % 128
    return seed

for f0 in range(128):
    seed = f0
    enc = [102, 3, 46, 0, 78, 102, 103, 57, 116, 63, 110, 127, 121, 59, 57, 33, 49, 11, 110, 18, 6]
    data = [102, 50, 35, 35, 35, 17, 67, 35, 69, 35, 51, 34, 35, 69, 35, 69, 35, 51, 34, 35, 153]
    i = 0
    tmp = data[i] ^ i ^ (rand() % 128)
    if chr(enc[i + 1] ^ tmp) == 'f':
        print('seed', f0)

可以得知种子是22(但是22好像不是可见字符...咋可能是flag[19]的值,感觉是题目有问题)

图 18

然后用种子去反向计算每轮的加密前的值,即为flag。

a = 17
b = 13

def rand():
    global seed
    seed = (a * seed + b) % 128
    return seed

for f0 in range(128):
    seed = f0
    enc = [102, 3, 46, 0, 78, 102, 103, 57, 116, 63, 110, 127, 121, 59, 57, 33, 49, 11, 110, 18, 6]
    data = [102, 50, 35, 35, 35, 17, 67, 35, 69, 35, 51, 34, 35, 69, 35, 69, 35, 51, 34, 35, 153]
    i = 0
    tmp = data[i] ^ i ^ (rand() % 128)
    if chr(enc[i + 1] ^ tmp) == 'f':
        print('seed', f0)
        break

seed = f0
enc = [102, 3, 46, 0, 78, 102, 103, 57, 116, 63, 110, 127, 121, 59, 57, 33, 49, 11, 110, 18, 6]
data = [102, 50, 35, 35, 35, 17, 67, 35, 69, 35, 51, 34, 35, 69, 35, 69, 35, 51, 34, 35, 153]
flag = bytearray(len(data)-1)
for i in range(len(flag)):
    tmp = data[i] ^ i ^ (rand() % 128)
    flag[i] = enc[i + 1] ^ tmp
    data[i + 1] = enc[i + 1]
print(flag)

图 19

flag{Pyth0n_1s_yyds}

HNGK-xxtea[二血]

图 20

有轻微的混淆,不过没啥影响,140004038的值就是switch顺序。也就是acb08edf

a只是在输出东西,不用管。

图 21

c在接收输入然后计算长度并判断。

图 22

b的话好像就是换了个地方存输入。

图 23

0走默认,因为没定义,直接就开始xxtea了。

图 24

然后8这边就开始判断加密结果是否一致了。

图 25

这题既然是考xxtea,那大概率又是改了魔数或者算法细节,所以就用偷懒办法。

先随便输入点符合长度的东西(4的倍数就行),跑到8这边if判断的时候下个断点看看预期结果v87是啥。

图 26

rdx指向的这块内存就是预期结果,只不过是倒着的,因为下标是--v87递减的。

然后还要来个^0x56565656的操作才是加密结果,所以写个脚本拿结果。

a = bytearray(bytes.fromhex("DFD2F732"+"817EC5C1"+"C11C8097"+"055BA086"+"07F5F7FA"+"9D36F9EA"))

for i in range(len(a)):
    a[i] ^= 0x56
print(a.hex())

加密结果为8984a164d7289397974ad6c1530df6d051a3a1accb60afbc

图 27

然后再重新跑一下,去xxtea函数开头下个断点,再随便输入点满足长度的东西(这回长度要确保是和预期一样,因为待会要替换)。

图 30

然后根据ida给的传参寄存器情况,数据指针在rcx,加解密标识符在edx,密钥在r8

图 29

咱们要把数据替换成上面的加密结果,然后标识改成负的。

图 31

图 32

图 33

图 34

然后运行到返回,数据就变成了加密之前的,也就是flag。

图 35

flag{W31C0M3_To_Rev3rs3}

HNGK-反调试[三血]

图 36

这题其实看着逻辑挺简单的,就是把输入给* $41A040 % 1031,然后确保是等于1就完事,但是写了脚本发现解不出啥东西。

b = [
    0x0110, 0x03f8, 0x02f5, 0x039b,
    0x017b, 0x00f7, 0x024f, 0x0036,
    0x019f, 0x0117, 0x014d, 0x028a,
    0x0366, 0x00eb, 0x039b, 0x0117,
    0x00b7, 0x00eb, 0x00b0, 0x0036,
    0x0117, 0x0162, 0x027f, 0x0047,
]

for x in b:
    for i in range(10, 128):
        if x * i % 1031 == 1:
            print(chr(i), end="")
            break

于是猜测$41A040可能被修改过。

但是它的引用只有刚才那一处。

图 37

然后下面也没有其他变量命名了,所以就往上找找引用,可能会存在下标初始值大于0的情况。

8字节开外还真有一处。

图 38

跟过去一看,确实是走了不止8字节的步长,所以确实被修改了。

图 39

所以把^ 0x66这个操作给不上,成功得到flag。

b = [
    0x0110, 0x03f8, 0x02f5, 0x039b,
    0x017b, 0x00f7, 0x024f, 0x0036,
    0x019f, 0x0117, 0x014d, 0x028a,
    0x0366, 0x00eb, 0x039b, 0x0117,
    0x00b7, 0x00eb, 0x00b0, 0x0036,
    0x0117, 0x0162, 0x027f, 0x0047,
]

for x in b:
    for i in range(10, 128):
        if (x ^ 0x66) * i % 1031 == 1:
            print(chr(i), end="")
            break

图 40

flag{@nt1_d3bug_Ju5t_s0}

HNGK-签到

这题upx特征明显,直接upx -d脱。

图 41

打开后发现命名空间有混淆,不过问题不大,大致看出来逻辑。

图 42

dwfsxe::dwfsxehandvfiu::handvfiu是初始化预期的运算数据,分别存到v7v8里面,然后接收到输入存进v9,再复制到v5,然后用B::cewrwe23rf去运算三个数据,然后里面顺带比较就完事。

所以直接在B::cewrwe23rf函数开头下个断点,看看初始化的数据是啥。

图 43

先看输入的,在rdi,但是过去看了下,实际上是往下走32字节才是,应该是命名空间也占了一些内存。

图 44

所以v7rsi也要往下找。

图 45

然后v8rdx也是同样的道理。

图 46

然后具体看比较的时候指针是啥。

图 47

图 48

所以v70123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+v8hP&p0!5L^#3NXLs@*QR%L&UN!L)0%Q^

按照函数的逻辑写个脚本成功得到flag。

v7 = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+"
v8 = b"hP&p0!5L^#3NXLs@*QR%L&UN!L)0%Q^"

for i in range(31):
    print(chr(v7.index(v8[i])+ord('0')), end="")

图 49

flag{ActI0n5_sp3ak_Louder_than_w0rds}


The End