ruby逆向

工具链接 github.com/mruby/mruby.git

题目下载下来有10个附件 对应10题 这题在线上应该是动态附件 这里挑选0为wp 其余都是一样的操作

拿到手发现是一个mrb文件 使用mruby来反编译

image

./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}