实验一:使用符号执行工具klee对软件进行破解(来自于klee官网)
原文地址:https://gitlab.com/Manouchehri/Matryoshka-Stage-2/blob/master/stage2.md
实验用代码下载地址:https://gitlab.com/Manouchehri/Matryoshka-Stage-2/blob/master/stage2.md
下面是我自己的实验进程:
2017-05-09 16:18:09
1. linux 64位提权并执行stage2.bin文件

可以看到,输入错误的密码时,会提示Try again...
2. 对stage2.bin文件利用IDA拍摄6.6版本定位出main函数
程序入口点start看到:

查询一下__libc_start_main函数的功能:
int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));
第一个函数参数是main,也就是二进制程序中的mov rdi, offset sub_4006F2。
查看sub_4006F2的函数调用流程图,可以确定这个是main函数。因为调用流程里为false的时候,会输出Try again;没有输入的时候,输出pass等等。图比较大,这里不粘贴了。
3.反汇编出C代码。
可以按F5,使用Hex-Rays Decompiler反汇编出main函数的代码。也可以点击IDA的File>produce file>create C File。我们使用后者,生成的C文件的内容如下:、
/* This file has been generated by the Hex-Rays decompiler.
Copyright (c) 2007-2014 Hex-Rays <info@hex-rays.com> Detected compiler: GNU C++
*/ #include <defs.h> //-------------------------------------------------------------------------
// Function declarations int init_proc();
// ssize_t write(int fd, const void *buf, size_t n);
// size_t strlen(const char *s);
// int fprintf(FILE *stream, const char *format, ...);
// int __gmon_start__(void); weak
// size_t fwrite(const void *ptr, size_t size, size_t n, FILE *s);
signed int sub_400590();
int sub_4005C0();
signed int sub_400600();
int sub_400620();
__int64 __fastcall sub_40064D(const char *a1);
int __fastcall sub_4006F2(int a1, __int64 a2);
void term_proc(); //-------------------------------------------------------------------------
// Data declarations char byte_400A60[] = { '' }; // weak
FILE *stdout; // idb
char byte_608068; // weak
// extern _UNKNOWN _gmon_start__; weak //----- (00000000004004C8) ----------------------------------------------------
int init_proc()
{
_UNKNOWN *v0; // rax@1 v0 = &_gmon_start__;
if ( &_gmon_start__ )
LODWORD(v0) = __gmon_start__();
return (signed int)v0;
}
// 400540: using guessed type int __gmon_start__(void); //----- (0000000000400560) ----------------------------------------------------
#error "400566: positive sp value has been found (funcsize=3)" //----- (0000000000400590) ----------------------------------------------------
signed int sub_400590()
{
return ;
} //----- (00000000004005C0) ----------------------------------------------------
int sub_4005C0()
{
return ;
} //----- (0000000000400600) ----------------------------------------------------
signed int sub_400600()
{
signed int result; // eax@2 if ( !byte_608068 )
{
result = sub_400590();
byte_608068 = ;
}
return result;
}
// 608068: using guessed type char byte_608068; //----- (0000000000400620) ----------------------------------------------------
int sub_400620()
{
return sub_4005C0();
}
// 400620: could not find valid save-restore pair for rbp //----- (000000000040064D) ----------------------------------------------------
__int64 __fastcall sub_40064D(const char *a1)
{
char buf; // [sp+13h] [bp-Dh]@2
int v3; // [sp+14h] [bp-Ch]@1
int v4; // [sp+18h] [bp-8h]@1
int v5; // [sp+1Ch] [bp-4h]@1 fwrite("Good good!\n", 1uLL, 0xBuLL, stdout);
v3 = ;
v4 = ;
v5 = strlen(a1);
while ( v3 <= )
{
buf = byte_400A60[(signed __int64)v3] ^ a1[v4 % v5];
write(, &buf, 1uLL);
++v3;
++v4;
}
return 0LL;
} //----- (00000000004006F2) ----------------------------------------------------
int __fastcall sub_4006F2(int a1, __int64 a2)
{
int result; // eax@2
__int64 v3; // rbx@10
signed int v4; // [sp+1Ch] [bp-14h]@4 if ( a1 == )
{
if ( * (strlen(*(const char **)(a2 + )) + ) != )
goto LABEL_31;
v4 = ;
if ( **(_BYTE **)(a2 + ) != )
v4 = ;
if ( * *(_BYTE *)(*(_QWORD *)(a2 + ) + 3LL) != )
v4 = ;
if ( **(_BYTE **)(a2 + ) + != *(_BYTE *)(*(_QWORD *)(a2 + ) + 6LL) - )
v4 = ;
v3 = *(_BYTE *)(*(_QWORD *)(a2 + ) + 5LL);
if ( v3 != * strlen(*(const char **)(a2 + )) - )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 7LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 10LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) - != **(_BYTE **)(a2 + ) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 3LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 9LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 4LL) != )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 2LL) - *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 8LL) - *(_BYTE *)(*(_QWORD *)(a2 + ) + 7LL) != )
v4 = ;
if ( v4 )
result = sub_40064D(*(const char **)(a2 + ));
else
LABEL_31:
result = fprintf(stdout, "Try again...\n", a2);
}
else
{
result = fprintf(stdout, "Usage: %s <pass>\n", *(_QWORD *)a2, a2);
}
return result;
} //----- (0000000000400A34) ----------------------------------------------------
void term_proc()
{
;
} #error "There were 1 decompilation failure(s) on 9 function(s)"
4. 既然是破解,我们只需要在通过验证的地方,做一些改动。所以,围绕main函数(这里是sub_4006F2函数)我们去除所有不想干的代码,提取出主要流程,比如,sub_40064D实现的功能是在验证通过的时候,输出密码验证通过的信息,那么我就直接用printf("good!")进行代替,以简化破解难度。改动后的代码是:
#include <defs.h>
int __fastcall sub_4006F2(int a1, __int64 a2)
{
int result; // eax@2
__int64 v3; // rbx@10
signed int v4; // [sp+1Ch] [bp-14h]@4 if ( a1 == )
{
if ( * (strlen(*(const char **)(a2 + )) + ) != )
goto LABEL_31;
v4 = ;
if ( **(_BYTE **)(a2 + ) != )
v4 = ;
if ( * *(_BYTE *)(*(_QWORD *)(a2 + ) + 3LL) != )
v4 = ;
if ( **(_BYTE **)(a2 + ) + != *(_BYTE *)(*(_QWORD *)(a2 + ) + 6LL) - )
v4 = ;
v3 = *(_BYTE *)(*(_QWORD *)(a2 + ) + 5LL);
if ( v3 != * strlen(*(const char **)(a2 + )) - )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 7LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 10LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) - != **(_BYTE **)(a2 + ) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 3LL) != *(_BYTE *)(*(_QWORD *)(a2 + ) + 9LL) )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 4LL) != )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 2LL) - *(_BYTE *)(*(_QWORD *)(a2 + ) + 1LL) != )
v4 = ;
if ( *(_BYTE *)(*(_QWORD *)(a2 + ) + 8LL) - *(_BYTE *)(*(_QWORD *)(a2 + ) + 7LL) != )
v4 = ;
if ( v4 )
printf("good\n");
else
LABEL_31:
result = fprintf(stdout, "Try again...\n", a2);
}
else
{
result = fprintf(stdout, "Usage: %s <pass>\n", *(_QWORD *)a2, a2);
}
return result;
}
5. 当然,这离可执行还差很远。我们还要改动为KLEE能够分析的形式。但是在满足klee能够分析之前,我们要先将其改动为可以编译执行的C文件。
我们一边用经验去改,一边用编译器同时编译,根据编译不通过所提示的错误进行改动。
主要改动如下:
- 第一处
将defs.h文件从IDA的目录中复制到test.c(改动代码放置位置)的相同目录中去。Defs.h文件中保存的是IDA生成的C文件中相关类型的定义。
- 第二处:
../src/test.c:35:5: warning: second argument of ‘main’ should be ‘char **’ [-Wmain]
int main(int a1, __int64 a2)
将__int64改为char**(这是经验)
- 第三处:
../src/test.c:71:7: warning: implicit declaration of function ‘sub_40064D’ [-Wimplicit-function-declaration]
result = sub_40064D(*(const char **)(a2 + 8));
^
../src/test.c:74:7: warning: too many arguments for format [-Wformat-extra-args]
result = fprintf(stdout, "Try again...\n", a2);
^
../src/test.c:78:5: warning: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘uint64’ [-Wformat=]
result = fprintf(stdout, "Usage: %s <pass>\n", *(_QWORD *)a2, a2);
所以将fprintf改为printf。并且去掉return result。
- 第四处
代码中有大量的类型转换,我们在IDA对sub_4006F2按F5弹出的反汇编后C代码的窗口中,右键选择Hiden casts.

