babyruby
ruby逆向
工具链接 github.com/mruby/mruby.git
题目下载下来有10个附件 对应10题 这题在线上应该是动态附件 这里挑选0为wp 其余都是一样的操作
拿到手发现是一个mrb文件 使用mruby来反编译
./mruby -v -b chall_0.mrb 2>&1 > 1.txt
or
printf 'x\n' | ./mruby -v -b chall_0.mrb 2>/dev/null | sed '$d' > chall_0_dis.txt
主逻辑依次加载这些类 有S合 有轮数 有key 看起来挺像AES的
Matrix
SBox
State
Util
Consts
Round
Keys
Encryptor
Cipher
然后定义两个顶层方法
092 OP_METHOD R3 I(9:0x63032f97a470)
095 OP_DEF R2 :validchar
100 OP_METHOD R3 I(10:0x63032f97a590)
103 OP_DEF R2 :check
最后读入用户输入并调用 check
108 OP_SEND R2 :gets 0
112 OP_SEND R2 :chomp 0
116 OP_MOVE R1 R2 ; plain_text
123 OP_MOVE R4 R1 ; plain_text
126 OP_SEND R3 :check 1
130 OP_SEND R2 :puts 1
搜索Cipher能看到 是flag{}包裹 再往下能看到是判断长度 38 所以38 - len("flag{") - len("}") = 32 中间32位
irep 0x650c0c414590 nregs=85 nlocals=14 pools=3 syms=15 reps=3 iseq=2087
local variable names:
R1:plain_text
R2:&
R3:content
R4:i
R5:v
R6:step
R7:lst
R8:c
R9:j
R10:wanted
R11:cipher
R12:cc
R13:output
000 OP_ENTER 1:0:0:0:0:0:0
004 OP_MOVE R14 R1 ; R1:plain_text
007 OP_STRING R15 L(0) ; flag{
010 OP_SEND R14 :start_with? 1
014 OP_SEND R14 :! 0
018 OP_JMPIF R14 036
022 OP_MOVE R14 R1 ; R1:plain_text
025 OP_STRING R15 L(1) ; }
028 OP_SEND R14 :end_with? 1
032 OP_SEND R14 :! 0
036 OP_JMPIF R14 054
040 OP_MOVE R14 R1 ; R1:plain_text
043 OP_SEND R14 :length 0
047 OP_LOADI R15 38
050 OP_SEND R14 :!= 1
054 OP_JMPNOT R14 062
也就是
flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}
再往上看 限制中间的字符为可读大小写还有数字
irep 0x646d5a26e470 nregs=6 nlocals=3 pools=0 syms=0 reps=0 iseq=189
local variable names:
R1:v
R2:&
000 OP_ENTER 1:0:0:0:0:0:0
004 OP_MOVE R3 R1 ; R1:v
007 OP_LOADI R4 48
010 OP_LT R3 R4
012 OP_JMPIF R3 024
016 OP_MOVE R3 R1 ; R1:v
019 OP_LOADI R4 122
022 OP_GT R3 R4
024 OP_JMPIF R3 048
028 OP_MOVE R3 R1 ; R1:v
031 OP_LOADI R4 57
034 OP_GT R3 R4
036 OP_JMPNOT R3 048
040 OP_MOVE R3 R1 ; R1:v
043 OP_LOADI R4 65
046 OP_LT R3 R4
048 OP_JMPIF R3 072
052 OP_MOVE R3 R1 ; R1:v
055 OP_LOADI R4 90
058 OP_GT R3 R4
060 OP_JMPNOT R3 072
064 OP_MOVE R3 R1 ; R1:v
067 OP_LOADI R4 97
070 OP_LT R3 R4
072 OP_JMPNOT R3 080
076 OP_LOADI__1 R3
078 OP_RETURN R3
080 OP_MOVE R3 R1 ; R1:v
083 OP_LOADI R4 48
086 OP_GE R3 R4
088 OP_JMPNOT R3 100
092 OP_MOVE R3 R1 ; R1:v
095 OP_LOADI R4 57
098 OP_LE R3 R4
100 OP_JMPNOT R3 112
104 OP_MOVE R3 R1 ; R1:v
107 OP_SUBI R3 48
110 OP_RETURN R3
112 OP_MOVE R3 R1 ; R1:v
115 OP_LOADI R4 65
118 OP_GE R3 R4
120 OP_JMPNOT R3 132
124 OP_MOVE R3 R1 ; R1:v
127 OP_LOADI R4 90
130 OP_LE R3 R4
132 OP_JMPNOT R3 147
136 OP_MOVE R3 R1 ; R1:v
139 OP_SUBI R3 65
142 OP_ADDI R3 10
145 OP_RETURN R3
147 OP_MOVE R3 R1 ; R1:v
150 OP_LOADI R4 97
153 OP_GE R3 R4
155 OP_JMPNOT R3 167
159 OP_MOVE R3 R1 ; R1:v
162 OP_LOADI R4 122
165 OP_LE R3 R4
167 OP_JMPNOT R3 185
171 OP_MOVE R3 R1 ; R1:v
174 OP_SUBI R3 97
177 OP_ADDI R3 36
180 OP_RETURN R3
182 OP_JMP 187
185 OP_LOADNIL R3
187 OP_RETURN R3
然后 check 里第一个 block 是
093 OP_BLOCK R15 I(0:0x63032f97af20)
096 OP_SENDB R14 :each 0
这个 block 内容:
010 OP_GETUPVAR R3 3 0
018 OP_SEND R3 :[] 1
022 OP_SEND R3 :ord 0
026 OP_SEND R2 :validchar 1
030 OP_SETUPVAR R2 5 0
038 OP_LOADI__1 R3
040 OP_EQ R2 R3
046 OP_LOADF R2
048 OP_RETURN_BLK R2
054 OP_SEND R2 :chr 0
069 OP_SEND R3 :[]= 2
等价 Ruby:
(0..content.length - 1).each do |i|
v = validchar(content[i].ord)
return false if v == -1
content[i] = v.chr
end
注意:这里虽然 content 还是字符串,但每个字符已经被替换成一个数值字符,字节值范围是 0..61
接下来字节码:
100 OP_LOADI_1 R6 ; step = 1
102 OP_LOADI_0 R4 ; i = 0
104 OP_LOADI_0 R7 ; lst = 0
109 OP_MOVE R15 R6
112 OP_ADD R14 R15
114 OP_MOVE R4 R14 ; i += step
进入 while 循环:
214 OP_MOVE R14 R4
220 OP_SEND R15 :length 0
224 OP_LT R14 R15
226 OP_JMPIF R14 120
等价循环条件:
while i < content.length
循环体中先取段首:
123 OP_MOVE R15 R7
126 OP_SEND R14 :[] 1
130 OP_SEND R14 :ord 0
134 OP_MOVE R8 R14 ; c = content[lst].ord
然后处理区间 lst + 1 .. i - 1
140 OP_ADDI R14 1
146 OP_SUBI R15 1
149 OP_RANGE_INC R14
151 OP_BLOCK R15 I(1:0x63032f97aff0)
154 OP_SENDB R14 :each 0
block 0x63032f97aff0
016 OP_SEND R2 :[] 1
020 OP_SEND R2 :ord 0
024 OP_SETUPVAR R2 5 0
028 OP_GETUPVAR R2 8 0 # c
032 OP_GETUPVAR R3 5 0 # v
036 OP_SEND R2 :^ 1 # c ^ v
040 OP_GETUPVAR R3 9 0 # j
044 OP_SEND R2 :^ 1 # c ^ v ^ j
048 OP_SEND R2 :chr 0
063 OP_SEND R3 :[]= 2
等价
(lst + 1 .. i - 1).each do |j|
v = content[j].ord
content[j] = (c ^ v ^ j).chr
end
然后处理段首 lst
158 OP_MOVE R14 R8 # c
161 OP_MOVE R15 R7 # lst
164 OP_ADDI R15 1
167 OP_SEND R14 :^ 1
171 OP_SEND R14 :chr 0
184 OP_SEND R15 :[]= 2
等价
content[lst] = (c ^ (lst + 1)).chr
最后更新
191 OP_MOVE R7 R14 # lst = i
197 OP_ADDI R14 1 # step += 1
206 OP_MOVE R15 R6
209 OP_ADD R14 R15
211 OP_MOVE R4 R14 # i += step
完整伪代码
step = 1
i = 0
lst = 0
i += step
while i < content.length
c = content[lst].ord
(lst + 1 .. i - 1).each do |j|
v = content[j].ord
content[j] = (c ^ v ^ j).chr
end
content[lst] = (c ^ (lst + 1)).chr
lst = i
step += 1
i += step
end
对于长度 32,循环的 i 依次是
1, 3, 6, 10, 15, 21, 28
实际被作为段首处理的 lst 是
0, 1, 3, 6, 10, 15, 21
因此每一段是
[0]
[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 不进入这轮循环 三角形扰动之后 字节码构造了 8 个长度为 64 的数组
OP_ARRAY R14 64
OP_ARRAY R15 64
太多了不复制了
OP_ARRAY R21 64
OP_ARRAY R14 8
OP_MOVE R10 R14 ; wanted
整理出来是
wanted = [
[16, 1, 87, 173, 17, 105, 224, 61, 44, 62, 18, 244, 0, 46, 91, 185, 165, 23, 28, 225, 211, 136, 39, 32, 124, 66, 142, 79, 227, 6, 195, 175, 122, 198, 217, 239, 90, 90, 235, 178, 241, 175, 38, 20, 54, 15, 223, 210, 13, 55, 227, 62, 186, 64, 16, 235, 34, 175, 34, 23, 109, 154, 123, 252],
[36, 178, 137, 59, 73, 219, 40, 180, 225, 211, 246, 128, 142, 108, 187, 177, 238, 224, 53, 173, 87, 177, 78, 50, 0, 20, 140, 45, 65, 156, 1, 62, 199, 86, 150, 242, 175, 142, 4, 169, 113, 43, 208, 81, 160, 227, 117, 65, 57, 69, 58, 5, 197, 38, 212, 102, 177, 221, 173, 4, 165, 130, 33, 150],
[134, 74, 229, 219, 211, 240, 39, 113, 189, 85, 180, 56, 189, 12, 6, 110, 114, 92, 33, 167, 232, 229, 18, 45, 209, 133, 89, 85, 73, 119, 79, 32, 100, 44, 29, 114, 175, 13, 194, 183, 122, 115, 49, 206, 195, 124, 119, 44, 170, 151, 96, 226, 241, 11, 43, 190, 252, 119, 188, 86, 212, 241, 106, 251],
[81, 148, 108, 136, 231, 52, 146, 136, 137, 30, 88, 95, 59, 33, 121, 165, 79, 36, 4, 255, 43, 97, 15, 107, 174, 0, 209, 55, 176, 143, 91, 222, 197, 19, 3, 178, 91, 20, 165, 209, 68, 212, 214, 60, 21, 83, 237, 110, 234, 95, 42, 203, 17, 95, 2, 130, 80, 20, 190, 27, 128, 187, 51, 170],
[105, 15, 55, 217, 62, 217, 242, 15, 138, 138, 162, 174, 74, 98, 184, 135, 240, 231, 4, 89, 228, 26, 36, 229, 110, 84, 58, 85, 56, 234, 137, 236, 244, 113, 168, 201, 141, 174, 244, 79, 55, 51, 21, 134, 184, 210, 19, 14, 255, 224, 141, 175, 123, 118, 198, 207, 178, 94, 222, 158, 100, 69, 128, 7],
[149, 182, 200, 165, 254, 94, 40, 171, 145, 255, 70, 220, 72, 228, 161, 160, 27, 167, 200, 171, 113, 28, 207, 255, 106, 180, 188, 85, 126, 208, 235, 188, 26, 47, 181, 239, 172, 249, 198, 177, 81, 106, 233, 120, 194, 222, 255, 66, 216, 250, 85, 179, 35, 28, 217, 23, 54, 132, 34, 29, 251, 103, 3, 48],
[23, 166, 87, 4, 20, 153, 53, 83, 209, 138, 214, 207, 68, 235, 88, 161, 96, 8, 184, 199, 38, 124, 184, 234, 145, 160, 241, 71, 24, 236, 33, 101, 92, 66, 42, 93, 233, 224, 110, 140, 208, 88, 138, 163, 77, 10, 237, 120, 103, 209, 166, 162, 204, 67, 7, 247, 5, 215, 76, 126, 131, 188, 234, 15],
[56, 52, 150, 163, 243, 222, 118, 2, 217, 198, 156, 162, 181, 73, 238, 68, 187, 82, 58, 139, 58, 1, 69, 219, 71, 13, 108, 63, 68, 116, 35, 253, 29, 189, 144, 76, 234, 98, 219, 8, 36, 56, 19, 114, 118, 160, 27, 165, 6, 181, 46, 42, 139, 163, 161, 255, 207, 69, 190, 49, 71, 82, 102, 235]
]
后面进入循环
1797 OP_JMP 2063
1800 OP_GETCONST R14 :Cipher
1803 OP_SEND R14 :new 0
1807 OP_MOVE R11 R14 ; cipher
太多了不复制了
2063 OP_MOVE R14 R4
2069 OP_SEND R15 :length 0
2073 OP_LOADI_4 R16
2075 OP_DIV R15 R16
2077 OP_LT R14 R15
2079 OP_JMPIF R14 1800
因为 content.length = 32 所以
i < content.length / 4
也就是
i = 0..7
每一轮只取四个位置
content[i]
content[i + 8]
content[i + 16]
content[i + 24]
但是拼接顺序不是简单的四字节 而是 14 字节 令
a = content[i]
b = content[i + 8]
c = content[i + 16]
d = content[i + 24]
则
cc = a + c + c + b + d + c + a + c + a + c + b + c + d + c
然后
2018 OP_STRING R16 L(2) ; c*
2021 OP_SEND R15 :unpack 1
2025 OP_SEND R14 :hash 1
2029 OP_MOVE R13 R14 ; output
2035 OP_MOVE R16 R4
2038 OP_SEND R15 :[] 1
2042 OP_SEND R14 :!= 1
2050 OP_LOADF R14
2052 OP_RETURN_BLK R14
等价 Ruby
output = cipher.hash(cc.unpack("c*"))
return false if output != wanted[i]
如果 8 组都通过 最后返回 true
2083 OP_LOADT R14
2085 OP_RETURN R14
然后继续看 Cipher#hash
irep 0x63032f979ff0 nregs=3 nlocals=1 pools=0 syms=1 reps=1 iseq=13
000 OP_TCLASS R1
002 OP_METHOD R2 I(0:0x63032f97a0a0)
005 OP_DEF R1 :hash
008 OP_LOADSYM R1 :hash
011 OP_RETURN R1
进入真正的 hash 函数
irep 0x63032f97a0a0 nregs=13 nlocals=9 pools=1 syms=7 reps=2 iseq=83
local variable names:
R1:input_byte_array
R2:&
R3:padded_byte_array
R4:input_blocks
R5:encryptor
R6:encrypted_state
R7:iteration
R8:key
关键字节码如下
004 OP_GETCONST R9 :Util
007 OP_MOVE R10 R1 ; input_byte_array
010 OP_SEND R9 :pad_byte_array 1
014 OP_MOVE R3 R9 ; padded_byte_array
017 OP_GETCONST R9 :Util
020 OP_MOVE R10 R3 ; padded_byte_array
023 OP_SEND R9 :chunk_into_blocks 1
027 OP_MOVE R4 R9 ; input_blocks
030 OP_GETCONST R9 :Encryptor
033 OP_SEND R9 :new 0
037 OP_MOVE R5 R9 ; encryptor
040 OP_LOADNIL R6 ; encrypted_state
042 OP_LOADI_0 R7 ; iteration
044 OP_GETCONST R9 :Array
047 OP_LOADI16 R10 512
051 OP_LOADI R11 8
054 OP_DIV R10 R11
056 OP_BLOCK R11 I(0:0x63032f97a1c0)
059 OP_SENDB R9 :new 1
063 OP_MOVE R8 R9 ; key
这里初始化了一个 64 字节全 0 的 key
key = Array.new(512 / 8) { 0 }
前面传入的 cc 长度是 14 字节,所以会先进入 Util.pad_byte_array 做 padding
pad_byte_array 中有
011 OP_LOADI16 R10 256
015 OP_LOADI R11 8
018 OP_DIV R10 R11
020 OP_SEND R9 :divmod 1
也就是以 256 / 8 = 32 字节为一个半块单位计算
对于当前输入
cc.length = 14
count, offset = 14.divmod(32)
count = 0
offset = 14
因为 count % 2 == 0,所以
padding_length = 32 - offset
padding_length = 18
然后
padding = Array.new(18) { 0 }
padding[0] = 0x80
接着计算原始消息 bit 长度
blen = bytes_array.length * 8
blen = 14 * 8
blen = 112
再调用 create_comp(112) 生成 32 字节长度字段
所以 14 字节的 cc 最后会被填充成 64 字节
14 字节原文 + 18 字节 padding + 32 字节长度字段 = 64 字节
因为原始长度只有 14 字节 所以最终只有一个 512-bit block 这里等价成代码就是
block = cc.unpack("c*")
block += [0x80]
block += [0] * 17
block += [0] * 31 + [112]
也就是 C 里面可以简化成
uint8_t block[64] = {0};
block[0] = a;
block[1] = c;
block[2] = c;
block[3] = b;
block[4] = d;
block[5] = c;
block[6] = a;
block[7] = c;
block[8] = a;
block[9] = c;
block[10] = b;
block[11] = c;
block[12] = d;
block[13] = c;
block[14] = 0x80;
block[63] = 112;
再往下看 hash 里对每个 block 做处理
066 OP_MOVE R9 R4 ; input_blocks
069 OP_BLOCK R10 I(1:0x63032f97a230)
072 OP_SENDB R9 :each 0
076 OP_RETURN R6 ; encrypted_state
进入 block
irep 0x63032f97a230 nregs=8 nlocals=4 pools=0 syms=2 reps=2 iseq=62
local variable names:
R1:block
R2:&
R3:previous_key
关键逻辑
004 OP_GETUPVAR R4 8 0
008 OP_MOVE R3 R4 ; previous_key = key
011 OP_GETUPVAR R4 5 0 ; encryptor
015 OP_MOVE R5 R1 ; block
018 OP_GETUPVAR R6 8 0 ; key
022 OP_SEND R4 :encrypt_state 2
026 OP_SETUPVAR R4 6 0 ; encrypted_state
030 OP_GETUPVAR R4 6 0
034 OP_SETUPVAR R4 8 0 ; key = encrypted_state
等价 Ruby
previous_key = key
encrypted_state = encryptor.encrypt_state(block, key)
key = encrypted_state
然后对 key 原地异或两次
第一轮
038 OP_GETUPVAR R4 8 0
042 OP_BLOCK R5 I(0:0x63032f97a310)
045 OP_SENDB R4 :each_index 0
第二轮
049 OP_GETUPVAR R4 8 0
053 OP_BLOCK R5 I(1:0x63032f97a3c0)
056 OP_SENDB R4 :each_index 0
所以 Cipher#hash 大概可以还原成
def hash(input_byte_array)
padded_byte_array = Util.pad_byte_array(input_byte_array)
input_blocks = Util.chunk_into_blocks(padded_byte_array)
encryptor = Encryptor.new
encrypted_state = nil
iteration = 0
key = Array.new(512 / 8) { 0 }
input_blocks.each do |block|
previous_key = key
encrypted_state = encryptor.encrypt_state(block, key)
key = encrypted_state
key.each_index do |i|
key[i] = key[i] ^ previous_key[i]
end
key.each_index do |i|
key[i] = key[i] ^ block[i]
end
end
encrypted_state
end
因为我们这里每次只有一个 block 而且初始 key 是 64 个 0 所以可以简化理解成
hash(cc) = encrypt_state(block, zero_key) ^ block
接着看 Encryptor#encrypt_state
irep 0x63032f979db0 nregs=11 nlocals=7 pools=0 syms=10 reps=1 iseq=88
local variable names:
R1:block
R2:key
R3:&
R4:encryption_state
R5:key_state
R6:key_generator
关键流程
004 OP_GETCONST R7 :State
007 OP_GETCONST R8 :Matrix
010 OP_MOVE R9 R1 ; block
013 OP_SEND R8 :create_matrix 1
017 OP_SEND R7 :new 1
021 OP_MOVE R4 R7 ; encryption_state
024 OP_LOADSELF R7
026 OP_MOVE R8 R2 ; key
029 OP_SEND R7 :create_key_state 1
033 OP_MOVE R5 R7 ; key_state
036 OP_GETCONST R7 :Keys
039 OP_MOVE R8 R5
042 OP_SEND R7 :new 1
046 OP_MOVE R6 R7 ; key_generator
049 OP_MOVE R7 R4
052 OP_MOVE R8 R5
055 OP_SEND R8 :state 0
059 OP_SEND R7 :add_round_key! 1
063 OP_LOADI_1 R7
065 OP_LOADI R8 10
068 OP_BLOCK R9 I(0:0x63032f979eb0)
071 OP_SENDB R7 :upto 1
075 OP_MOVE R7 R4
078 OP_SEND R7 :state 0
082 OP_SEND R7 :flatten 0
086 OP_RETURN R7
也就是
def encrypt_state(block, key)
encryption_state = State.new(Matrix.create_matrix(block))
key_state = create_key_state(key)
key_generator = Keys.new(key_state)
encryption_state.add_round_key!(key_state.state)
1.upto(10) do |i|
round_key = key_generator.get_round_key(i)
round = Round.new(encryption_state, round_key)
round.execute
encryption_state = round.state
end
encryption_state.state.flatten
end
整体非常像 AES 但是参数不是标准 AES
block size = 512 bit
state = 8 x 8
round = 10
SBox = 自定义 16 x 16 SBox
MixRows = 自定义 8 x 8 矩阵
GF 多项式 = 0x11d
Round#execute 里可以看到
sub_bytes!
shift_columns!
mix_rows!
add_round_key!
等价
def execute
@state.sub_bytes!
@state.shift_columns!
@state.mix_rows!
@state.add_round_key!(@round_key)
end
所以这块本质是一个自定义 512-bit AES-like 加密
不过这题不用去完整逆这个加密 因为前面已经发现
a = content[i]
b = content[i + 8]
c = content[i + 16]
d = content[i + 24]
每一组 hash 只和 4 个字节有关
而这 4 个字节来自 validchar 映射和三角形异或之后 范围最大只会在 0..63 附近 为了保险直接枚举 0..63
单组复杂度
64 ^ 4 = 16777216
总共 8 组 但是可以一次枚举 (a,b,c,d) 然后和 8 个 wanted 同时比较 所以复杂度可以接受 核心爆破逻辑
for (int a = 0; a < 64; a++)
for (int c = 0; c < 64; c++)
for (int b = 0; b < 64; b++)
for (int d = 0; d < 64; d++) {
hash4(a, b, c, d, out);
for (int g = 0; g < 8; g++) {
if (!memcmp(out, wanted[g], 64)) {
tuple[g][0] = a;
tuple[g][1] = b;
tuple[g][2] = c;
tuple[g][3] = d;
}
}
}
这里的 hash4 就是把 a,b,c,d 按照题目顺序拼成 14 字节 再补成 64 字节 然后跑自定义 AES-like 加密 最后异或原 block
static void hash4(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t out[64]) {
uint8_t block[64] = {0};
block[0] = a;
block[1] = c;
block[2] = c;
block[3] = b;
block[4] = d;
block[5] = c;
block[6] = a;
block[7] = c;
block[8] = a;
block[9] = c;
block[10] = b;
block[11] = c;
block[12] = d;
block[13] = c;
block[14] = 0x80;
block[63] = 112;
memcpy(out, block, 64);
for (int round = 1; round <= 10; round++) {
round_exec(out, RK[round]);
}
for (int i = 0; i < 64; i++) {
out[i] ^= block[i];
}
}
跑出来 8 组扰动之后的结果
group0 = 39, 4, 26, 5
group1 = 3, 8, 24, 28
group2 = 14, 63, 4, 1
group3 = 6, 60, 37, 12
group4 = 2, 44, 35, 54
group5 = 48, 41, 55, 18
group6 = 54, 28, 52, 23
group7 = 16, 8, 60, 6
把它们放回 32 字节数组
for (int i = 0; i < 8; i++) {
t[i] = tuple[i][0];
t[i + 8] = tuple[i][1];
t[i + 16] = tuple[i][2];
t[i + 24] = tuple[i][3];
}
此时 t 是三角形异或扰动之后的数组 还不是原始 flag 中间 32 位 前面的扰动是
while i < content.length
c = content[lst].ord
(lst + 1 .. i - 1).each do |j|
v = content[j].ord
content[j] = (c ^ v ^ j).chr
end
content[lst] = (c ^ (lst + 1)).chr
lst = i
step += 1
i += step
end
因为异或可逆,所以可以直接按相同段顺序逆回去正向里
new_lst = old_lst ^ (lst + 1)
new_j = old_lst ^ old_j ^ j
所以逆向
uint8_t old_lst = t[lst] ^ (lst + 1);
for (int j = lst + 1; j < i; j++) {
t[j] = t[j] ^ old_lst ^ j;
}
t[lst] = old_lst;
完整逆扰动
int step = 1;
int i = 1;
int lst = 0;
while (i < 32) {
uint8_t c = t[lst] ^ (uint8_t)(lst + 1);
for (int j = lst + 1; j < i; j++) {
t[j] = t[j] ^ c ^ (uint8_t)j;
}
t[lst] = c;
lst = i;
step++;
i += step;
}
最后再映射回字符
const char *alpha = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
printf("flag{");
for (int i = 0; i < 32; i++) {
putchar(alpha[t[i]]);
}
puts("}");
得到flag
flag{c1D24tnczmq3KGcOIHEklX3AyawssIN6}
完整exp
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#ifdef _OPENMP
#include <omp.h>
#endif
static const uint8_t TARGETS[8][64] = {
{16,1,87,173,17,105,224,61,44,62,18,244,0,46,91,185,165,23,28,225,211,136,39,32,124,66,142,79,227,6,195,175,122,198,217,239,90,90,235,178,241,175,38,20,54,15,223,210,13,55,227,62,186,64,16,235,34,175,34,23,109,154,123,252},
{36,178,137,59,73,219,40,180,225,211,246,128,142,108,187,177,238,224,53,173,87,177,78,50,0,20,140,45,65,156,1,62,199,86,150,242,175,142,4,169,113,43,208,81,160,227,117,65,57,69,58,5,197,38,212,102,177,221,173,4,165,130,33,150},
{134,74,229,219,211,240,39,113,189,85,180,56,189,12,6,110,114,92,33,167,232,229,18,45,209,133,89,85,73,119,79,32,100,44,29,114,175,13,194,183,122,115,49,206,195,124,119,44,170,151,96,226,241,11,43,190,252,119,188,86,212,241,106,251},
{81,148,108,136,231,52,146,136,137,30,88,95,59,33,121,165,79,36,4,255,43,97,15,107,174,0,209,55,176,143,91,222,197,19,3,178,91,20,165,209,68,212,214,60,21,83,237,110,234,95,42,203,17,95,2,130,80,20,190,27,128,187,51,170},
{105,15,55,217,62,217,242,15,138,138,162,174,74,98,184,135,240,231,4,89,228,26,36,229,110,84,58,85,56,234,137,236,244,113,168,201,141,174,244,79,55,51,21,134,184,210,19,14,255,224,141,175,123,118,198,207,178,94,222,158,100,69,128,7},
{149,182,200,165,254,94,40,171,145,255,70,220,72,228,161,160,27,167,200,171,113,28,207,255,106,180,188,85,126,208,235,188,26,47,181,239,172,249,198,177,81,106,233,120,194,222,255,66,216,250,85,179,35,28,217,23,54,132,34,29,251,103,3,48},
{23,166,87,4,20,153,53,83,209,138,214,207,68,235,88,161,96,8,184,199,38,124,184,234,145,160,241,71,24,236,33,101,92,66,42,93,233,224,110,140,208,88,138,163,77,10,237,120,103,209,166,162,204,67,7,247,5,215,76,126,131,188,234,15},
{56,52,150,163,243,222,118,2,217,198,156,162,181,73,238,68,187,82,58,139,58,1,69,219,71,13,108,63,68,116,35,253,29,189,144,76,234,98,219,8,36,56,19,114,118,160,27,165,6,181,46,42,139,163,161,255,207,69,190,49,71,82,102,235}
};
static uint8_t gf[256][256], SBOX[256], RK[11][64];
static const uint8_t eMap[16]={1,11,9,12,13,6,15,3,14,8,7,4,10,2,5,0};
static const uint8_t invEMap[16]={15,0,13,7,11,14,5,10,9,2,12,1,3,4,8,6};
static const uint8_t rMap[16]={7,12,11,13,14,4,9,15,6,3,8,10,2,5,1,0};
static const uint8_t MIX[8][8]={{1,1,4,1,8,5,2,9},{9,1,1,4,1,8,5,2},{2,9,1,1,4,1,8,5},{5,2,9,1,1,4,1,8},{8,5,2,9,1,1,4,1},{1,8,5,2,9,1,1,4},{4,1,8,5,2,9,1,1},{1,4,1,8,5,2,9,1}};
static uint8_t gf2(uint8_t a, uint8_t b){
uint8_t products[9]; int v=b; products[0]=b;
for(int k=0;k<8;k++){ v = (v>>7) ? ((v<<1)^0x11d) : (v<<1); v &= 255; products[k+1]=(uint8_t)v; }
uint8_t r=0; for(int i=7;i>=0;i--) if((a>>i)&1) r ^= products[i]; return r;
}
static uint8_t sbox_byte(uint8_t x){
uint8_t a=x>>4, b=x&15, aE=eMap[a], bE=invEMap[b], bR=rMap[aE^bE];
return (uint8_t)((eMap[aE^bR]<<4) | invEMap[bE^bR]);
}
static void shift_columns(uint8_t st[64]){
uint8_t tmp[8];
for(int c=1;c<8;c++){ for(int r=0;r<8;r++) tmp[r]=st[r*8+c]; for(int r=0;r<8;r++) st[r*8+c]=tmp[(r-c+8)%8]; }
}
static void mix_rows(uint8_t st[64]){
uint8_t out[64];
for(int r=0;r<8;r++) for(int c=0;c<8;c++){ uint8_t s=0; for(int i=0;i<8;i++) s ^= gf[st[r*8+i]][MIX[i][c]]; out[r*8+c]=s; }
memcpy(st,out,64);
}
static void round_exec(uint8_t st[64], const uint8_t rk[64]){
for(int i=0;i<64;i++) st[i]=SBOX[st[i]];
shift_columns(st); mix_rows(st);
for(int i=0;i<64;i++) st[i]^=rk[i];
}
static void init_cipher(){
for(int a=0;a<256;a++) for(int b=0;b<256;b++) gf[a][b]=gf2((uint8_t)a,(uint8_t)b);
for(int i=0;i<256;i++) SBOX[i]=sbox_byte((uint8_t)i);
memset(RK,0,sizeof(RK)); uint8_t st[64]={0};
for(int round=1; round<=10; round++){
uint8_t rcon[64]={0}; for(int c=0;c<8;c++) rcon[c]=SBOX[8*(round-1)+c];
round_exec(st,rcon); memcpy(RK[round],st,64);
}
}
static void hash4(uint8_t a,uint8_t b,uint8_t c,uint8_t d,uint8_t out[64]){
uint8_t block[64]={0};
block[0]=a; block[1]=c; block[2]=c; block[3]=b; block[4]=d; block[5]=c; block[6]=a; block[7]=c;
block[8]=a; block[9]=c; block[10]=b; block[11]=c; block[12]=d; block[13]=c;
block[14]=128; block[63]=112;
memcpy(out,block,64);
for(int round=1; round<=10; round++) round_exec(out,RK[round]);
for(int i=0;i<64;i++) out[i]^=block[i];
}
int main(){
init_cipher();
int found[8]={0}; uint8_t tuple[8][4];
#pragma omp parallel
{
uint8_t out[64];
#pragma omp for collapse(2) schedule(dynamic)
for(int a=0;a<64;a++) for(int c=0;c<64;c++) for(int b=0;b<64;b++) for(int d=0;d<64;d++){
hash4(a,b,c,d,out);
for(int g=0;g<8;g++) if(!found[g] && !memcmp(out,TARGETS[g],64)){
#pragma omp critical
{
if(!found[g]){ found[g]=1; tuple[g][0]=a; tuple[g][1]=b; tuple[g][2]=c; tuple[g][3]=d; }
}
}
}
}
for(int g=0;g<8;g++) if(!found[g]){ puts("missing"); return 1; }
uint8_t t[32]={0};
for(int i=0;i<8;i++){ t[i]=tuple[i][0]; t[i+8]=tuple[i][1]; t[i+16]=tuple[i][2]; t[i+24]=tuple[i][3]; }
int step=1, i=1, lst=0;
while(i<32){
uint8_t c = t[lst] ^ (uint8_t)(lst+1);
for(int j=lst+1;j<i;j++) t[j] = t[j] ^ c ^ (uint8_t)j;
t[lst]=c; lst=i; step++; i+=step;
}
const char *alpha="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
printf("flag{"); for(int k=0;k<32;k++) putchar(alpha[t[k]]); puts("}");
return 0;
}
总flag 但是你怎么交都不对 交不上flag
chall_0 flag{c1D24tnczmq3KGcOIHEklX3AyawssIN6}
chall_1 flag{6GiJ7UG3btk43xH5MdrWbHTp3RjM4ZUB}
chall_2 flag{2TkxO7cTM0gkr22jrZggmW4nOWvk7Q97}
chall_3 flag{Nbdn7YVDrt8PQOzAtZMQsUW7eszx4TLZ}
chall_4 flag{2YePC9AglRccnaP69c17kelGd1welI1d}
chall_5 flag{YYLPnnyjYXrcDr2uWEgEKLtmntgNDnip}
chall_6 flag{Hq9ruGSNkvfJRYWvQ6NId1l5YXUOu9YN}
chall_7 flag{qlQYbLfvvV1RKQsZNBEUwxjipA2Tmqmr}
chall_8 flag{xxUiqfGEESbNauweyevmFISqk5gPZ7Xy}
chall_9 flag{7grpc8e1DXr3YMDflonVR1OplMVA6aoZ}
评论