我们可以在 SMT 语言中定义递归函数(例如 with define-fun-rec),但是一些流行的求解器(例如z3)目前还不能处理它们(我不知道有什么可以支持);所以在这样的求解器中编码循环不是直接的。
但是我们可以使用一个技巧,即通过自动生成 SMT 公式来展开循环(然后它仍然必须测试几个长度的密码)。例如,以下程序为每个密码长度生成一个 SMT 公式:
#include <stdlib.h>
#include <stdio.h>
#define PRE_VAL_NUM 0xfff
int ran_vals[PRE_VAL_NUM];
void gen_pre_vals()
{
for (unsigned int i = 0; i < PRE_VAL_NUM; ++i) {
ran_vals[i] = random();
}
return;
}
int main(int argc, char* argv[])
{
if (argc != 2) {
printf("please run as keygen length_of_password\n");
return 0;
}
gen_pre_vals();
FILE* smt_file = fopen("reverseme.smt2", "w+");
fprintf(smt_file, "(set-logic QF_BV)\n");
fprintf(smt_file, "(set-info :smt-lib-version 2.0)\n");
int passwd_len = strtol(argv[1], NULL, 0);
fprintf(smt_file, "\n");
for (int i = 0; i < passwd_len; ++i) {
fprintf(smt_file, "(declare-fun pw%d () (_ BitVec 8))\n", i);
}
fprintf(smt_file, "\n");
fprintf(smt_file, "(define-fun prev_i ((i (_ BitVec 32)) (pw_i (_ BitVec 8))) (_ BitVec 32)\n");
fprintf(smt_file, "(let ((pw_i_ext ((_ sign_extend 24) pw_i)))\n");
fprintf(smt_file, "(bvadd (bvmul i pw_i_ext) #x00031337)))\n");
fprintf(smt_file, "\n");
for (int i = 0; i < passwd_len; ++i) {
fprintf(smt_file, "(assert (and (bvuge pw%d #x21) (bvule pw%d #x7e)))\n", i, i);
}
fprintf(smt_file, "\n");
fprintf(smt_file, "(assert\n");
fprintf(smt_file, "(let (\n");
unsigned int acc_ran;
for (int i = 0; i < passwd_len; ++i) {
acc_ran = 0x00;
for (int j = ran_vals[0] & 0xff; j > 0; j--) {
acc_ran ^= ran_vals[1 + i + (j - 1) * passwd_len];
}
fprintf(smt_file, "(pwn%d (bvxor pw%d #x%x))\n", i, i, (acc_ran & 0xff));
}
fprintf(smt_file, ")\n");
fprintf(smt_file, "(let ((i%d (prev_i #x1337 pwn%d)))\n", passwd_len - 2, passwd_len - 1);
for (int i = passwd_len - 2; i >= 1; --i) {
fprintf(smt_file, "(let ((i%d (prev_i i%d pwn%d)))\n", i - 1, i, i);
}
fprintf(smt_file, "(let ((i (prev_i i0 pwn0)))\n");
fprintf(smt_file, "(= i #xfd0970e7))\n");
for (int i = 0; i < passwd_len; ++i) fprintf(smt_file, ")");
fprintf(smt_file, ")\n");
fprintf(smt_file, "\n");
fprintf(smt_file, "(check-sat)\n");
fprintf(smt_file, "(get-value (\n");
for (int i = 0; i < passwd_len; ++i) fprintf(smt_file, "pw%d\n", i);
fprintf(smt_file, "))\n");
fclose(smt_file);
printf("output smt file: reverseme.smt2\n");
return 1;
}
它会产生一个SMT文件,命名reverseme.smt2为密码的每个长度(如长度生成的SMT文件6是在这里),那么我们可以输入:z3 reverseme.smt2获得有效的密码。
我已经测试了2, 3, 4, 5(长度1显然是不可能的)的长度。在我的机器上,z3每次测试大约需要 1-5 秒,并为每个测试提供UNSAT;第一个SAT结果“6`SHQe”(ASCII 代码:0x36, 0x60, 0x53, 0x48, 0x51, 0x65找到长度为6。我不检查是否存在一些长度大于6虽然的有效密码。