可以看到,之前的QWORD *和BYTE *等IDA中的类型,都去除了。所以,再次改动后的代码是:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "defs.h"
int main(int a1, char ** a2)
{ __int64 v3; // rbx@10
signed int v4; // [sp+1Ch] [bp-14h]@4 if ( a1 == )
{
if ( * (strlen(*(a2 + )) + ) != )
goto LABEL_31;
v4 = ;
if ( **(a2 + ) != )
v4 = ;
if ( * *(*(a2 + ) + 3LL) != )
v4 = ;
if ( **(a2 + ) + != *(*(a2 + ) + 6LL) - )
v4 = ;
v3 = *(*(a2 + ) + 5LL);
if ( v3 != * strlen(*(a2 + )) - )
v4 = ;
if ( *(*(a2 + ) + 1LL) != *(*(a2 + ) + 7LL) )
v4 = ;
if ( *(*(a2 + ) + 1LL) != *(*(a2 + ) + 10LL) )
v4 = ;
if ( *(*(a2 + ) + 1LL) - != **(a2 + ) )
v4 = ;
if ( *(*(a2 + ) + 3LL) != *(*(a2 + ) + 9LL) )
v4 = ;
if ( *(*(a2 + ) + 4LL) != )
v4 = ;
if ( *(*(a2 + ) + 2LL) - *(*(a2 + ) + 1LL) != )
v4 = ;
if ( *(*(a2 + ) + 8LL) - *(*(a2 + ) + 7LL) != )
v4 = ;
if ( v4 )
printf("good\n");
else
LABEL_31:
printf("Try again...\n");
}
else
{
printf("Usage: %s <pass>\n", *a2);
}
return ;
}
6. 使用KLEE进行分析
改动
if ( v4 )
printf("good\n");
为:
if ( v4 ){
printf("good\n");
klee_assert();
}
并且添加两个头文件:
#include <assert.h>
#include <klee/klee.h>
下一步使用KLEE进行符号执行:KLEE官网见(http://klee.github.io/)
需要大家自行准备一下KLEE的知识。这里不再赘述。
执行下面的脚本。test.c为可以编译通过的c文件。test-klee.c为添加klee_assert等内容以后的文件
clang -I /home/klee/xiaojiework/klee/include/ -emit-llvm -c -g test-klee.c
klee --optimize --libc=uclibc --posix-runtime test-klee.bc --sym-arg
结果是:

出现了问题,没有像官网那样,报assertion的错误,而且探寻的路径只有一条。究竟是怎么回事???
7. 问题所在。
经过一系列研究,才发现问题的所在。
首先,int a1,char **a2是main函数中用于接收命令行信息的参数。a1是命令行输入的字符串的个数。a2[0]就是程序名称。a2[1]是程序后的第一个参数。
比如,你输入stage2.bin “123”,那么传入test.c中main函数的,就是a1为2,a2为char**类型,包含两个字符串,分别是“stage2.bin”和“123”。
所以,a2[1]存储的是密码字符串。但是我们反汇编出的程序反复处理的是*(a2+8)位置的字符串。这也是klee发现out of bound pointer的原因。
8.问题是如何导致的?又如何解决?
首先,明确一个问题。
如果a2是char **类型,那么*(a2+1)和a2[1]是一样的。因为char **是char *的指针,a2+1会自动解释为真正a2的地址值加上8(也就是8个字节)。
如果a2是__int64类型,那么*(a2+8)和a2[1]的值是相等的。当然,要对a2+8先cast为char **类型以后,才能用*取值。
这是因为C编译器对a2+n计算的处理是这样做的。
如果a2是int类型,那么就是简单+n。
如果a2是指针类型,则加上a2指向的对象大小*n。比如,a2是char**类型的时候,a2+1,就是a2+1*(sizeof(char*))。由于64位程序下指针类型都是64位,即8个字节。所以a2+1的真正计算值是a2值+8。
C语言的cast特别多。对cast机制,可以自行补课。这里不再赘述。
我的错误是:
一开始反汇编出的代码,将a2作为__int64类型处理,所以代码中用
( *(_BYTE *)(*(_QWORD *)(a2 + 8) + 1LL)
表示a2[1][1]。也就是将__int64类型的地址a2,将其作为char**类型的时候,计算a2[1][1]的正确过程。
我在后续将反汇编出的C代码立求编译通过的时候,简单的在main函数括号内,将a2的类型改正为cahr**了,但是后面计算a2[1][1]的方式还没有变。
这就是错误的原因。
正确的做法:在IDA F5反汇编出的代码窗口,右键main函数括号内的a2,选择set lvar type为char **a2。可以看到main函数的代码为:
int __fastcall sub_4006F2(int a1, char **a2)
{
int result; // eax@2
__int64 v3; // rbx@10
signed int v4; // [sp+1Ch] [bp-14h]@4 if ( a1 == )
{
if ( * (strlen(a2[]) + ) != )
goto LABEL_31;
v4 = ;
if ( *a2[] != )
v4 = ;
if ( * a2[][] != )
v4 = ;
if ( *a2[] + != a2[][] - )
v4 = ;
v3 = a2[][];
if ( v3 != * strlen(a2[]) - )
v4 = ;
if ( a2[][] != a2[][] )
v4 = ;
if ( a2[][] != a2[][] )
v4 = ;
if ( a2[][] - != *a2[] )
v4 = ;
if ( a2[][] != a2[][] )
v4 = ;
if ( a2[][] != )
v4 = ;
if ( a2[][] - a2[][] != )
v4 = ;
if ( a2[][] - a2[][] != )
v4 = ;
if ( v4 )
result = sub_40064D(a2[]);
else
LABEL_31:
result = fprintf(stdout, "Try again...\n", a2);
}
else
{
result = fprintf(stdout, "Usage: %s <pass>\n", *a2, a2);
}
return result;
}
可以看到,这才是正确的代码。之前对a2的类型改动以后,没有在代码中对计算a2[1]的方式进行改动。还是按照a2为__int64类型时的计算方式。
9. 计算出软件密钥。
执行前述的脚本:
klee@ubuntu:~/kleeexperiment/Keygenningwithklee/stage2$ sh ./xiaojie.sh
KLEE: NOTE: Using klee-uclibc : /usr/local/lib/x86_64-linux-gnu/klee/runtime/klee-uclibc.bca
KLEE: NOTE: Using model: /usr/local/lib/x86_64-linux-gnu/klee/runtime/libkleeRuntimePOSIX.bca
KLEE: output directory is "/home/klee/kleeexperiment/Keygenningwithklee/stage2/klee-out-0"
KLEE: Using STP solver backend
KLEE: WARNING: undefined reference to function: puts
KLEE: WARNING ONCE: calling external: syscall(, , , )
KLEE: WARNING ONCE: Alignment of memory from call "malloc" is not modelled. Using alignment of .
KLEE: WARNING ONCE: calling external: puts()
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Good good!
Try again...
KLEE: ERROR: /home/klee/kleeexperiment/Keygenningwithklee/stage2/main.c:: ASSERTION FAIL:
KLEE: NOTE: now ignoring this error at this location
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again...
Try again... KLEE: done: total instructions =
KLEE: done: completed paths =
KLEE: done: generated tests =
klee@ubuntu:~/kleeexperiment/Keygenningwithklee/stage2$
生成的文件中,test000027.assert.err是klee_assert报错的位置。其实也就是密码验证通过的位置。查看第27个测试用例的详细内容:

可以看到Pandi_panda为密钥。
我们验证一下,可以看到通过验证了。

实验一:使用符号执行工具klee对软件进行破解(来自于klee官网)的更多相关文章
- 工具-绿色使用软件等-破解pycharm,idea等Jet brain出品软件(99.2.1)
1.下载此文件链接:https://pan.baidu.com/s/12nbtgeWiD1xKMtPIr-S1-g密码:b66f 并将 JetbrainsCrack-3.1-release-enc.j ...
- openstack(liberty):部署实验平台(二,简单版本软件安装 part2)
继续前面的part1,将后续的compute以及network部分的安装过程记录完毕! 首先说说compute部分nova的安装. n1.准备工作.创建数据库,配置权限!(密码依旧是openstack ...
- openstack(liberty):部署实验平台(二,简单版本软件安装 part1)
软件安装过程中,考虑到现在是一个实验环境,且也考虑到规模不大,还有,网络压力不会大,出于简单考虑,将各个节点的拓扑结构改了一下,主要体现在网络节点和控制节点并在了一起.在一个服务器上安装! 到目前位置 ...
- 【工利其器】必会工具之(二)Android开发者官网篇
前言 当刚开始踏入Android程序员这个行业的时候,想必绝大多数的人都和笔者一样,热血沸腾,激情四射,买了很多讲解Android开发的书籍.当开发某个功能需要学习某方面知识的时候,大家又成了“面向百 ...
- Hash值破解工具(findmyhash与hash-identifier破解Hash值)
Hash值破解工具(findmyhash与hash-identifier破解Hash值) 前言: Kali Linux提供各种哈希密文破解工具,如hashcat.john.rainbows.不论哪一种 ...
- go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE
go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE Go语言是谷歌2009发布的专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速 ...
- 怎样取消老毛桃软件赞助商---只需在输入框中输入老毛桃官网网址“laomaotao.org”
来源:www.laomaotao.org 时间:2015-01-29 在众多网友和赞助商的支持下,迄今为止,老毛桃u盘启动盘制作工具已经推出了多个版本.如果有用户希望取消显示老毛桃软件中的赞助商,那不 ...
- Protrator 官网和下载工具稍慢 , 但能使用. Angular CLI 内置 官方推荐 TS的 demo 不明显 , 而且依赖无法安装
npm uninstall -g protractor && cnpm install -g protractor && protractor --version 复 ...
- 如何让你的手机U盘集PE工具、系统安装、无线破解等众多功能于一身
不久前,手里的U盘坏了,于是乎,又在网上淘了一个Type-C U盘,刚好手机电脑都可以用. 那么现在U有了,我们要做什么呢? 第一:让U盘插在手机上时,可以供手机读写,实现手机存储扩容,随插随用,简单 ...
随机推荐
- 【后渗透】PowerSploit
1.下载安装:git clone https://github.com/mattifestation/PowerSploit.git 2.搭建web服务器(如172.16.12.2),将powersp ...
- CAN2.0A帧格式 与 LIN帧格式 简单说明
一.标准的2.0A帧格式 各字段解释:SOF帧开始标志比特是一个显性比特(0),由一个或多个准备发送帧的节点传输.SOF标志着帧的开始(或仲裁发送帧的权利),并用于“硬同步”总线上的设备.只有在开始发 ...
- Apache-jmeter3.3安装
一.首先检查机子上是否有安装jdk 检查方式,在cmd中输入java,出现如下信息,即已经安装好jdk 若未安装jdk,则看如下步骤 步骤一: 1.下载jdk,到官网下载jdk,地址:http://w ...
- selenium+Python(浏览器操作)
1 .浏览器最大化我们知道调用启动的浏览器不是全屏的,这样不会影响脚本的执行,但是有时候会影响我们“观看”脚本的执行. #coding=utf-8 from selenium import webdr ...
- unity对敏感词库处理的记录
string txt = Resources.Load<TextAsset>("minganci").ToString(); //string txt = " ...
- Xcode 常用路径收集
Xcode证书 路径: ~/Library/MobileDevice/Provisioning Profiles Xcode 插件路径: ~/Library/Application Support/D ...
- Codeforces 543E. Listening to Music
Description 题面 Solution 分块套分块,分别对时间和位置进行分块 差不多是一个定期保存信息的方法 对于询问我们不妨求出 \(>=x\) 的答案,然后用 \(m-(>=x ...
- 架构实战项目心得(八):dubbo知识的整理
1 Dubbo启动时检查.集群容错.负载均衡.线程模型的设置以及选项: 启动时检查:(1)Dubbo默认会在启动的时候检查依赖的服务是否可用,不可用时会抛出异常.阻止Spring初始化完成.以便 ...
- jQuery比较时间处理过程(日期转换为时间戳比较)
不管在JS中还是PHP中,终会遇到时间比较,但是很尴尬的是,举个例子,2017-04-12和2017-5-21是不一样或者说不能比较的,这在PHP和JS都是这种情况,这种情况怎么办呢,换成统一的格式比 ...
- MySql 模糊连接
我们有时候会遇到比较扯的数据库设计,也可能处于某种原因,或当时特殊考虑,情况类似如下: 表A,主键Id: 表B,关联字段 = 表A的Id的逗号连接,如:1009,2393,1235 B表的一行,对应A ...