SUSCTF(DigitalCircuits+hello_word)

官方的WP

DigitalCircuits

这是一个pyc反编译的题目

pyc文件转换成py文件

这是一个将pyc打包成地exe文件,首先使用软件解包,这个软件反编译出来地主程序并不是一个pyc文件,需要自己通过instruct这个文件修改这个文件地格式

将pyc文件用unemployed6 编译为py文件,然后得到源代码,分析代码,发现这是一个tea加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# uncompyle6 version 3.8.1.dev0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: DigitalCircuits.py
# Compiled at: 1995-09-28 00:18:56
# Size of source mod 2**32: 257 bytes
import time

def f1(a, b): # 只有当两个字符都是1的时候才输出为1 与运算&
if a == '1':
if b == '1':
return '1'
return '0'


def f2(a, b): # 有1则1 或运算|
if a == '0':
if b == '0':
return '0'
return '1'


def f3(a): # 取反运算~
if a == '1':
return '0'
if a == '0':
return '1'


def f4(a, b): # 当一个字符相同的时候返回0 当两个字符不同的时候返回1 异或运算
# f1(a, f3(b) : a&(~b)
# f1(f3(a), b) : (~a)&b
# f2(f1(a, f3(b)), f1(f3(a), b)) : (a&(~b))|((~a)&b)
return f2(f1(a, f3(b)), f1(f3(a), b))


def f5(x, y, z):
# f4(x, y) : d = (x&(~y))|((~x)&y) = x^y
# f4(d, z) : ((x^y)&(~z))|((~(x^y))&z) 若z是0时 x和y要不同才返回1 若z是1时 x和y要相同 才返回1
s = f4(f4(x, y), z)
# f1(x, y) : x&y
# f2(x, y) : x|y
# f1(z, f2(x, y)) : z&(x|y)
# f2(f1(x, y), f1(z, f2(x, y))) : (x&y)|(z&(x|y)) 当x和y都是1时 或者 z是1 x和y其中一个是1时 返回1
c = f2(f1(x, y), f1(z, f2(x, y)))
return (s, c)


def f6(a, b):
ans = ''
z = '0'
a = a[::-1] # 将其倒过来
b = b[::-1] # 将其倒过来
for i in range(32): # 循环32次
ans += f5(a[i], b[i], z)[0] # 单个字符
z = f5(a[i], b[i], z)[1] # 单个字符

def f6(a, b):
ans = ''
z = '0'
a = a[::-1] # 将其倒过来
b = b[::-1] # 将其倒过来
for i in range(32): # 循环32次
ans += ((a[i]^b[i])&(~z))|((~(a[i]^b[i]))&z) # 单个字符 ((x^y)&(~z))|((~(x^y))&z)
z = (a[i]&b[i])|(z&(a[i]|b[i])) # 单个字符
return ans[::-1]

# 向左移4位
def f7(a, n):
return a[n:] + '0' * n

# 向右移5位
def f8(a, n):
return n * '0' + a[:-n]


def f9(a, b):
ans = ''
for i in range(32):
ans += f4(a[i], b[i]) # 逐项比较每一位数,如果相同则加0 如果不同则加1

return ans


def f10(v0, v1, k0, k1, k2, k3):
s = '00000000000000000000000000000000'
d = '10011110001101110111100110111001'
for i in range(32):
s = f6(s, d)
# f7(v1, 4) v1<<4 f8(v1, 5) v1>>5
# f6(v0, f9(f9(f6(v1<<4, k0), f6(v1, s)), f6(v1>>5, k1)))
v0 = f6(v0, f9(f9(f6(f7(v1, 4), k0), f6(v1, s)), f6(f8(v1, 5), k1)))
v1 = f6(v1, f9(f9(f6(f7(v0, 4), k2), f6(v0, s)), f6(f8(v0, 5), k3)))


return v0 + v1


