f5无门,遂动调,硬看汇编。
在主逻辑之前加载数据时有一段call strlen:
反复提到0D,应当是判断输入长度的位置,长度存储于ebp寄存器偏移-0x50的位置,多次动调输入后确认输入总长度至少大于13,同时确认大量临时数据位于ebp栈。
第一段主逻辑在此:
1、如图,第一段逻辑一开始又检查了一遍输入长度,长度达标则跳到第二段逻辑,ecx存储输入数据;循环次数为9,每次对于数据的某一位位先异或一个地址为ebp-64h的序列的对应位,再进行一个41(字符A)的减,结果对应的ascii码存储在ebp-48h中,每次跳四字节存储。
2、之后如上,进行从小到大的顺序判断,若发现逆序对跳出。
3、还有一段如上,计算所得到ebp-48h中所有内容的乘积。
4、接下来如上,得出的结果会被减0xd4c2086,作为分母除0x64。之后进入一段未知领域。
线索到这里就因为整除0的报错而夹断了。不过,我们确实可以知道flag的前9个字母是什么了。根据其为顺序数列,并且前9位乘积为0xd4c2086,我们完全可以做质因数分解:
from sympy import nextprime
p = 2
a = 0xd4c2086
ans = []
while a != 1:
while a % p:
p = nextprime(p)
a /= p
ans.append(p)
print(ans)
得到的刚好是从2开始九个顺序的质数,对上了。我们直接进行一个解密:
#include <bits/stdc++.h>
using namespace std;
int a[9]={2,3,5,7,11,13,17,19,23};
int b[9]={0x33,0x21,0x22,0x21,0x35,0x7C,0x62,0x65,0x6e};
int main()
{
for(int i=0;i<9;i++){
a[i]+='A';
a[i]^=b[i];
printf("%c",a[i]);
}
printf("\n");
return 0;
}
得到前9位为pediy2016……猜都猜得出来。
那么后四位呢?回到刚才被夹断的部分,在动调的时候,寄存器的值是可以修改的,我们把ecx的值修改为任意非0值就可以无伤跳过了。
但是,继续动态调试的时候,我们发现一件非常可怕的事情——程序自动往结束的地方走了。
考虑到代码一般是顺序执行的,我们看看这之后的函数究竟干了些啥:
我超,地狱绘图!
这种情况的代码一般已经不可读了。后续看题解才知道这是虚拟机保护产生的大量中间数据,也就是说,一串代码被改编成了这种没人能看懂的样子。
一般在这种情况下,我们有几个选择:
1、从第九位开始爆破,这种方法慢的一批,但可能可以出flag
2、尝试逐个解读指令,同样慢的一批,而且极其考验技术
3、尝试绕过报错指令,trace一波
不过嘛,之后就会明白,我们需要的不是13位密码,而是20位密码……至于为什么,因为实在是卡思路了,所以去dasctf群和NEPCTF群问了dalao,dasctf方面使用动调手动爆破出了第10到13位,又用ida trace硬调分析逻辑,将爆破位数压到了四位。而NEPCTF群的dalao直接表示血压高了,血压高就会生气,生气就会angry,angry了就会想着angr硬搓,但是angr我不会。
得,从这里爆破不现实了,解读指令又慢的离谱,我们干脆尝试绕过报错。
从刚才的idiv开始,正常来讲在跳过报错继续执行的过程中会跳转到loc_401373,但是,loc_401373只有通到sub_404284的入口,那里可是程序的出口啊QAQ。
不过嘛,loc_401373上面还有一段没有被正确识别到函数sub_401120内的东西:
似乎在idiv报错被跳过之后,退出了函数就会跑到这里来。那么直接修改报错语句,让程序直接跳到这里不是一个样吗?于是我们动调的时候就做一些手脚:
总算可以trace了。于是我们可以得到一串又臭又长的逻辑:
但是这一串逻辑太不可读了,本人的读汇编能力并不像写出这题的题解的dalao一样nb,只能放了。不过手法还是可以活用于下一次的。
所以,就算假设我们手动过了后4位,那么后面连接的串长度未知,也只能尝试爆破剩下的n位了。下面是在下和do1phin一起搞出来的搞毛玩意:
import time
from pymouse import PyMouse
from pykeyboard import PyKeyboard
k = PyKeyboard()
m = PyMouse()
m.click(139, 68)
s1 = "pediy2016cool15"
global s2
s2=""
k.type_string(s1+s2)
k.tap_key(k.enter_key)
# while 1: # 确定你的鼠标位置
# print(m.position())
# time.sleep(1)
def f(len, stp=1): # 浅浅做一波回溯
global s1, s2
for i in range(0, 10): # 每一位从0到9直接硬爆
s2 += str(i)
if stp+1<len:
f(len, stp+1)
else:
k.type_string(s1+s2)
k.tap_key(k.enter_key)
time.sleep(0.02)
for j in range(20):
k.tap_key(k.backspace_key)
# print(s1 + s2+"\n")
s2=s2[:-1]
for i in range(5, 6): # 假装不知道答案有几位,想爆几位手动调
f(i)
flag{pediy2016cool1531223}