赞
踩
babysmc 主函数逻辑如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[36]; // [esp+0h] [ebp-28h] BYREF
memset(v4, 0, 0x22u);
printf("please input: \n", v4[0]);
scanf("%34s", v4);
patch_code1(v4[0], v4[33]);
((void (__cdecl *)(char *, int))dword_401850)(&v4[1], 32);
if ( (unsigned __int8)sub_4018C0(&v4[1], 16) && (unsigned __int8)sub_402F30(&v4[17]) )
printf("Great\nYour input is right!!!\n", v4[0]);
else
printf("Sorry\nYour input is wrong...\n", v4[0]);
return 0;
}
可以看到在输入 34 字节长度的字符串后会以输入的第一个和最后一个字节作为参数调用 patch_code1 函数。
patch_code1 以及它调用的 patch_code2 函数逻辑如下:
BOOL __cdecl patch_code2(char a1) { DWORD flOldProtect; // [esp+0h] [ebp-Ch] BYREF SIZE_T dwSize; // [esp+4h] [ebp-8h] SIZE_T i; // [esp+8h] [ebp-4h] dwSize = 256; VirtualProtect(unk_4017C0, 0x100u, 0x40u, &flOldProtect); for ( i = 0; i < dwSize; ++i ) unk_4017C0[i] ^= a1; return VirtualProtect(unk_4017C0, dwSize, flOldProtect, &flOldProtect); } BOOL __cdecl patch_code1(char a1, char a2) { DWORD flOldProtect; // [esp+0h] [ebp-Ch] BYREF SIZE_T dwSize; // [esp+4h] [ebp-8h] SIZE_T i; // [esp+8h] [ebp-4h] dwSize = 256; VirtualProtect(dword_401850, 0x100u, 0x40u, &flOldProtect); for ( i = 0; i < dwSize; ++i ) dword_401850[i] ^= a1; VirtualProtect(dword_401850, dwSize, flOldProtect, &flOldProtect); return patch_code2(a2); }
两个函数 patch 的范围如下:
在执行完 patch_code1 之后,主函数又调用了 0x401850 和 0x4018c0 两处地址,因此这两个地址是函数入口。
一般情况下函数入口处的第一条指令是 push ebp
,对应的硬编码为 0x55 。
因此可以确定 a1 = 0x23,a2 = 0x21 。
使用如下 IDAPython 脚本将代码解密。
import ida_bytes
def patch_code(addr, len, a):
content = bytearray(ida_bytes.get_bytes(addr, len))
for _ in range(len): content[_] ^= a
ida_bytes.patch_bytes(addr, bytes(content))
patch_code(0x401850, 256, 0x23)
patch_code(0x4017C0, 256, 0x21)
print("done")
解密后的 sub_401850 内容:
unsigned int __cdecl sub_401850(char *a1, int a2)
{
unsigned int result; // eax
unsigned int i; // [esp+0h] [ebp-14h]
char v4[12]; // [esp+4h] [ebp-10h] BYREF
memset(v4, 0, 0xAu);
result = sub_4017C0(v4);
for ( i = 0; i < 10; ++i )
result = funcs_4018AB[(unsigned __int8)v4[i]]((int)a1, a2);
return result;
}
分析可知,函数首先调用 sub_4017C0 初始化 v4 数组,之后根据 v4 数组中的内容依次调用 funcs_4018AB 数组中的函数对输入的 1 到 32 字节进行变换。
sub_4017C0 函数内容如下:
char __cdecl sub_401780(unsigned int a1) { unsigned int i; // [esp+0h] [ebp-4h] for ( i = 2; i <= a1 >> 1; ++i ) { if ( !(a1 % i) ) return 0; } return 1; } unsigned int __cdecl sub_4017C0(_BYTE *a1) { unsigned int result; // eax unsigned int i; // [esp+0h] [ebp-8h] unsigned int v3; // [esp+4h] [ebp-4h] *a1 = 2; a1[1] = 3; result = 2; v3 = 2; for ( i = 4; i <= 0x1E && v3 < 0xA; ++i ) { result = (unsigned __int8)sub_401780(i); if ( (_BYTE)result ) { a1[v3] = i; result = ++v3; } } return result; }
将上面的代码复制到 IDE 中运行可以得到 v4 的内容:
2,3,5,7,11,13,17,19,23,29
funcs_4018AB 是一个长度为 30 的函数表
函数表中的函数功能相似,其中的 sub_401000 函数内容如下。这些都是通过一个表对输入进行变换。
unsigned int __cdecl sub_401000(char *a, unsigned int len)
{
unsigned int result; // eax
unsigned int i; // [esp+0h] [ebp-4h]
for ( i = 0; i < len; ++i )
{
a[i] = byte_422080[(unsigned __int8)a[i]];
result = i + 1;
}
return result;
}
由于函数结构相似因此直接利用脚本将每个函数对应表中的数据提取出来:
import ida_bytes
from idc import *
funcs_4018AB = get_name_ea_simple('funcs_4018AB')
s = []
for i in range(30):
func_addr = get_wide_dword(funcs_4018AB + i * 4)
data_addr = get_name_ea_simple(print_operand(func_addr + 45, 1))
s += [list(ida_bytes.get_bytes(data_addr, 256))]
print(s)
最后 sub_4018C0 和 sub_402F30 都是通过一组等式对变换后的输入进行验证。
由于存在多解,因此可以按照定义列出方程求解出变换后的 a,之后按照逆过程变换检验 flag 是否都是可见字符。主要代码如下(完整代码见附件):
from z3 import * if __name__ == '__main__': s = Solver() a = [BitVec("%d" % i, 8) for i in range(32)] s.add(dword_422000[12] == 199 * a[9] + 98 * a[7] + 192 * a[8] + 23 * a[12] + 79 * a[14] + 77 * a[10] + 185 * a[13] + 135 * a[15] + 119 * a[4] + 54 * a[0] + 41 * a[1] + 124 * a[6] + 18 * a[2] + 181 * a[11] + 191 * a[5] + 7 * a[3]) s.add(dword_422000[3] == 210 * a[11] + 26 * a[15] + 169 * a[0] + 177 * a[13] + a[6] + 205 * a[8] + 223 * a[10] + 32 * a[5] + 225 * a[3] + 61 * a[14] + 72 * a[1] + 186 * a[9] + 253 * a[12] + 205 * a[2] + 49 * a[4] + 232 * a[7]) s.add(dword_422000[13] == 192 * a[3] + 22 * a[10] + 175 * a[1] + 184 * a[7] + 116 * a[15] + 70 * a[13] + 153 * a[14] + 119 * a[0] + 217 * a[6] + 123 * a[5] + 17 * a[2] + 244 * a[12] + 116 * a[8] + 46 * a[4] + 19 * a[9] + 130 * a[11]) s.add(dword_422000[7] == 41 * a[12] + 71 * a[7] + 185 * a[1] + 69 * a[11] + 142 * a[8] + 221 * a[5] + 24 * a[3] + 208 * a[6] + 41 * a[9] + 159 * a[2] + 231 * a[14] + 235 * a[13] + 225 * a[0] + (a[4] << 6) + 162 * a[10] + 134 * a[15]) s.add(dword_422000[11] == 36 * a[12] + 220 * a[4] + 110 * a[13] + 45 * a[7] + 123 * a[9] + 133 * a[1] + 101 * a[5] + 137 * a[10] + 102 * a[0] + 227 * a[14] + 94 * a[15] + 18 * a[2] + 22 * a[6] + 189 * a[11] + 218 * a[8]) s.add(dword_422000[15] == 86 * a[11] + 31 * a[9] + 229 * a[6] + 27 * a[3] + 6 * a[12] + 13 * a[10] + 158 * a[1] + 89 * a[7] + 35 * a[15] + 126 * a[8] + 165 * a[13] + 220 * a[0] + 138 * a[5] + 100 * a[4] + 84 * a[14] + 175 * a[2]) s.add(dword_422000[8] == 7 * a[1] + 28 * a[8] + 131 * a[10] + 6 * a[6] + 254 * a[0] + 130 * a[13] + 124 * a[3] + 55 * a[12] + 157 * a[14] + 175 * a[5] + 140 * a[4] + 241 * a[9] + 11 * a[11] + 211 * a[2] + 121 * a[7] + 200 * a[15]) s.add(dword_422000[6] == 195 * a[14] + 197 * a[13] + 218 * a[7] + 83 * a[1] + 98 * a[2] + 70 * a[10] + 229 * a[15] + 148 * a[11] + 195 * a[0] + 94 * a[6] + 211 * a[12] + 220 * a[9] + 81 * a[5] + 253 * a[8] + 78 * a[4] + 4 * a[3]) s.add(dword_422000[14] == 3 * a[4] + 136 * a[7] + 156 * a[3] + 189 * a[1] + 244 * a[12] + 157 * a[15] + 83 * a[9] + 6 * a[0] + 113 * a[6] + 63 * a[14] + 35 * a[2] + 22 * a[8] + 26 * a[10] + 62 * a[11] + 98 * a[5] + 110 * a[13]) s.add(dword_422000[4] == 96 * a[4] + 248 * a[8] + 191 * a[9] + 194 * a[2] + 154 * a[1] + 31 * a[6] + 157 * a[7] + 248 * a[13] + 81 * a[15] + 56 * a[10] + 52 * a[0] + 94 * a[12] + 212 * a[5] + 83 * a[3] + 83 * a[14] + 158 * a[11]) s.add(dword_422000[1] == 67 * a[4] + 220 * a[2] + 123 * a[11] + 168 * a[5] + 23 * a[12] + 148 * a[7] + 127 * a[10] + 194 * a[1] + 132 * a[8] + 44 * a[0] + 60 * a[13] + 98 * a[15] + 38 * a[14] + 245 * a[9] + 159 * a[6] + 146 * a[3]) s.add(dword_422000[5] == 132 * a[3] + 10 * a[7] + 95 * a[0] + 83 * a[10] + 99 * a[1] + 77 * a[12] + 195 * a[2] + 47 * a[6] + 38 * a[13] + 178 * a[8] + 74 * a[4] + 86 * a[11] + 208 * a[9] + 240 * a[14] + 120 * a[5] + 43 * a[15]) s.add(dword_422000[9] == 172 * a[1] + 110 * a[2] + 92 * a[7] + 126 * a[15] + 91 * a[0] + 77 * a[6] + 207 * a[5] + 249 * a[11] + 240 * a[12] + 129 * a[10] + 6 * a[13] + 100 * a[3] + a[14] + 76 * a[9] + 127 * a[4] + 4 * a[8]) s.add(dword_422000[10] == 46 * a[15] + 37 * a[0] + 3 * a[3] + 72 * a[6] + 116 * a[7] + 186 * a[1] + 221 * a[14] + 236 * a[4] + 79 * a[2] + 175 * a[10] + 184 * a[9] + 160 * a[11] + 227 * a[12] + 99 * a[8] + 71 * a[13] + 4 * a[5]) s.add(dword_422000[0] == 203 * a[3] + 31 * a[0] + 11 * a[14] + 149 * a[7] + 215 * a[5] + 206 * a[1] + 245 * a[6] + 9 * a[11] + 16 * a[10] + 241 * a[13] + 110 * a[8] + 175 * a[2] + 38 * a[4] + 227 * a[9] + 208 * a[12] + 8 * a[15]) s.add(dword_422000[2] == 132 * a[3] + 119 * a[14] + 26 * a[8] + 24 * a[6] + 121 * a[11] + 235 * a[2] + 228 * a[12] + 34 * a[5] + 37 * a[15] + 24 * a[9] + 145 * a[13] + 199 * a[4] + 173 * a[10] + 58 * a[0] + 246 * a[7] + 199 * a[1]) s.add(dword_422040[0] == 159 * a[16 + 8] + 109 * a[16 + 12] + 14 * a[16 + 0] + 92 * a[16 + 14] + 211 * a[16 + 4] + 178 * a[16 + 7] + 57 * a[16 + 2] + 175 * a[16 + 5] + 170 * a[16 + 11] + 59 * a[16 + 6] + 200 * a[16 + 9] + 5 * a[16 + 15] + 48 * a[16 + 13] + 28 * a[16 + 3] + 18 * a[16 + 10] + 228 * a[16 + 1]) s.add(dword_422040[6] == 173 * a[16 + 11] + 34 * a[16 + 5] + 69 * a[16 + 4] + 216 * a[16 + 14] + 225 * a[16 + 9] + 160 * a[16 + 1] + 207 * a[16 + 10] + 175 * a[16 + 7] + 121 * a[16 + 0] + 122 * a[16 + 2] + 179 * a[16 + 12] + 91 * a[16 + 13] + 181 * a[16 + 8] + 93 * a[16 + 3] + 121 * a[16 + 6] + 12 * a[16 + 15]) s.add(dword_422040[8] == 215 * a[16 + 11] + 164 * a[16 + 5] + 97 * a[16 + 2] + 99 * a[16 + 3] + 188 * a[16 + 4] + (a[16 + 9] << 7) + 214 * a[16 + 6] + 106 * a[16 + 8] + 169 * a[16 + 0] + 28 * a[16 + 14] + 18 * a[16 + 12] + a[16 + 1] + 177 * a[16 + 10] + 114 * a[16 + 7] + 176 * a[16 + 15] + 25 * a[16 + 13]) s.add(dword_422040[9] == 175 * a[16 + 14] + 42 * a[16 + 4] + 214 * a[16 + 12] + 43 * a[16 + 13] + 147 * a[16 + 6] + 53 * a[16 + 10] + 12 * a[16 + 1] + 213 * a[16 + 7] + 241 * a[16 + 9] + 223 * a[16 + 5] + 65 * a[16 + 3] + 42 * a[16 + 15] + 131 * a[16 + 2] + 81 * a[16 + 0] + 92 * a[16 + 11] + 110 * a[16 + 8]) s.add(dword_422040[13] == 57 * a[16 + 0] + 109 * a[16 + 7] + 60 * a[16 + 2] + 228 * a[16 + 13] + 166 * a[16 + 4] + 236 * a[16 + 9] + 100 * a[16 + 6] + 179 * a[16 + 11] + 20 * a[16 + 12] + 45 * a[16 + 8] + 204 * a[16 + 3] + 182 * a[16 + 14] + 84 * a[16 + 10] + 170 * a[16 + 15] + 199 * a[16 + 5] + 138 * a[16 + 1]) s.add(dword_422040[10] == 98 * a[16 + 11] + 122 * a[16 + 9] + 237 * a[16 + 12] + 117 * a[16 + 0] + 34 * a[16 + 3] + 168 * a[16 + 8] + 135 * a[16 + 10] + 119 * a[16 + 6] + 91 * a[16 + 2] + 161 * a[16 + 15] + 152 * a[16 + 7] + 186 * a[16 + 4] + 187 * a[16 + 13] + 72 * a[16 + 14] + 36 * a[16 + 5] + 171 * a[16 + 1]) s.add(dword_422040[7] == 184 * a[16 + 9] + 112 * a[16 + 0] + 107 * a[16 + 11] + 170 * a[16 + 13] + 55 * a[16 + 8] + 85 * a[16 + 14] + 212 * a[16 + 10] + 173 * a[16 + 15] + 166 * a[16 + 12] + 142 * a[16 + 4] + 202 * a[16 + 5] + 63 * a[16 + 2] + 30 * a[16 + 7] + 175 * a[16 + 3] + 217 * a[16 + 6] + 63 * a[16 + 1]) s.add(dword_422040[15] == (a[16 + 7] << 6) + 228 * a[16 + 4] + 90 * a[16 + 11] + 85 * a[16 + 3] + 196 * a[16 + 6] + 219 * a[16 + 0] + 93 * a[16 + 14] + 183 * a[16 + 15] + 156 * a[16 + 12] + 197 * a[16 + 8] + 119 * a[16 + 13] + 36 * a[16 + 10] + 205 * a[16 + 2] + 94 * a[16 + 9] + 153 * a[16 + 5]) s.add(dword_422040[5] == 9 * a[16 + 4] + (a[16 + 5] << 6) + 62 * a[16 + 1] + 58 * a[16 + 7] + 100 * a[16 + 13] + 137 * a[16 + 11] + 6 * a[16 + 0] + 119 * a[16 + 9] + 180 * a[16 + 6] + 228 * a[16 + 8] + 88 * a[16 + 12] + 107 * a[16 + 15] + 56 * a[16 + 14] + 207 * a[16 + 2] + 248 * a[16 + 10] + 150 * a[16 + 3]) s.add(dword_422040[3] == 38 * a[16 + 7] + 194 * a[16 + 4] + 105 * a[16 + 0] + 150 * a[16 + 6] + 75 * a[16 + 1] + 89 * a[16 + 15] + 99 * a[16 + 14] + 98 * a[16 + 3] + 91 * a[16 + 8] + 178 * a[16 + 12] + 117 * a[16 + 2] + 48 * a[16 + 13] + 239 * a[16 + 10] + 233 * a[16 + 11] + 63 * a[16 + 5] + 250 * a[16 + 9]) s.add(dword_422040[11] == 30 * a[16 + 8] + 13 * a[16 + 5] + 206 * a[16 + 3] + 234 * a[16 + 15] + 71 * a[16 + 7] + 239 * a[16 + 12] + 141 * a[16 + 10] + 179 * a[16 + 13] + 113 * a[16 + 14] + 181 * a[16 + 9] + 52 * a[16 + 6] + 74 * a[16 + 11] + 168 * a[16 + 4] + 239 * a[16 + 1] + 164 * a[16 + 0] + 179 * a[16 + 2]) s.add(dword_422040[14] == 211 * a[16 + 1] + 74 * a[16 + 5] + 144 * a[16 + 8] + 234 * a[16 + 0] + 241 * a[16 + 2] + 157 * a[16 + 11] + 25 * a[16 + 15] + 6 * a[16 + 10] + 243 * a[16 + 6] + 107 * a[16 + 9] + 77 * a[16 + 12] + 127 * a[16 + 4] + 67 * a[16 + 7] + 13 * a[16 + 14] + 151 * a[16 + 3] + 127 * a[16 + 13]) s.add(dword_422040[2] == 209 * a[16 + 9] + 110 * a[16 + 7] + 22 * a[16 + 10] + 102 * a[16 + 11] + 187 * a[16 + 1] + 58 * a[16 + 8] + 236 * a[16 + 6] + 146 * a[16 + 13] + 205 * a[16 + 15] + 63 * a[16 + 2] + 211 * a[16 + 4] + 152 * a[16 + 3] + 82 * a[16 + 14] + 14 * a[16 + 5] + 49 * a[16 + 12] + 251 * a[16 + 0]) s.add(dword_422040[12] == 230 * a[16 + 0] + 27 * a[16 + 3] + 186 * a[16 + 10] + 58 * a[16 + 7] + 121 * a[16 + 1] + 59 * a[16 + 14] + 90 * a[16 + 12] + 40 * a[16 + 2] + 230 * a[16 + 11] + 25 * a[16 + 6] + 198 * a[16 + 5] + 81 * a[16 + 4] + 71 * a[16 + 13] + 180 * a[16 + 8] + 149 * a[16 + 9] + 73 * a[16 + 15]) s.add(dword_422040[4] == 188 * a[16 + 5] + 80 * a[16 + 1] + 221 * a[16 + 6] + (a[16 + 12] << 6) + 230 * a[16 + 3] + 123 * a[16 + 8] + 124 * a[16 + 11] + 253 * a[16 + 0] + 202 * a[16 + 10] + 63 * a[16 + 2] + 40 * a[16 + 7] + 109 * a[16 + 9] + 195 * a[16 + 15] + 199 * a[16 + 13] + 82 * a[16 + 4] + 225 * a[16 + 14]) s.add(dword_422040[1] == 236 * a[16 + 15] + 44 * a[16 + 14] + 214 * a[16 + 13] + 52 * a[16 + 8] + 37 * a[16 + 6] + 101 * a[16 + 9] + 244 * a[16 + 10] + 238 * a[16 + 11] + 109 * a[16 + 0] + 188 * a[16 + 1] + 20 * a[16 + 3] + 87 * a[16 + 7] + 93 * a[16 + 4] + 158 * a[16 + 5] + 105 * a[16 + 12] + 3 * a[16 + 2]) rmap = [[-1 for _ in range(len(map[0]))] for _ in range(len(map))] for i in range(len(map)): for j in range(len(map[i])): rmap[i][map[i][j]] = j assert -1 not in rmap[i] while s.check() == sat: m = s.model() res = [int("%s" % m[a[_]]) for _ in range(len(m))] for i in range(len(id) - 1, -1, -1): for j in range(len(res)): res[j] = rmap[id[i]][res[j]] flag = chr(0x23) + "".join([chr(_) for _ in res]) + chr(0x21) if flag.isprintable(): print(flag) break
运行即可求得 flag #y0u_4r3_7h3_m4573r_0f_r3v3r51n6_!
。
将 TEAAA4.exe 用 IDA 在 main 函数下断点调试会卡死。
在导入表中搜索发现使用检测调试的 API 。
查找到该 API 的调用位置,发现程序会根据是否处在调试状态来修改 main 函数中用到的数据。
int sub_AD1050()
{
int i; // [esp+D0h] [ebp-14h]
char *v2; // [esp+DCh] [ebp-8h]
__CheckForDebuggerJustMyCode(&unk_B82015);
if ( !IsDebuggerPresent() )
{
v2 = Str2;
for ( i = 0; i < 8; ++i )
*v2++ ^= 0x75u;
}
return 0;
}
只需将该条语句 nop 掉就可以绕过此处反调试。
然而这里并不能导致 IDA 卡死,因此还存在其他检测调试的位置。在该函数位置下断点,发现 IDA 无法停下来,因此在这个函数调用前执行了检测调试的代码。
继续查找该函数的引用,发现该函数位于一个函数表中。
该函数表的边界作为参数传到了 sub_B232A0 函数中。
而 sub_B232A0 依次调用了这个函数表中的所有函数。
int __cdecl sub_B232A0(_DWORD *a1, _DWORD *a2) { int v3; // [esp+0h] [ebp-Ch] while ( a1 != a2 ) { if ( *a1 ) { v3 = ((int (__thiscall *)(_DWORD))*a1)(*a1); if ( v3 ) return v3; } ++a1; } return 0; }
通过改变断点位置可以判断出,该函数表中 sub_B232A0 函数的前一个函数 sub_AD1000 同样存在反调试。具体反调试位置在 sub_AD1A50 中,该函数内容如下:
int sub_AD1A50()
{
HMODULE LibraryW; // eax
HANDLE CurrentThread; // eax
FARPROC ZwSetInformationThread; // [esp+D0h] [ebp-8h]
__CheckForDebuggerJustMyCode(&unk_B82015);
LibraryW = LoadLibraryW(L"ntdll.dll");
ZwSetInformationThread = GetProcAddress(LibraryW, "ZwSetInformationThread");
CurrentThread = GetCurrentThread();
return ((int (__stdcall *)(HANDLE, int, _DWORD, _DWORD))ZwSetInformationThread)(CurrentThread, 0x11, 0, 0);
}
该函数调用的 ZwSetInformationThread() 是一个未被公开的 API。如果有调试器挂载在目标进程上,并且传递 0x11 给这个函数的第二个参数,操作系统将会立即迫使所有已挂载的调试器 detach,并且终止进程。
这里只需要将第二个参数 patch 成 0 即可绕过反调试。
patch 掉这两处反调试后,可以正常调试了。
main 函数内容如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char input[68]; // [esp+D0h] [ebp-48h] BYREF
__CheckForDebuggerJustMyCode(byte_652015);
memset(input, 0, 0x40u);
printf("Plz give me your flag\n");
scanf("%64s", input);
if ( strlen(input) == 32 && checkHex(input) && check(input) )
printf("Right!\n");
else
printf("Wrong!\n");
return 0;
}
输入字符串后,首先判断字符串长度等于 32 ,之后判断字符串是一个可以表示 16 进制类型的数的字符串,具体逻辑如下:
char __cdecl checkHex(char *input)
{
size_t i; // [esp+D0h] [ebp-14h]
char v3; // [esp+DFh] [ebp-5h]
__CheckForDebuggerJustMyCode(&byte_652015);
v3 = 1;
for ( i = 0; i < strlen(input); ++i )
{
if ( !isxdigit(input[i]) )
v3 = 0;
}
return v3;
}
之后是检验函数 check ,内容如下:
// local variable allocation has failed, the output may be wrong! char __cdecl check(char *input) { char Buffer[56]; // [esp+D0h] [ebp-C0h] BYREF char v3[56]; // [esp+108h] [ebp-88h] BYREF int i; // [esp+140h] [ebp-50h] _WORD hex2[8]; // [esp+14Ch] [ebp-44h] BYREF _DWORD hex1[4]; // [esp+164h] [ebp-2Ch] OVERLAPPED BYREF char tmp[9]; // [esp+17Ch] [ebp-14h] BYREF __CheckForDebuggerJustMyCode(byte_652015); memset(tmp, 0, sizeof(tmp)); memset(hex1, 0, sizeof(hex1)); memset(hex2, 0, sizeof(hex2)); for ( i = 0; i < 4; ++i ) { strncpy_s(tmp, &input[8 * i], 8u); vsscanf(tmp, "%x", &hex1[i]); vsscanf(tmp, "%x", &hex2[2 * i]); } delta = 0x6EA237A6; tea(hex1, key); tea(&hex1[1], &key[1]); tea(&hex1[2], &key[2]); if ( strncmp((const char *)hex1, data, 0x10u) ) return 0; memset(v3, 0, 0x30u); memset(v3, 45, 0x20u); crypt(*(int *)hex2, v3, 0x10u); crypt(hex2[3], &v3[9], 0x10u); crypt(hex2[2], &v3[14], 0x10u); crypt(hex2[5], &v3[19], 0x10u); crypt(hex2[4], &v3[24], 0x10u); crypt(*(int *)&hex2[6], &v3[28], 0x10u); memset(Buffer, 0, 0x30u); sub_5A10E0(Buffer, "DASCTF{%s}\n", (char)v3); printf("You get the flag: %s"); return 1; }
该函数首先将 32 字节的输入转为 4 个 DWORD 类型的数。并用魔改的 tea 对其进行加密。
对应的写出解密代码:
#include <bits/stdc++.h> using u32 = unsigned int; u32 data[4] = {0x8E7D580B, 0x0C48EEF9, 0x9A5449EA, 0x6028B46D}; u32 key[6] = {0x147D3EAD, 0x72CD938D, 0x0DC14B5C9, 0x5BC6532D, 0x0F85F32FC, 0x0CDEA7FEC}; constexpr u32 delta = 0x6EA237A6; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); for (int i = 0; i < 3; i++) { key[i] -= 1335; key[i + 1] += 1351; key[i + 2] -= 1367; key[i + 3] += 1383; } auto tea = [&](u32 *data, u32 *key) { u32 rdelta = delta * 0x10; for (int i = 0; i < 0x10; i++) { data[1] -= (key[3] + (data[0] >> 6)) ^ (rdelta + data[0]) ^ (key[2] + 8 * data[0]); data[0] -= (key[1] + (data[1] >> 6)) ^ (rdelta + data[1]) ^ (key[0] + 8 * data[1]); rdelta -= delta; } key[0] += 1335; key[1] -= 1351; key[2] += 1367; key[3] -= 1383; }; for (int i = 2; i >= 0; i--) { tea(data + i, key + i); } for (int i = 0; i < 4; i++) { std::cout << std::hex << data[i]; } return 0; }
运行结果为
b6bab60548884e957d3952225fd29278
将其输入到程序中即可得到 flag:DASCTF{b6bab605-4888-4e95-7d39-52225fd29278}
。
ida 分析 babytea.exe,其中主函数内容如下。
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int i; // [esp+4h] [ebp-28h] char input[32]; // [esp+8h] [ebp-24h] BYREF memset(input, 0, sizeof(input)); printf("please input: \n"); scanf("%32s", input); for ( i = 0; i < 4; ++i ) tea(t, (unsigned int *)&input[8 * i], key); if ( !memcmp(input, &data, 32u) ) printf("Great\nYour input is right!!!\n"); else printf("Sorry\nYour input is wrong...\n"); return 0; }
主函数主要逻辑是读入 32 字节的字符串,然后每 8 个字节为一组进行 tea 加密后与 data 做比较。
tea 加密函数内容如下:
int __cdecl tea(unsigned int t, unsigned int *a, int *key) { int result; // eax unsigned int i; // [esp+20h] [ebp-28h] int v; // [esp+24h] [ebp-24h] unsigned int a1; // [esp+28h] [ebp-20h] unsigned int a0; // [esp+2Ch] [ebp-1Ch] a0 = *a; a1 = a[1]; v = 0; for ( i = 0; i < t; ++i ) { v += delta; a0 += (key[1] + (a1 >> 5)) ^ (v + a1) ^ (*key + 16 * a1); a1 += (key[3] + (a0 >> 5)) ^ (v + a0) ^ (key[2] + 16 * a0); } *a = a0; result = 4; a[1] = a1; return result; }
函数逻辑比较简单。但是查看汇编发信该函数中存在异常处理代码,而这些代码不会出现在 IDA 的反编译代码中,因此需要对函数汇编进行分析。
函数的栈结构如下:
-00000038 key3 dd ?
-00000034 key2 dd ?
-00000030 key1 dd ?
-0000002C key0 dd ?
-00000028 i dd ?
-00000024 v dd ?
-00000020 a1 dd ?
-0000001C a0 dd ?
-00000018 ms_exc CPPEH_RECORD ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 t dd ?
+0000000C a dd ?
+00000010 key dd ? ; offset
首先函数初始化局部变量以及异常处理结构。
.text:00401000 push ebp .text:00401001 mov ebp, esp .text:00401003 push 0FFFFFFFEh .text:00401005 push offset stru_41E0C0 .text:0040100A push offset __except_handler4 .text:0040100F mov eax, large fs:0 .text:00401015 push eax .text:00401016 add esp, 0FFFFFFD8h .text:00401019 push ebx .text:0040101A push esi .text:0040101B push edi .text:0040101C mov eax, ___security_cookie .text:00401021 xor [ebp+ms_exc.registration.ScopeTable], eax .text:00401024 xor eax, ebp .text:00401026 push eax .text:00401027 lea eax, [ebp+ms_exc.registration] .text:0040102A mov large fs:0, eax .text:00401030 mov [ebp+ms_exc.old_esp], esp .text:00401033 mov eax, 4 .text:00401038 imul ecx, eax, 0 .text:0040103B mov edx, [ebp+a] .text:0040103E mov eax, [edx+ecx] .text:00401041 mov [ebp+a0], eax .text:00401044 mov ecx, 4 .text:00401049 shl ecx, 0 .text:0040104C mov edx, [ebp+a] .text:0040104F mov eax, [edx+ecx] .text:00401052 mov [ebp+a1], eax .text:00401055 mov [ebp+v], 0 .text:0040105C mov ecx, 4 .text:00401061 imul edx, ecx, 0 .text:00401064 mov eax, [ebp+key] .text:00401067 mov ecx, [eax+edx] .text:0040106A mov [ebp+key0], ecx .text:0040106D mov edx, 4 .text:00401072 shl edx, 0 .text:00401075 mov eax, [ebp+key] .text:00401078 mov ecx, [eax+edx] .text:0040107B mov [ebp+key1], ecx .text:0040107E mov edx, 4 .text:00401083 shl edx, 1 .text:00401085 mov eax, [ebp+key] .text:00401088 mov ecx, [eax+edx] .text:0040108B mov [ebp+key2], ecx .text:0040108E mov edx, 4 .text:00401093 imul eax, edx, 3 .text:00401096 mov ecx, [ebp+key] .text:00401099 mov edx, [ecx+eax] .text:0040109C mov [ebp+key3], edx
之后函数会进入第一个异常处理,由于 ecx 清 0,因此 idiv ecx
必然会触发除零异常,因此会执行 loc_4010BE 处的代码,将局部变量 a0 和 a1 分别异或 dword_41F038 和 dword_41F03C 。
.text:0040109F ; __try { // __except at loc_4010BE .text:0040109F mov [ebp+ms_exc.registration.TryLevel], 0 .text:004010A6 xor edx, edx .text:004010A8 xor eax, eax .text:004010AA inc eax .text:004010AB xor ecx, ecx .text:004010AD idiv ecx .text:004010AD ; } // starts at 40109F .text:004010AF mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh .text:004010B6 jmp short loc_4010E0 .text:004010B8 ; --------------------------------------------------------------------------- .text:004010B8 .text:004010B8 loc_4010B8: ; DATA XREF: .rdata:stru_41E0C0↓o .text:004010B8 ; __except filter // owned by 40109F .text:004010B8 mov eax, 1 .text:004010BD retn .text:004010BE ; --------------------------------------------------------------------------- .text:004010BE .text:004010BE loc_4010BE: ; DATA XREF: .rdata:stru_41E0C0↓o .text:004010BE ; __except(loc_4010B8) // owned by 40109F .text:004010BE mov esp, [ebp+ms_exc.old_esp] .text:004010C1 mov eax, [ebp+a0] .text:004010C4 xor eax, dword_41F038 .text:004010CA mov [ebp+a0], eax .text:004010CD mov ecx, [ebp+a1] .text:004010D0 xor ecx, dword_41F03C .text:004010D6 mov [ebp+a1], ecx .text:004010D9 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
修复完异常后,程序会跳转到 loc_4010E0 继续执行,这里首先会将循环变量 i 初始化为 0 。
然后跳过 i++
的代码到 loc_4010F2 处执行。
.text:004010E0 loc_4010E0: ; CODE XREF: tea+B6↑j
.text:004010E0 mov [ebp+i], 0
.text:004010E7 jmp short loc_4010F2
在 loc_4010F2 处,程序首先判断循环次数是否足够,然后将用于 tea 加密的变量 v 加上 delta 。之后进入一个异常处理。
如果在执行 idiv ecx
这条指令时触发异常,那么 ecx 需要等于 0 ,也就是说 (v >> 0x1F) == 0
。否则不会触发异常。
如果触发异常会将变量 v 异或 0x1234567 。
.text:004010F2 loc_4010F2: ; CODE XREF: tea+E7↑j .text:004010F2 mov eax, [ebp+i] .text:004010F5 cmp eax, [ebp+t] .text:004010F8 jnb loc_40118C .text:004010FE mov ecx, [ebp+v] .text:00401101 add ecx, delta .text:00401107 mov [ebp+v], ecx .text:0040110A ; __try { // __except at loc_40112D .text:0040110A mov [ebp+ms_exc.registration.TryLevel], 1 .text:00401111 mov ecx, [ebp+v] .text:00401114 shr ecx, 1Fh .text:00401117 xor edx, edx .text:00401119 xor eax, eax .text:0040111B inc eax .text:0040111C idiv ecx .text:0040111C ; } // starts at 40110A .text:0040111E mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh .text:00401125 jmp short loc_401143 .text:00401127 ; --------------------------------------------------------------------------- .text:00401127 .text:00401127 loc_401127: ; DATA XREF: .rdata:stru_41E0C0↓o .text:00401127 ; __except filter // owned by 40110A .text:00401127 mov eax, 1 .text:0040112C retn .text:0040112D ; --------------------------------------------------------------------------- .text:0040112D .text:0040112D loc_40112D: ; DATA XREF: .rdata:stru_41E0C0↓o .text:0040112D ; __except(loc_401127) // owned by 40110A .text:0040112D mov esp, [ebp+ms_exc.old_esp] .text:00401130 mov edx, [ebp+v] .text:00401133 xor edx, 1234567h .text:00401139 mov [ebp+v], edx .text:0040113C mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
之后执行的这一部分对应的是反编译代码中的
a0 += (key[1] + (a1 >> 5)) ^ (v + a1) ^ (*key + 16 * a1);
a1 += (key[3] + (a0 >> 5)) ^ (v + a0) ^ (key[2] + 16 * a0);
.text:00401143 loc_401143: ; CODE XREF: tea+125↑j .text:00401143 mov eax, [ebp+a1] .text:00401146 shl eax, 4 .text:00401149 add eax, [ebp+key0] .text:0040114C mov ecx, [ebp+a1] .text:0040114F add ecx, [ebp+v] .text:00401152 xor eax, ecx .text:00401154 mov edx, [ebp+a1] .text:00401157 shr edx, 5 .text:0040115A add edx, [ebp+key1] .text:0040115D xor eax, edx .text:0040115F add eax, [ebp+a0] .text:00401162 mov [ebp+a0], eax .text:00401165 mov eax, [ebp+a0] .text:00401168 shl eax, 4 .text:0040116B add eax, [ebp+key2] .text:0040116E mov ecx, [ebp+a0] .text:00401171 add ecx, [ebp+v] .text:00401174 xor eax, ecx .text:00401176 mov edx, [ebp+a0] .text:00401179 shr edx, 5 .text:0040117C add edx, [ebp+key3] .text:0040117F xor eax, edx .text:00401181 add eax, [ebp+a1] .text:00401184 mov [ebp+a1], eax .text:00401187 jmp loc_4010E9
之后跳转到 loc_4010E9 更新循环变量 i 的值然后进入下一次循环。
.text:004010E9 loc_4010E9: ; CODE XREF: tea+187↓j
.text:004010E9 mov edx, [ebp+i]
.text:004010EC add edx, 1
.text:004010EF mov [ebp+i], edx
跳出循环后首先会进入一个异常处理。由于 ecx = 0,因此必然触发异常,如果触发异常就将 dword_41F038 和 dword_41F03C 分别异或 a0 和 a1。
最后,程序会将计算出的 a0 和 a1 赋给 a 数组的对应位置然后返回。
.text:0040118C loc_40118C: ; CODE XREF: tea+F8↑j .text:0040118C ; __try { // __except at loc_4011AB .text:0040118C mov [ebp+ms_exc.registration.TryLevel], 2 .text:00401193 xor edx, edx .text:00401195 xor eax, eax .text:00401197 inc eax .text:00401198 xor ecx, ecx .text:0040119A idiv ecx .text:0040119A ; } // starts at 40118C .text:0040119C mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh .text:004011A3 jmp short loc_4011C6 .text:004011A5 ; --------------------------------------------------------------------------- .text:004011A5 .text:004011A5 loc_4011A5: ; DATA XREF: .rdata:stru_41E0C0↓o .text:004011A5 ; __except filter // owned by 40118C .text:004011A5 mov eax, 1 .text:004011AA retn .text:004011AB ; --------------------------------------------------------------------------- .text:004011AB .text:004011AB loc_4011AB: ; DATA XREF: .rdata:stru_41E0C0↓o .text:004011AB ; __except(loc_4011A5) // owned by 40118C .text:004011AB mov esp, [ebp+ms_exc.old_esp] .text:004011AE mov eax, [ebp+a0] .text:004011B1 mov dword_41F038, eax .text:004011B6 mov ecx, [ebp+a1] .text:004011B9 mov dword_41F03C, ecx .text:004011BF mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh .text:004011C6 .text:004011C6 loc_4011C6: ; CODE XREF: tea+1A3↑j .text:004011C6 mov edx, 4 .text:004011CB imul eax, edx, 0 .text:004011CE mov ecx, [ebp+a] .text:004011D1 mov edx, [ebp+a0] .text:004011D4 mov [ecx+eax], edx .text:004011D7 mov eax, 4 .text:004011DC shl eax, 0 .text:004011DF mov ecx, [ebp+a] .text:004011E2 mov edx, [ebp+a1] .text:004011E5 mov [ecx+eax], edx .text:004011E8 mov ecx, [ebp+ms_exc.registration.Next] .text:004011EB mov large fs:0, ecx .text:004011F2 pop ecx .text:004011F3 pop edi .text:004011F4 pop esi .text:004011F5 pop ebx .text:004011F6 mov esp, ebp .text:004011F8 pop ebp .text:004011F9 retn
根据上述分析个写出如下解密程序:
#include <bits/stdc++.h> using u8 = unsigned char; using u32 = unsigned int; constexpr u32 key[4] = {0x67452301, 0x0EFCDAB89, 0x98BADCFE, 0x10325476}; constexpr u32 delta = (int) 0x9E3779B1; constexpr u32 t = 32; u8 data[33]{0x30, 0xB5, 0x27, 0x5E, 0xF3, 0xF7, 0xBE, 0xBD, 0x8F, 0x6A, 0x51, 0xE3, 0xFE, 0x6C, 0x83, 0x5D, 0x09, 0xFA, 0x3D, 0xD8, 0x7A, 0x73, 0xFC, 0x8E, 0xA3, 0x53, 0xA8, 0x55, 0xC5, 0x4E, 0x56, 0x7A, 0x00}; u32 dword_41F038 = 0x1234567; u32 dword_41F03C = 0x89ABCDEF; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::vector<u32> v(t); for (int i = 0; i < t; i++) { v[i] = (i == 0 ? 0 : v[i - 1]) + delta; if ((v[i] >> 0x1F) == 0) { v[i] ^= 0x1234567; } } std::reverse(v.begin(), v.end()); auto tea = [&](u32 *a) { u32 a0 = a[0]; u32 a1 = a[1]; for (int i = 0; i < t; i++) { a[1] -= (key[3] + (a[0] >> 5)) ^ (v[i] + a[0]) ^ (key[2] + 16 * a[0]); a[0] -= (key[1] + (a[1] >> 5)) ^ (v[i] + a[1]) ^ (key[0] + 16 * a[1]); } a[0] ^= dword_41F038; a[1] ^= dword_41F03C; dword_41F038 = a0; dword_41F03C = a1; }; for (int i = 0; i < 4; i++) { tea((u32 *) &data[8 * i]); } std::cout << data << std::endl; return 0; }
运行即可得到 flag:600d_y0u_r34lly_kn0w_734_4nd_53h
。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。