k0 = '0100010001000101'.zfill(32) # 0x4445
k1 = '0100000101000100'.zfill(32) # 0x4144
k2 = '0100001001000101'.zfill(32) # 0x4245
k3 = '0100010101000110'.zfill(32) # 0x4546
flag = input('please input flag:')
if flag[0:7] != 'SUSCTF{' or flag[(-1)] != '}':
print('Error!!!The formate of flag is SUSCTF{XXX}')
time.sleep(5)
exit(0)
flagstr = flag[7:-1]
if len(flagstr) != 24:
print('Error!!!The length of flag 24')
time.sleep(5)
exit(0)
else:
res = ''
# 将这里面的字符串每8个分为一组
for i in range(0, len(flagstr), 8):
v0 = flagstr[i:i + 4]
v0 = bin(ord(flagstr[i]))[2:].zfill(8) + bin(ord(flagstr[(i + 1)]))[2:].zfill(8) + bin(ord(flagstr[(i + 2)]))[2:].zfill(8) + bin(ord(flagstr[(i + 3)]))[2:].zfill(8)
v1 = bin(ord(flagstr[(i + 4)]))[2:].zfill(8) + bin(ord(flagstr[(i + 5)]))[2:].zfill(8) + bin(ord(flagstr[(i + 6)]))[2:].zfill(8) + bin(ord(flagstr[(i + 7)]))[2:].zfill(8)
res += f10(v0, v1, k0, k1, k2, k3)

if res == '001111101000100101000111110010111100110010010100010001100011100100110001001101011000001110001000001110110000101101101000100100111101101001100010011100110110000100111011001011100110010000100111':
print('True')
else:
print('False')
time.sleep(5)

tea算法

tea 的密钥是:0x4445,0x4144,0x4245,0x4546

最后的加密结果:[0x3e8947cb,0xcc944639,0x31358388,0x3b0b6893,0xda627361,0x3b2e6427]

dalt:0x9e3779b9

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include<windows.h>
using namespace std;

void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}

int main()
{
uint32_t v1[3]={0x3e8947cb,0xcc944639,0x0},k[4]={0x4445,0x4144,0x4245,0x4546};
uint32_t v2[3]={0x31358388,0x3b0b6893,0x0};
uint32_t v3[3]={0xda627361,0x3b2e6427};
decrypt(v1, k);
decrypt(v2, k);
decrypt(v3, k);
printf("解密后的数据:%s %s %s\n",(char*)v1,(char*)v2,(char*)v3);
return 0;
}
fvBXQdEa rcbvhBPx cOA8Ag6J

在输入数据的时候:从图中我们可以看到每组字符串中(每4个字符为一组)它将低位的字符放到二进制的高位,所以最后的结果还需要调整字符串的顺序,逆序取出每组数据

image-20220226130532348

最后的flag是 SUSCTF{XBvfaEdQvbcrxPBh8AOcJ6gA}

hello_word

这个题目考察vhdl

定位代码

通过字符串找到一个含有inputflag页面的函数,在里面出现了有vhdl

image-20220226224203141

VHDL介绍

GHDL是一个基于GCC的VHDL语言编译/模拟命令行工具

通过使用代码生成器(LLVMGCC或仅x86_64 / i386,内置的),它比任何解释模拟器都要快得多。它可以处理非常大的设计,例如leon3/grlib

可将波形写入GHWVCD或 FST 文件。结合基于GUI的波形查看器和良好的文本编辑器,GHDL 是用于编写、测试和模拟代码的非常强大的工具。

image-20220303190451907

状态机介绍

分析代码这是一个状态机

有限状态机(Finite-state machine,FSM):又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型

FSM:是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。

六种元素:起始、终止、现态、次态(目标状态)、动作、条件,我们就可以完成一个状态机图

状态机,也就是 State Machine ,不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

例如:自动门有两个状态,open 和 closed ,closed 状态下,如果读取开门信号,那么状态就会切换为 open 。open 状态下如果读取关门信号,状态就会切换为 closed 。

能够抽象出来的图:

image-20220304122921881

状态机的四大概念:

  • 第一个是 State ,状态。一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。
  • 第二个是 Event ,事件。事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。
  • 第三个是 Action ,动作。事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action一般就对应一个函数。
  • 第四个是 Transition ,变换。也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换。

strip

这道题是被strip了的,下面是对strip概念的解释

strip的作用

