长城杯初赛wp xiran_encrypto
0x00
该题为正常ctf题与恶意脚本相结合的题目,cha为常规的re题目,clickme为恶意样本,在cha中拿到信息,然后根据clickme的逻辑解密
0x01
cha的核心为比较经过换表base64然后与自己xor之后与内存中的32字节相比较

求解思路:
- DFS爆破,因为前面的字节会影响后面的状态
- 利用前面的数据求和信息,筛选唯一解


爆破脚本:
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 | convert_table = b'APet8BQfu9CRgv+DShw/ETixFUjyGVkzHWl0IXm1JYn2KZo3Lap4Mbq5Ncr6Ods7'encrypted_flag = [0xD9, 0x40, 0x6F, 0xCA, 0x3D, 0x8F, 0x53, 0xB1, 0x8B, 0x34, 0x92, 0x8E, 0xF7, 0x19, 0x94, 0x61, 0x68, 0x71, 0x55, 0xB6, 0xCE, 0x5B, 0x71, 0x1A, 0x79, 0x42, 0x9D, 0x02, 0x93, 0x38, 0xAD, 0x1F, 0xD3, 0x24, 0x48, 0xFF, 0x21, 0xA2, 0x24, 0xBE, 0x95, 0x3A, 0xC1, 0xD2]def convert(flag,index): group = (index // 4) * 3 if index % 4 == 0: return convert_table[flag[group] >> 2] if index % 4 == 1: return convert_table[(flag[group] << 4 & 0x30) + (flag[group + 1] >> 4)] if index % 4 == 2: return convert_table[(flag[group + 1] << 2 & 0x3c) + (flag[group + 2] >> 6)] if index % 4 == 3: return convert_table[flag[group + 2] & 0x3f]def brup(flag): cur_len = len(flag) if cur_len != 0: res = flag[cur_len-1] ^ convert(flag,cur_len-1) if res != encrypted_flag[cur_len-1]: return if cur_len == 32: sum = 0 for i in range(len(flag)): sum = 19*sum + flag[i] sum = sum & 0xffffffff if sum == 0xD033A96A: for i in flag: print(i,end=',') return for i in range(256): flag.append(i) brup(flag) flag.pop()flag = bytearray()brup(flag) |
0x02 clickme
clickme为babyuk家族,主要加密逻辑也类似
核心加密逻辑在main_encrypt_file中
- 根据curve25519算法(随机私钥和对应的公钥,共享密钥) 产生key nonce
- 根据key,nonce 产生cipher(golang_org_x_crypto_chacha20_newUnauthenticatedCipher)
- xorKeyStream加密文件
- 如果文件大小 < 0x1400000
- 加密整个文件
- 如果文件大小 > 0x1400000
- 每0xA00000加密0x100000
- 最后写入32字节公钥和固定标识
- 如果文件大小 < 0x1400000
解密脚本:
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 | package mainimport ( "crypto/sha256" "os" "golang.org/x/crypto/chacha20" "golang.org/x/crypto/curve25519")func main() { file, err := os.OpenFile("flag.png.xiran",os.O_RDWR,0) if err != nil { panic(err) } fileinfo,err := file.Stat() if err != nil { panic(err) } filesize := fileinfo.Size() var start int64 = 0 end := filesize privateKeyBob := [32]byte{144,16,46,165,100,230,124,220,241,66,166,239,164,119,237,82,49,23,47,236,139,107,73,98,43,49,237,80,249,117,245,115} publicKeyAlice := make([]byte,32,32) offset,err := file.Seek(filesize-32-6,0) if err != nil { panic(err) } file.ReadAt(publicKeyAlice,offset) var sharedKeyAlice [32]byte curve25519.ScalarMult(&sharedKeyAlice, &privateKeyBob, (*[32]byte)(publicKeyAlice)) var key [32]byte= sha256.Sum256(sharedKeyAlice[:]) var res [32]byte = sha256.Sum256(key[:]) nonce := res[10:22] cipher, err := chacha20.NewUnauthenticatedCipher(key[:],nonce) if err != nil { panic(err) } buffer := make([]byte, 0x100000,0x100000) for start < end { file.ReadAt(buffer,start) cipher.XORKeyStream(buffer,buffer) file.WriteAt(buffer,start) start += 0xA00000 } if err := file.Close(); err != nil { panic(err) }} |
0x03 一些思考
cha中的私钥如果纯静态的分析 只有爆破才行 能不能在cha运行时把 私钥dump出来?
在尝试的过程中发现cha中,在.init_array中存在一个特殊的初始化函数,在这个函数中做了两个(或者一个)反调试:
- ptrace(PTRACE_TRACEME) == -1 退出
- 读取某个内存中的值若为0 则退出(前面有个call 清除这个内存处的值)
在bypass这两个点之后,进入main之后 发现私钥对应的内存实际为0,这也和在ida中找不到它的xref呼应,我觉得这里作者的意思就是故意让程序运行不到main,所以正确做法只能爆破,不然直接进内存dump太简单了。