1625-5 王子昂 总结《2018年2月2日》 【连续第490天总结】
A. Whale-CrackMe200
B.
前言
昨天搞定了Pintools的安装,但是由于win下加载了太多库导致Example中的inscount无法准确记录运行指令数,而用proccount虽然可以记录下数百万次指令和各自所属的模块,应该可以精确记录程序的用户模块所执行的指令,但是时间开销有点巨大,而且输出文件中包含了大量冗余信息,不利用读取
在我相对而言更熟悉Python的情况下选择去找了Pin_Python的模块,虽然Github上有Windows版本,但据Readme所言编译用的是VS2005版本……尝试了许多次都无法跨过这些错误
正在创建库 obj-intel64/Python_Pin.lib 和对象 obj-intel64/Python_Pin.exp
libcpmt.lib(xonce.obj) : error LNK2001: 无法解析的外部符号 _purecall
libcpmt.lib(wlocale.obj) : error LNK2001: 无法解析的外部符号 _purecall
libcpmt.lib(locale0.obj) : error LNK2001: 无法解析的外部符号 _purecall
libcpmt.lib(xlocale.obj) : error LNK2001: 无法解析的外部符号 _purecall
无奈之下只好放弃python转而查阅C-Pin的文档
然而在等待编译、安装的过程中,静态分析先出了结果……OTZ
静态分析
看到这种结构,显然是自定义的一个解释器
机器码保存在a1中,全部dump下来
然后按照运行顺序,依次照抄
第一个opcode为0x104:
case 0x104u:
v6 = *(_DWORD *)(a1 + 4 * v3);
i = v3 + 1;
push(v6);
break;
调用了一个函数
int *__cdecl push(int a1)
{
int *result; // eax
if ( (_UNKNOWN *)p == &q )
Error();
result = (int *)(p + 4); // p = p + 4
p = (int)result;
*result = a1; // *p = a1
return result;
}
p作为esp,即堆栈指针来使用
这个函数可以看出来就是push
显然,另外有一个函数对应的是pop,即sub_4013e0
每个opcode对应的指令都不复杂,按照运行逻辑一一复刻即可
复刻运行并分析
打出log如下
0 push 0x118 | [280]
2 s[280] = input() | []
3 push 0x0 | [0]
5 push 0x0 | [0, 0]
7 s[0] = 0 | []
8 push 0x2f | [47]
10 push 0x0 | [47, 0]
12 get s[0] | [47, 0]
13 sub 0-47 | [-47]
14 jqz 53 fail | []
16 push 0x18 | [24]
18 push 0x0 | [24, 0]
20 get s[0] | [24, 0]
21 plus 0+24 | [24]
22 get s[24] | [3]
23 push 0x10 | [3, 16]
25 push 0x0 | [3, 16, 0]
27 get s[0] | [3, 16, 0]
28 push 0x7 | [3, 16, 0, 7]
30 and 7&0 | [3, 16, 0]
31 plus 0+16 | [3, 16]
32 get s[16] | [3, 65]
33 xor 65^3 | [66]
34 push 0x118 | [66, 280]
36 push 0x0 | [66, 280, 0]
38 get s[0] | [66, 280, 0]
39 plus 0+280 | [66, 280]
40 get s[280] | [66, 1]
41 sub 1-66 | [-65]
42 jnz 53 succ | []
53 push 0x2f | [47]
55 push 0x0 | [47, 0]
57 get s[0] | [47, 0]
58 sub 0-47 | [-47]
59 jnz 65 succ | []
65 push 0x21c | [540]
67 printf 540 | []
68 Finshed |
单一段运行大概看不出来什么,但是可以发现第40处的指令取得了输入,然后和66作差,因为结果非0而跳向53导致结束
那么很明显第一个输入就应该是66,使其继续往下运行
于是给input[0]赋值为66,再跑一遍
...
40 get s[280] | [66, 66]
41 sub 66-66 | [0]
42 jnz 53 fail | []
44 push 0x0 | [0]
46 get s[0] | [0]
47 stack++ 0 | [1]
48 push 0x0 | [1, 0]
50 s[0] = 1 | []
51 jmp 8 | []
8 push 0x2f | [47]
10 push 0x0 | [47, 0]
12 get s[0] | [47, 1]
13 sub 1-47 | [-46]
14 jqz 53 fail | []
16 push 0x18 | [24]
18 push 0x0 | [24, 0]
20 get s[0] | [24, 1]
21 plus 1+24 | [25]
22 get s[25] | [36]
23 push 0x10 | [36, 16]
25 push 0x0 | [36, 16, 0]
27 get s[0] | [36, 16, 1]
28 push 0x7 | [36, 16, 1, 7]
30 and 7&1 | [36, 16, 1]
31 plus 1+16 | [36, 17]
32 get s[17] | [36, 110]
33 xor 110^36 | [74]
34 push 0x118 | [74, 280]
36 push 0x0 | [74, 280, 0]
38 get s[0] | [74, 280, 1]
39 plus 1+280 | [74, 281]
40 get s[281] | [74, 0]
41 sub 0-74 | [-74]
42 jnz 53 succ | []
53 push 0x2f | [47]
55 push 0x0 | [47, 0]
57 get s[0] | [47, 1]
58 sub 1-47 | [-46]
59 jnz 65 succ | []
65 push 0x21c | [540]
67 printf 540 | []
68 Finshed |
有了第二个段运行就能理出来循环的逻辑了
8-14: if(i<47){
15-33: get(x)
34-42: if(input[i]==x)
53-68: (else)return False
}
那么只需要爆破42处的指令使其无论判断正负都判断通过,再记录33处指令的结果,即可得到flag列表了
脚本
完整模拟器如下:
orders = [0x104, 0x118, 0x305, 0x104, 0x0, 0x104, 0x0, 0x404, 0x104, 0x2f, 0x104, 0x0, 0x304, 0x201, 0x303, 0xd4, 0x104, 0x18, 0x104, 0x0, 0x304, 0x101, 0x504, 0x104, 0x10, 0x104, 0x0, 0x304, 0x104, 0x7, 0x102, 0x101, 0x504, 0x402, 0x104, 0x118, 0x104, 0x0, 0x304, 0x101, 0x504, 0x201, 0x403, 0xd4, 0x104, 0x0, 0x304, 0x301, 0x104, 0x0, 0x404, 0x203, 0x20, 0x104, 0x2f, 0x104, 0x0, 0x304, 0x201, 0x403, 0x104, 0x104, 0x218, 0x405, 0x103, 0x104, 0x21c, 0x405, 0x103]
s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 110, 116, 105, 121, 76, 97, 98, 3, 36, 35, 49, 59, 19, 34, 54, 7, 21, 55, 92, 74, 124, 86, 38, 117, 88, 89, 44, 78, 124, 36, 79, 117, 94, 71, 81, 84, 14, 87, 36, 120, 67, 76, 42, 74, 10, 87, 91, 121, 44, 67, 42, 76, 127, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
input = [0]
stack = []
flag = []
i = 0
while(i!=len(orders)):
print(i, end='\t')
print(hex(orders[i])[2:], end='\t')
if(orders[i] == 0x104):
i += 1
print('push 0x%x'% orders[i], end='\t\t| ')
stack.append(orders[i])
elif(orders[i] == 0x305):
p = stack.pop()
print('s[%d] = input()'%p, end='\t\t| ')
for t in range(len(input)):
s[p+t] = input[t]
elif(orders[i] == 0x404):
p = stack.pop()
q = stack.pop()
print('s[%d] = %d'%(p, q), end='\t\t| ')
s[p] = q
elif(orders[i] == 0x304 or orders[i] == 0x504):
p = stack.pop()
print('get s[%d]'%p, end='\t\t| ')
stack.append(s[p])
elif(orders[i] == 0x201):
p = stack.pop()
q = stack.pop()
print('sub %d-%d'%(p, q), end='\t\t| ')
stack.append(p-q)
elif(orders[i] == 0x303):
i += 1
print("jqz %d" % (orders[i] // 4), end='\t')
if(stack.pop()==0):
print("succ", end='\t| ')
print(stack)
i = orders[i]//4
continue
else:
print("fail", end='\t| ')
elif(orders[i] == 0x403):
i += 1
print("jnz %d" % (orders[i] // 4), end='\t')
if(stack.pop()!=0):
print("succ", end='\t| ')
# print(stack)
# 爆破
# i = orders[i]//4
# continue
else:
print("fail", end='\t| ')
elif(orders[i] == 0x101):
p = stack.pop()
q = stack.pop()
print("plus %d+%d"%(p, q), end='\t\t| ')
stack.append(p+q)
elif(orders[i] == 0x102):
p = stack.pop()
q = stack.pop()
print("and %d&%d\t"%(p, q), end='\t\t| ')
stack.append(p & q)
elif (orders[i] == 0x402):
p = stack.pop()
q = stack.pop()
print("xor %d^%d" % (p, q), end='\t\t| ')
stack.append(p ^ q)
flag.append(p ^ q)
elif(orders[i] == 0x405):
p = stack.pop()
print("printf %d"%p, end='\t\t| ')
elif(orders[i] == 0x103):
print("Finshed\t", end='\t\t| ')
break
elif(orders[i] == 0x301):
p = stack.pop()
print("stack++ %d"% p, end='\t\t| ')
stack.append(p+1)
elif(orders[i] == 0x203):
i += 1
print("jmp %d"% (orders[i]//4), end='\t\t| ')
print(stack)
i = orders[i]//4
continue
else:
print("0x%x"%orders[i], end='\t\t| ')
break
i += 1
print(stack)
print('\n')
print("执行指令数:" + str(i))
print(flag)
for i in flag:
print(chr(i), end='')
就能得到flag
确认逻辑也是逐字符读取的,明天尝试用Pintools、Frida之类通过Hook/指令计数来爆破
C. 明日计划
PinTools、Frida