so文件组成:一个完整的 so 由C代码加一些 debug 信息组成,这些debug信息会记录 so 中所有方法的对照表,就是方法名和其偏移地址的对应表,也叫做符号表。

未strip的so文件:这种既有c代码和debug信息的 so文件就是未 strip 的so文件,通常体积会比较大

strip操作:so 中的debug信息会被剥离,整个 so 的体积也会缩小。

strip操作解说:

image-20220304124408410

逻辑电路

逻辑电路一般是状态机

逻辑电路:是指完成逻辑运算的电路。这种电路,一般有若干个输入端一个 或几个输出端,当输入信号之间满足某一特定逻辑关系时,电路就开通,有输 出;否则,电路就关闭,无输出。所以,这种电路又叫逻辑门电路,简称门电路。

这里的case语句展示了这样的逻辑电路的构成

这里考察的是VHDL这个程序

case语句的代码的算法分析

定位代码

首先通过字符串inputflag定位到对应的函数,这是一个switch的循环语句

image-20220304130403296

分析这个switch语句

case1-4

case1-4不断地跳转,这个地方地代码是对输入地flag的长度赋值

image-20220304130605519

向下跳转到case9的地方

case9模块

这个地方把输入的值给到vhdl中进行xor模块,然后跳到case10模块

image-20220304132901912

case10模块

调用vhdl的异或的方法,并且在这里可以知道一个用于异或运算的数组

image-20220304133058776

image-20220304133113178

数组是

1
[0x56,0xda,0xcd,0x3a,0x7e,0x86,0x13,0xb5,0x1d,0x9d,0xfc,0x97,0x8c,0x31,0x6b,0xc9,0xfb,0x1a,0xe2,0x2d,0xdc,0xd3,0xf1,0xf4,0x36,0x09,0x20,0x42,0x04,0x6a,0x71,0x53,0x78,0xa4,0x97,0x8f,0x7a,0x72,0x39,0xe8,0x3d,0xfa,0x40,0x3d,0x98,0x01]

case11

将异或的结果和最后的答案对比

image-20220304133511919

对比使用的数组:

image-20220304133538057

数组:

1
[0x05,0x8f,0x9e,0x79,0x2a,0xc0,0x68,0x81,0x2d,0xfc,0xcf,0xa4,0xb5,0x55,0x5f,0xe4,0x9d,0x23,0xd6,0x1d,0xf1,0xe7,0x97,0x91,0x06,0x24,0x42,0x71,0x3c,0x58,0x5c,0x30,0x19,0xc6,0xf5,0xbc,0x4b,0x42,0x5d,0xda,0x58,0x9b,0x24,0x40]

case 5模块

通过表示正确字符个数的correct判断最后的结果,错误就跳转到wrong的case5这个模块

image-20220304134147758

脚本:

1
2
3
4
5
obj1 = [0x05,0x8f,0x9e,0x79,0x2a,0xc0,0x68,0x81,0x2d,0xfc,0xcf,0xa4,0xb5,0x55,0x5f,0xe4,0x9d,0x23,0xd6,0x1d,0xf1,0xe7,0x97,0x91,0x06,0x24,0x42,0x71,0x3c,0x58,0x5c,0x30,0x19,0xc6,0xf5,0xbc,0x4b,0x42,0x5d,0xda,0x58,0x9b,0x24,0x40]
obj2=[0x56,0xda,0xcd,0x3a,0x7e,0x86,0x13,0xb5,0x1d,0x9d,0xfc,0x97,0x8c,0x31,0x6b,0xc9,0xfb,0x1a,0xe2,0x2d,0xdc,0xd3,0xf1,0xf4,0x36,0x09,0x20,0x42,0x04,0x6a,0x71,0x53,0x78,0xa4,0x97,0x8f,0x7a,0x72,0x39,0xe8,0x3d,0xfa,0x40,0x3d,0x98,0x01]
for i in range(len(obj1)):
print(chr(obj1[i]^obj2[i]),end='')
SUSCTF{40a339d4-f940-4fe0-b382-cabb310d2ead}

所以最后的flag是 SUSCTF{40a339d4-f940-4fe0-b382-cabb310d2ead}