0x00 前置 来点GoCipher:https://pkg.go.dev/crypto/cipher
再来点Go的XXTEA:https://github.com/xxtea/xxtea-go/blob/master/xxtea/xxtea.go
0x01 解 go语言和rust一样,静态是真看不明白
所以只能调试
然而这题里面有反调
根据调试时出现的字符串可以去搜索然后找到反调的地方
将jz改为jmp就可以过掉了
之后调试需要使用F4(运行至光标处),否则会跳到很多奇怪的地方
由于对go的反编译并不给力,因此大多时候还是以汇编为主
首先来到看上去像是真正执行的地方,可以看到有一个读文件的操作
在文件路径处下断点,往下跟
可以遇到第一个看起来像是加密的函数
进入这个main_tlFyZv,反编译后往下翻翻可以看到一个异或
在汇编看,可以发现基本没有别的操作了,合理怀疑这个函数就是一个异或
查看一下值,发现ptr内是一个表,这个表应该就是异或用的表了
先将其dump下来,然后写个脚本加密一下原flag,看看是否异或成功
1 2 3 4 5 6 7 8 xortable = b"D7BJLsOk9@f&1dWIn53IDlJqUS6$^WhkAk2kk*2GaqmLwiLX^bGGE$&dmqR^g5bL3lCA5^HGK$9qo5T@Bwom9vEXya0HAV3LrWW" xortable = bytearray (xortable) plaintxt = b"flag{xxxxxxxxxx}" plaintxt = bytearray (plaintxt) for i in range (len (plaintxt)): plaintxt[i] ^= xortable[i%len (xortable)] open ("flag" ,"wb" ).write(plaintxt)
将异或后的flag作为输入,可以发现异或成功了
那么就继续往下翻翻吧
搜一下这个bytesToUint32s,不过没什么结果
继续看
下面有个main_zQyveE
进去可以发现很像是XXTEA
搜一下go语言版本XXTEA的可以得到这个:https://github.com/xxtea/xxtea-go/blob/master/xxtea/xxtea.go
对比可发现基本一致,但其中有三处不同:
1.MX被改
1 2 3 4 ((z>>5 ^ y<<2 ) + (y>>3 ^ z<<4 )) ^ ((sum ^ y) + (k[p&3 ^e] ^ z)) ((z>>5 ^ y<<2 ) + (y>>3 ^ z<<4 ) ^ (sum ^ y)) + (k[p&3 ^e] ^ z)
2.执行toBytes时不带长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func Decrypt (data []byte , key []byte ) []byte { if data == nil || len (data) == 0 { return data } return toBytes(decrypt(toUint32s(data, false ), toUint32s(key, false )), true ) } func Decrypt (data []byte , key []byte ) []byte { if data == nil || len (data) == 0 { return data } return toBytes(decrypt(toUint32s(data, false ), toUint32s(key, false )), false ) }
3.delta被改
然后看魔改XXTEA后的操作
可以在call main_Q05qm6时将XXTEA的结果(存在RAX中)全部改为11,方便后面查看
后面是个sm4,但是有个rand,也就是为什么每次运行结果都不一样的原因
不过在汇编中可以看到更多的东西,有个NewCTR
可以去查看一下gocipher的使用指南看看如何进行调用的:https://pkg.go.dev/crypto/cipher
这里执行到sm4_NewCipher就能在RAX处得到key了
之后执行到rand_read的地方,可以在上面找到存储随机数key的地方,而旁边的rax就是存放生成的随机数的地方
记录rax的地址,在运行到cipher_NewCTR的时候就能在rcx中找到生成的随机数的值
根据go的crypto/rand,他会向指定大小的数组中填满随机数,可以用于验证(本题中指定的数组大小似乎为16字节)
这里将其全改为22,方便后续观察(相当于随机数生成了全22)
之后继续运行到call rdx处,发现它的参数是上一步XXTEA的结果
进去可以发现是一个XORKeyStream
从这里可以知道,这里的操作是使用一串字符作为key,一个随机数作为iv,使用XORKeyStream模式加密
关于go的对称加密可以看这个:https://zhuanlan.zhihu.com/p/58144027
之后继续运行,用sm4同样的方式可以得到aes的密钥(rax中)
然后运行至下面的CBC,可以得到iv
之后继续运行至rdx,可以发现要被加密的字符串中有刚刚生成的随机数
进去这个rsi,可以看到下面有一大串共0x20字节的内容(C2 8E E7 15 F4 2A B5 56 57 …)
这些其实就是上面sm4的结果,这里可以看出来sm4加密后将随机数生成的iv附加到了sm4加密结果的前面
继续执行至retn,可以发现rax的内容就是加密的结果了
将其前面部分改为33
然后出函数,发现又有个函数,进去可以看到是个base32encode
继续执行就输出了,因此整个加密流程就是
1 XOR->魔改XXTEA->sm4(key,iv=rand)(使用XORKeyStream模式加密)->将sm4的iv与sm4加密结果进行拼接->aes(key,iv)(iv和key都是固定的,这里使用CBC模式加密)->base32encode
据此可以写脚本,这里抄一下大哥代码,顺便学学go
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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 package mainimport ( "crypto/aes" "crypto/cipher" "encoding/base32" "fmt" "github.com/tjfoc/gmsm/sm4" ) const delta = 0x7FAB4CAD func toBytes (v []uint32 , includeLength bool ) []byte { length := uint32 (len (v)) n := length << 2 if includeLength { m := v[length-1 ] n -= 4 if (m < n-3 ) || (m > n) { return nil } n = m } bytes := make ([]byte , n) for i := uint32 (0 ); i < n; i++ { bytes[i] = byte (v[i>>2 ] >> ((i & 3 ) << 3 )) } return bytes } func toUint32s (bytes []byte , includeLength bool ) (v []uint32 ) { length := uint32 (len (bytes)) n := length >> 2 if length&3 != 0 { n++ } if includeLength { v = make ([]uint32 , n+1 ) v[n] = length } else { v = make ([]uint32 , n) } for i := uint32 (0 ); i < length; i++ { v[i>>2 ] |= uint32 (bytes[i]) << ((i & 3 ) << 3 ) } return v } func mx (sum uint32 , y uint32 , z uint32 , p uint32 , e uint32 , k []uint32 ) uint32 { return ((((z>>5 ^ y<<2 ) + (y>>3 ^ z<<4 )) ^ (sum ^ y)) + (k[p&3 ^e] ^ z)) } func fixk (k []uint32 ) []uint32 { if len (k) < 4 { key := make ([]uint32 , 4 ) copy (key, k) return key } return k } func encrypt (v []uint32 , k []uint32 ) []uint32 { length := uint32 (len (v)) n := length - 1 k = fixk(k) var y, z, sum, e, p, q uint32 z = v[n] sum = 0 for q = 6 + 52 /length; q > 0 ; q-- { sum += delta e = sum >> 2 & 3 for p = 0 ; p < n; p++ { y = v[p+1 ] v[p] += mx(sum, y, z, p, e, k) z = v[p] } y = v[0 ] v[n] += mx(sum, y, z, p, e, k) z = v[n] } return v } func decrypt (v []uint32 , k []uint32 ) []uint32 { length := uint32 (len (v)) n := length - 1 k = fixk(k) var y, z, sum, e, p, q uint32 y = v[0 ] q = 6 + 52 /length for sum = q * delta; sum != 0 ; sum -= delta { e = sum >> 2 & 3 for p = n; p > 0 ; p-- { z = v[p-1 ] v[p] -= mx(sum, y, z, p, e, k) y = v[p] } z = v[n] v[0 ] -= mx(sum, y, z, p, e, k) y = v[0 ] } return v } func XXTEAEncrypt (data []byte , key []byte ) []byte { if data == nil || len (data) == 0 { return data } return toBytes(encrypt(toUint32s(data, false ), toUint32s(key, false )), false ) } func XXTEADecrypt (data []byte , key []byte ) []byte { if data == nil || len (data) == 0 { return data } return toBytes(decrypt(toUint32s(data, false ), toUint32s(key, false )), false ) } func XOR (key []byte ,plaintext []byte ) []byte { ciphertext := make ([]byte ,len (plaintext)) for i:=0 ;i<len (plaintext);i++{ ciphertext[i] = plaintext[i] ^ key[i%len (key)] } return ciphertext } func dec (b32text string ) { dst := make ([]byte , base32.StdEncoding.DecodedLen(len (b32text))) n,err:=base32.StdEncoding.Decode(dst,[]byte (b32text)) if err != nil { fmt.Println("decode error:" ,err) return } dst = dst[:n] aes_iv := "dPGWgcLpqmxw3uOX" aes_key := "dPGWgcLpqmxw3uOXhKpKV009Cql@@XE6" cip,_ := aes.NewCipher([]byte (aes_key)) dec := cipher.NewCBCDecrypter(cip,[]byte (aes_iv)) dec.CryptBlocks(dst,dst) iv := dst[0 :16 ] dst = dst[16 :] sm4_key := "pg5g#k6Qo3L&1EzT" sm4_cip, _ := sm4.NewCipher([]byte (sm4_key)) ctr := cipher.NewCTR(sm4_cip,iv) ctr.XORKeyStream(dst,dst) key := []byte ("Bs^8*wZ4lu8oR&@k" ) for i := 16 ;i<len (dst);i++{ ddd2 := XXTEADecrypt(dst[:i],key) key2 := "D7BJLsOk9@f&1dWIn53IDlJqUS6$^WhkAk2kk*2GaqmLwiLX^bGGE$&dmqR^g5bL3lCA5^HGK$9qo5T@Bwom9vEXya0HAV3LrWW" dst2 := XOR([]byte (key2),ddd2) A:= string (dst2) fmt.Println(A) } } func main () { dec("nc result" ) }
由于题目是有远程环境的(用这个来生成动态flag)
因此赛后复现只能自己生成一个flag文件,然后用给的程序加密然后验证脚本是否正确了(