StarCTF
RE_NaCl(汇编, XTEA加密, 可逆加密算法)
这道题只看反编译很难看懂, 所以需要汇编 + 调试得到题目的大致逻辑
我们按照逻辑逐步调试
第一轮加密的data[]生成
首先是生成第一轮加密所需的data数组(元素类型为DWORD),
然后跳转到生成data数组的代码块
data生成完成后会有一个输入字符串分组逆序处理
随后来到第一部分的加密区域
第一部分加密
可以看到循环左移的次数都是固定的, 将算法逻辑写下来就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 输入: abcdefgh
分大组: abcdefgh 八个一组
分小组: abcd efgh
小组逆序: dcba(倒序一组) hgfe(倒序二组)
for i in range(0x2b):注意左移是循环左移 结果1 = (倒序一组 << 1) & (倒序一组 << 8) ^ (倒序一组 << 2) ^ 倒序二组(无左移) ^ (data + 4 * i)
结果2 = (结果1 << 1) & (结果1 << 8) ^ (结果1 << 2) ^ 倒序一组(无左移) ^ (data + 4 * i)
结果3 = (结果2 << 1) & (结果2 << 8) ^ (结果2 << 2) ^ 结果1 ^ (data + 4 * i)
结果4 = (结果3 << 1) & (结果3 << 8) ^ (结果3 << 2) ^ 结果2 ^ (data + 4 * i)
以此类推 ......... 进行0x2b次
|
其中的data就是前面生成的
随后来到第二部分的加密
第二部分加密
其实就是XTEA加密, 但是当时只照着XXTEA的算法看了一下发现不是就没管了.(主要想着有TEA特征delta值, 但是这道题把delta给改了)
下面是一些分析
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
| SFI:0000000008080180 ; START OF FUNCTION CHUNK FOR sub_8080900 SFI:0000000008080180 SFI:0000000008080180 loc_8080180: ; 加密结果高32位 SFI:0000000008080180 mov eax, [r15-0Ch] ; 上次加密的结果B562C653 SFI:0000000008080184 shl eax, 4 ; 高密 << 4, 注意这里不是逻辑左移了, 溢出32直接没了 SFI:0000000008080184 ; 密文 << 4 SFI:0000000008080187 mov edx, eax SFI:0000000008080189 mov eax, [r15-0Ch] SFI:000000000808018D shr eax, 5 ; 高密 >> 5 SFI:000000000808018D ; 密文 >> 5 SFI:0000000008080190 xor edx, eax ; (高密 << 4) ^ (高密 >> 5) SFI:0000000008080190 ; (密文 << 4) ^ (密文 >> 5) SFI:0000000008080192 mov eax, [r15-0Ch] SFI:0000000008080196 lea ecx, [rdx+rax] ; 高密 + ((高密 << 4) ^ (高密 >> 5)) SFI:0000000008080196 ; 密文 + ((密文 << 4) ^ (密文 >> 5)) SFI:0000000008080199 mov eax, [r15-10h] ; 0 SFI:0000000008080199 ; 10325476 SFI:000000000808019D and eax, 3 ; 0 SFI:000000000808019D ; eax = 2 SFI:00000000080801A0 lea rdx, ds:0[rax*4] SFI:00000000080801A8 mov rax, [r15-18h] ; 这是一个内存存有000102030405060708090A....注意小端排序 SFI:00000000080801AC add rax, rdx SFI:00000000080801AF mov eax, eax SFI:00000000080801B1 mov edx, [r13+rax+0] ; rdx = 03020100 SFI:00000000080801B1 ; rdx = B0A09080 SFI:00000000080801B6 mov eax, [r15-10h] ; 0 SFI:00000000080801B6 ; 10325476 SFI:00000000080801BA add eax, edx ; 03020100 SFI:00000000080801BA ; 1B3C5D7E SFI:00000000080801BC xor eax, ecx ; 03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))) SFI:00000000080801BC ; 1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))) SFI:00000000080801BE xchg ax, ax SFI:00000000080801C0 add [r15-8], eax ; 低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5)))) SFI:00000000080801C0 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))) SFI:00000000080801C4 mov eax, [r15-1Ch] ; 01234567, 小端排序rax = 10325476 SFI:00000000080801C4 ; 同上rax = 10325476 SFI:00000000080801C8 add [r15-10h], eax ; SFI:00000000080801C8 ; [r15 - 10h] = 2 * 10325476 = 2064A8EC SFI:00000000080801CC mov eax, [r15-8] SFI:00000000080801D0 shl eax, 4 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4 SFI:00000000080801D0 ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4 SFI:00000000080801D3 mov edx, eax SFI:00000000080801D5 mov eax, [r15-8] SFI:00000000080801D9 shr eax, 5 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5 SFI:00000000080801D9 ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5 SFI:00000000080801DC xor edx, eax ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4) SFI:00000000080801DC ; (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4) SFI:00000000080801DE xchg ax, ax SFI:00000000080801E0 mov eax, [r15-8] ; 低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5)))) SFI:00000000080801E0 ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))) SFI:00000000080801E4 lea ecx, [rdx+rax] ; (低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4)) SFI:00000000080801E4 ; ((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) SFI:00000000080801E7 mov eax, [r15-10h] ; 10325476 SFI:00000000080801E7 ; 2064A8EC SFI:00000000080801EB shr eax, 0Bh ; 2064A SFI:00000000080801EB ; 40C95 SFI:00000000080801EE mov eax, eax SFI:00000000080801F0 and eax, 3 ; EAX = 2 SFI:00000000080801F0 ; EAX = 1 SFI:00000000080801F3 lea rdx, ds:0[rax*4] ; RDX = 8 SFI:00000000080801F3 ; RDX = 4 SFI:00000000080801FB mov rax, [r15-18h] SFI:00000000080801FF nop SFI:0000000008080200 add rax, rdx ; 数组地址 + 8 SFI:0000000008080200 ; 数组地址 + 4 SFI:0000000008080203 mov eax, eax SFI:0000000008080205 mov edx, [r13+rax+0] ; 0B0A0908 SFI:0000000008080205 ; 07060504 SFI:000000000808020A mov eax, [r15-10h] ; 10325476 SFI:000000000808020A ; 2064A8EC SFI:000000000808020E add eax, edx ; 10325476 + 0B0A0908 SFI:000000000808020E ; 2064A8EC + 07060504 SFI:0000000008080210 xor eax, ecx ; ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908) SFI:0000000008080210 ; (((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))))) ^ (2064A8EC + 07060504) SFI:0000000008080212 add [r15-0Ch], eax ; 高密 + (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908)) SFI:0000000008080212 ; (高密 + (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) +(((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) >> 5) ^ ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) << 4))) ^ (10325476 + 0B0A0908))) + ((((((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) >> 5) ^ (((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5))))) << 4)) + ((低密 + (03020100 ^ (高密 + ((高密 << 4) ^ (高密 >> 5))))) + (1B3C5D7E ^ (密文 + ((密文 << 4) ^ (密文 >> 5)))))) ^ (2064A8EC + 07060504)) SFI:0000000008080216 inc dword ptr [r15-4] ; i++ SFI:000000000808021A nop word ptr [rax+rax+00h]
|
我们需要抽取其中最关键的特征
- (高密 << 4)
- (高密 >> 5)
- and eax, 3
- mov eax, [r15-10h] 和 add eax, edx, 传给eax的是10325476, 这个就是delta
综上是可以判断其为XTEA算法, 有没有魔改可以通过调试验证. 这道题是没有魔改过的
最后是关键判断
关键判断
查看一下
通过调试得到了大体的逻辑, 接下来复现算法
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
|
uint32_t rol(uint32_t num, uint32_t n); void encode(uint32_t n1, uint32_t n2, uint32_t *mid); void encipher(unsigned int num_rounds, uint32_t* high, uint32_t* low);
int main(void) { uint32_t input[8] = { 0x61626364, 0x65666768, 0x61626364, 0x65666768, 0x61626364, 0x65666768, 0x61626364, 0x65666768 };
uint32_t midput[8];
uint32_t i;
for (i = 0; i < 8; i += 2) { encode(input[i], input[i + 1], midput + i); //printf("第一部分加密:组%d:0x%x%x\n", i, tmp[i / 2] & 0xffff0000, tmp[i /2] & 0xffff); }
for (i = 0; i < 4; i++) { encipher(pow(2, i + 1), midput + (i * 2), midput + ((i * 2) + 1)); printf("0x%08x%08x\n", midput[i * 2], midput[(i * 2) + 1]); } }
uint32_t rol(uint32_t num, uint32_t n) { //uint32_t tmp = (num >> (32 - n)); //return ((num << n) & tmp); return ((num << n) | ((num >> (32 - n)))); }
//可逆加密 void encode(uint32_t n1, uint32_t n2, uint32_t *mid) { uint32_t data[] = { 0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B, 0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907 };
uint64_t result, i;
uint32_t xordata[2] = { n2, n1 };//初始化运算中轮换的异或数据
for (i = 0; i <= 0x2b; i++) { result = ((rol(xordata[1], 1) & rol(xordata[1], 8)) ^ rol(xordata[1], 2)) ^ xordata[0] ^ data[i]; xordata[0] = xordata[1]; xordata[1] = result; } printf("0x%x%x\n", xordata[1], xordata[0]);
*mid = xordata[0];//这里是顺序变了 *(mid + 1) = xordata[1]; }
void encipher(unsigned int num_rounds, uint32_t *high, uint32_t *low) { unsigned int i;
uint32_t key[4] = {0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C};
uint32_t v0 = *high, v1 = *low; uint32_t sum = 0, delta = 0x10325476;
for (i = 0; i < num_rounds; i++) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); sum += delta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); }
*high = v0; *low = v1; }
|
接下来是解密算法
解密算法
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
|
uint32_t rol(uint32_t num, uint32_t n); void decipther(unsigned int num_runds, uint32_t* high, uint32_t* low); void decode(uint32_t* mid); void re_chr(uint32_t num);
int main(void) { uint32_t en_data[8] = {0xFDF5C266, 0x7A328286, 0xCE944004, 0x5DE08ADC, 0xA6E4BD0A, 0x16CAADDC, 0x13CD6F0C, 0x1A75D936}; unsigned int i;
for (i = 0; i < 4; i++) { decipther(pow(2, i + 1), en_data + (2 * i), en_data + ((2 * i) + 1)); } for (i = 0; i < 8; i+=2) { decode(en_data + i); printf("0x%x%x\n", en_data[i], en_data[i + 1]); }
for (i = 0; i < 8; i++) { re_chr(en_data[i]); }
return 0; }
uint32_t rol(uint32_t num, uint32_t n) { return ((num << n) | ((num >> (32 - n)))); }
void decipther(unsigned int num_rounds, uint32_t* high, uint32_t* low) { unsigned int i;
uint32_t key[4] = { 0x03020100, 0x07060504, 0x0B0A0908, 0x0F0E0D0C };
uint32_t v0 = *high, v1 = *low, delta = 0x10325476, sum = delta * num_rounds; for (i = 0; i < num_rounds; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } *high = v0; *low = v1; }
void decode(uint32_t* mid) { uint32_t result, i;
uint32_t data[] = { 0x04050607, 0x00010203, 0x0C0D0E0F, 0x08090A0B, 0xCD3FE81B, 0xD7C45477, 0x9F3E9236, 0x0107F187, 0xF993CB81, 0xBF74166C, 0xDA198427, 0x1A05ABFF, 0x9307E5E4, 0xCB8B0E45, 0x306DF7F5, 0xAD300197, 0xAA86B056, 0x449263BA, 0x3FA4401B, 0x1E41F917, 0xC6CB1E7D, 0x18EB0D7A, 0xD4EC4800, 0xB486F92B, 0x8737F9F3, 0x765E3D25, 0xDB3D3537, 0xEE44552B, 0x11D0C94C, 0x9B605BCB, 0x903B98B3, 0x24C2EEA3, 0x896E10A2, 0x2247F0C0, 0xB84E5CAA, 0x8D2C04F0, 0x3BC7842C, 0x1A50D606, 0x49A1917C, 0x7E1CB50C, 0xFC27B826, 0x5FDDDFBC, 0xDE0FC404, 0xB2B30907 };
uint32_t xordata[2] = {*mid, *(mid + 1)};
for (i = 0; i <= 0x2b; i++) { result = ((rol(xordata[0], 1) & rol(xordata[0], 8)) ^ rol(xordata[0], 2)) ^ xordata[1] ^ data[0x2b - i]; xordata[1] = xordata[0]; xordata[0] = result; }
mid[0] = xordata[1]; mid[1] = xordata[0]; }
void re_chr(uint32_t num) { for (int i = 24; i >= 0; i -= 8) printf("%c", (num & (0xff << i)) >> i); }
|
得到了inputmM7pJIobsCTQPO6R0g-L8kFExhYuivBN
flag
CTF{mM7pJIobsCTQPO6R0g-L8kFExhYuivBN}
RE_SimpleFileSystem(可逆加密算法, 文件系统)
txt文档初始化后可以看到有很多的inode(译成中文为索引节点, 用于存放档案及目录的基本信息, 包含时间, 档名, 使用者及其群组, 只不过这道题简化成了编号)
使用IDA分析
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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
| __int64 __fastcall main(int a1, char **a2, char **a3) { int v4; // eax int *v5; // rax char *v6; // rax unsigned int v7; // eax unsigned int v8; // eax int i; // [rsp+18h] [rbp-1038h] int j; // [rsp+1Ch] [rbp-1034h] int v11; // [rsp+20h] [rbp-1030h] int v12; // [rsp+24h] [rbp-102Ch] unsigned int v13; // [rsp+28h] [rbp-1028h] unsigned int v14; // [rsp+28h] [rbp-1028h] int v15; // [rsp+28h] [rbp-1028h] unsigned int v16; // [rsp+28h] [rbp-1028h] unsigned int v17; // [rsp+28h] [rbp-1028h] unsigned int v18; // [rsp+28h] [rbp-1028h] unsigned int v19; // [rsp+28h] [rbp-1028h] unsigned int v20; // [rsp+28h] [rbp-1028h] int r_num1; // [rsp+2Ch] [rbp-1024h] int r_num2; // [rsp+30h] [rbp-1020h] time_t timer; // [rsp+38h] [rbp-1018h] BYREF char s[16]; // [rsp+40h] [rbp-1010h] BYREF char s1[1024]; // [rsp+440h] [rbp-C10h] BYREF char nptr[1024]; // [rsp+840h] [rbp-810h] BYREF char v27[1032]; // [rsp+C40h] [rbp-410h] BYREF unsigned __int64 v28; // [rsp+1048h] [rbp-8h]
v28 = __readfsqword(0x28u); if ( a1 != 3 ) // 如果参数不是三个则提示输入的格式 { printf("use: %s <diskfile> <nblocks>\n", *a2); return 1LL; } v4 = atoi(a2[2]);
if ( !(unsigned int)sub_55A3FD5D9BEC(a2[1], v4) )// 打开文件image.flag { v5 = __errno_location(); v6 = strerror(*v5); printf("couldn't initialize %s: %s\n", a2[1], v6); return 1LL; }
v7 = sub_55A3FD5D9C9E(); printf("opened emulated disk image %s with %d blocks\n", a2[1], v7); while ( 1 ) // shell的while循环 { printf(" simplefs> "); // 打印输入提示符 fflush(stdout); if ( !fgets(s, 1024, stdin) ) // 输入指令 break; if ( s[0] != '\n' ) // 如果有输入内容 { s[strlen(s) - 1] = 0; // 把结尾的'\n'改为'\0' v11 = __isoc99_sscanf(s, "%s %s %s", s1, nptr, v27);// 输入三个参数(也可以不输入这么多, 这个只是最大值), 从0号开始检验 if ( v11 ) // 其中v11是成功录入的变量数量, 根据这个判断输入是否有效 { if ( !strcmp(s1, "format") ) { if ( v11 == 1 ) { if ( (unsigned int)sub_55A3FD5D8357() )// 这里应该是为该系统分配磁盘, 在这里初始化了 puts("disk formatted."); // 磁盘格式化 else puts("format failed!"); } else { puts("use: format"); } } else if ( !strcmp(s1, "mount") ) // 挂载: 是指由操作系统使一个存储设备(诸如硬盘、CD-ROM或共享资源)上的计算机文件和目录可供用户通过计算机的文件系统访问的一个过程 { if ( v11 == 1 ) { if ( (unsigned int)sub_55A3FD5D87C2() )// 这里设置了inode_index为1 puts("disk mounted."); else puts("mount failed!"); } else { puts("use: mount"); } } else if ( !strcmp(s1, "debug") ) { if ( v11 == 1 ) sub_55A3FD5D84BB(); else puts("use: debug"); } else if ( !strcmp(s1, "getsize") ) { if ( v11 == 2 ) { v13 = atoi(nptr); v12 = sub_55A3FD5D9014(v13); if ( v12 < 0 ) puts("getsize failed!"); else printf("inode %d has size %d\n", v13, (unsigned int)v12); } else { puts("use: getsize <inumber>"); } } else if ( !strcmp(s1, "create") ) { if ( v11 == 1 ) sub_55A3FD5D816A(); else puts("use: create"); } else if ( !strcmp(s1, "delete") ) { if ( v11 == 2 ) { v14 = atoi(nptr); if ( (unsigned int)sub_55A3FD5D8DD1(v14) ) printf("inode %d deleted.\n", v14); else puts("delete failed!"); } else { puts("use: delete <inumber>"); } } else if ( !strcmp(s1, "cat") ) { if ( v11 == 2 ) { v15 = atoi(nptr); if ( !(unsigned int)sub_55A3FD5D8017(v15, "/dev/stdout") ) puts("cat failed!"); } else { puts("use: cat <inumber>"); } } else if ( !strcmp(s1, "captureflag") ) // 这个应该是个无用指令 { puts("Are you kidding?"); } else if ( !strcmp(s1, "plantflag") ) // 可疑代码 { v8 = time(&timer); // 记录现在的时间戳到v8和timer中 srand(v8); // 随机数播种 r_num1 = rand() % 100; // 100以内的随机数 r_num2 = rand() % 100; // 同上 for ( i = 0; i < r_num1; ++i ) // 循环随机的次数 { v16 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v16, 2) ) printf("copied file %s to inode %d\n", nptr, v16); else puts("copy failed!"); }
v17 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v17, 1) ) printf("plant flag to inode %d!\n", v17); else puts("copy failed!");
for ( j = 0; j < r_num2; ++j ) { v18 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v18, 2) ) printf("copied file %s to inode %d\n", nptr, v18); else puts("copy failed!"); } } else if ( !strcmp(s1, "copyin") ) { if ( v11 == 3 ) { v19 = atoi(v27); if ( !(unsigned int)sub_55A3FD5D7E16(nptr, v19, 0) ) goto LABEL_69; printf("copied file %s to inode %d\n", nptr, v19); } else { puts("use: copyin <filename> <inumber>"); } } else if ( !strcmp(s1, "copyout") ) { if ( v11 == 3 ) { v20 = atoi(nptr); if ( (unsigned int)sub_55A3FD5D8017(v20, v27) ) printf("copied inode %d to file %s\n", v20, v27); else LABEL_69: puts("copy failed!"); } else { puts("use: copyout <inumber> <filename>"); } } else if ( !strcmp(s1, "help") ) { puts("Commands are:"); puts(" format"); puts(" mount"); puts(" debug"); puts(" create"); puts(" delete <inode>"); puts(" cat <inode>"); puts(" captureflag"); puts(" plantflag"); puts(" copyin <file> <inode>"); puts(" copyout <inode> <file>"); puts(" help"); puts(" quit"); puts(" exit"); } else { if ( !strcmp(s1, "quit") || !strcmp(s1, "exit") ) break; printf("unknown command: %s\n", s1); puts("type 'help' for a list of commands."); } } } } puts("closing emulated disk."); sub_55A3FD5D9E6B(); return 0LL; }
|
可以看到跟shell一样, 我们要确定大致的分析方向, 知道哪个指令是比较关键
可以看到前两个指令都是初始化, 后面的debug, create, delete, cat, copyin, copyout, help, quit, exit都是关于文件操作的指令, 不重要. 所以排除法得到了两个关键的指令captureflag 和 plantflag. 而通过分析法相captureflag是一个无用指令, 所以我们可以锁定关键的指令就是plantflag.
同时我们通过调试发现所有索引上的数据都是通过flag文本文件经过加密后随机生成的
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
| else if ( !strcmp(s1, "plantflag") ) // 可疑代码 { v8 = time(&timer); // 记录现在的时间戳到v8和timer中 srand(v8); // 随机数播种 r_num1 = rand() % 100; // 100以内的随机数 r_num2 = rand() % 100; // 同上 for ( i = 0; i < r_num1; ++i ) // 循环随机的次数 { v16 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v16, 2) ) printf("copied file %s to inode %d\n", nptr, v16); else puts("copy failed!"); }
v17 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v17, 1) ) printf("plant flag to inode %d!\n", v17); else puts("copy failed!");
for ( j = 0; j < r_num2; ++j ) { v18 = sub_55A3FD5D816A(); if ( (unsigned int)sub_55A3FD5D7E16("flag", v18, 2) ) printf("copied file %s to inode %d\n", nptr, v18); else puts("copy failed!"); } }
|
而查看其代码内部可以发现两个可疑函数sub_55A3FD5D816A()
和sub_55A3FD5D7E16()
.
经过调试以后我们在sub_55A3FD5D7E16()
中发现一个加密函数sub_55A3FD5D81B2((__int64)str, len);
跟进查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| __int64 __fastcall sub_55A3FD5D81B2(__int64 a1, int a2) { int i; // [rsp+10h] [rbp-10h] int xordata; // [rsp+14h] [rbp-Ch] _BYTE *str2; // [rsp+18h] [rbp-8h]
xordata = sub_55A3FD5D90A3(); for ( i = 0; i < a2; ++i ) { str2 = (_BYTE *)(i + a1); *str2 = (*str2 >> 1) | (*str2 << 7); *str2 ^= xordata; *str2 = ((unsigned __int8)*str2 >> 2) | (*str2 << 6); *str2 ^= BYTE1(xordata); *str2 = ((unsigned __int8)*str2 >> 3) | (32 * *str2); *str2 ^= BYTE2(xordata); *str2 = ((unsigned __int8)*str2 >> 4) | (16 * *str2); *str2 ^= HIBYTE(xordata); *str2 = (*str2 >> 5) | (8 * *str2); // str2 << 3 } return 0LL; }
|
我们在这个函数下下断点
可以看到加密的过程
经过加密后的数据
我们知道flag的前四个字符就是*CTF, 所以可以通过这四个字符加密过后的数据在image文件中查找并得到加密后的flag
我们在HexEditor中查找0x00, 0xd2, 0xfc, 0xd8
将数据dump下来并解密
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
|
void decode(uint8_t* output, unsigned int n);
int main(void) { uint8_t output[] = { 0x00, 0xd2, 0xfc, 0xd8, 0xa2, 0xda, 0xba, 0x9e, 0x9c, 0x26, 0xf8, 0xf6, 0xb4, 0xce, 0x3c, 0xcc,0x96, 0x88, 0x98, 0x34, 0x82, 0xde, 0x80, 0x36, 0x8a, 0xd8, 0xc0, 0xf0, 0x38, 0xae, 0x40 }; decode(output, sizeof(output));
return 0; }
void decode(uint8_t* output, unsigned int n) { uint32_t xordata[4] = { 0xDEEDBEEF, 0xDEEDBE, 0xDEED, 0xDE}; uint8_t tmp; for (int i = 0; i < n; i++) { tmp = *(output + i); tmp = (tmp << 5) | (tmp >> 3); tmp ^= xordata[3]; tmp = (tmp << 4) | (tmp >> 4); tmp ^= xordata[2]; tmp = (tmp << 3) | (tmp >> 5); tmp ^= xordata[1]; tmp = (tmp << 2) | (tmp >> 6); tmp ^= xordata[0]; tmp = (tmp << 1) | (tmp >> 7); printf("%c", tmp); } printf("\n"); }
|
即可得到flag
flag
CTF{Gwed9VQpM4Lanf0kEj1oFJR6}
RE_Jump(setjump和longjump打乱逻辑顺序, Burrows-Wheeler压缩算法)
关于setjump和longjump更加详细的介绍可以看CSAPP第八章的最靠后的部分, 里面有讲到setjump和longjump
进入IDA分析可以发现是去符号表了, 但是还是可以分析的.
我们同通过字符串查找找到了核心函数
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
| __int64 __fastcall sub_40293E(__int64 a1, __int64 a2) { int v2; // edx int v3; // ecx int v4; // r8d int v5; // r9d int v7; // edx int v8; // ecx int v9; // r8d int v10; // r9d __int64 v11; // rdx int v12; // edx int v13; // ecx int v14; // r8d int v15; // r9d int v16; // edx int v17; // ecx int v18; // r8d int v19; // r9d __int64 v20; // rdx int v21; // ecx int v22; // r8d int v23; // r9d __int64 v24; // rdx int v25; // edx int v26; // ecx int v27; // r8d int v28; // r9d int v29; // edx int v30; // ecx int v31; // r8d int v32; // r9d char v33; // [rsp+0h] [rbp-10h] char v34; // [rsp+0h] [rbp-10h] char v35; // [rsp+0h] [rbp-10h] char v36; // [rsp+0h] [rbp-10h] char v37; // [rsp+0h] [rbp-10h] int v38; // [rsp+Ch] [rbp-4h] int v39; // [rsp+Ch] [rbp-4h] int v40; // [rsp+Ch] [rbp-4h] int v41; // [rsp+Ch] [rbp-4h]
sub_419570(&input); // scanf v38 = setjmp(&status, a2, v2, v3, v4, v5, v33); if ( !v38 ) sub_402689(); // (0) if ( v38 == 1 ) return 1LL; input23_addr = mb_malloc(len + 1, 1uLL); if ( !setjmp(&status, 1LL, v7, v8, v9, v10, v34) )// (1) 运行到了这里随后向下继续运行到了下一个setjmp longjmp(&off_4C9460); qword_4C9408 = sub_427140(8LL * len, 1LL, v11);
v39 = setjmp(&status, 1LL, v12, v13, v14, v15, v35);// (2) 设下坐标setjmp, 初始化了1 // (4) 在为input_change申请完内存空间后又回到了这里 if ( !v39 ) longjmp(&off_4C9460); if ( v39 <= len ) longjmp(&off_4C9460); sub_401F62(qword_4C9408, 1uLL); v40 = setjmp(&status, 1LL, v16, v17, v18, v19, v36); if ( !v40 ) sub_402826(&status, 1LL, v20, v21, v22, v23);// ****** if ( v40 <= len ) longjmp(&off_4C9540); sub_427730(qword_4C9408, 1LL, v20); sub_427730(input23_addr, 1LL, v24); // 真正的变换在这里 v41 = setjmp(&status, 1LL, v25, v26, v27, v28, v37); if ( !v41 ) longjmp(&off_4C9540); if ( v41 == 1 ) sub_411920("*CTF{%s}\n", &input, v29, v30, v31, v32);// 其实看到这里的时候就不应该把关注的重点放在input上面了, 因为如果对其变换的话这里的打印就不是我们输入的明文了 return 0LL; }
|
setjmp会将当前的上下文保存在status中, 而longjmp会跳回最近一次的setjmp并将其参数作为setjmp的返回值.
所以看起来这个函数中只有if语句, 其实是有很多的for语句的.
还需要注意的是longjmp不能F8步过, 相当于把直接跳过了for循环的所有语句了, 只能F7步入然后跳转到相应的地方才能进行分析
最后得到了其加密算法是Burrows-Wheeler, 是单纯的换序算法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| s = '\03jmGn_=uaSZLvN4wFxE6R+p\02D2qV1CBTck' lst = [[0]] * 34
for i in range(34): lst[i] = s[i]
tmp = [[0]] * 34
for i in range(34): tmp[i] = s[i]
for k in range(34): lst.sort() for i in range(34): lst[i] = tmp[i] + lst[i]
print(lst)
|