前言

最近看n1ctf题解的时候看到了一个IDA插件,用来符号执行的,简单的理解就是一个更简单的angr,比angr使用要方便一点,叫做ponce。

https://github.com/illera88/Ponce

安装

直接把Ponce编译好的版本全部粘贴到IDA的插件文件夹中即可。

使用

Ponce是一个符号执行的工具,按照我的理解,符号执行就是将输入看作一个变量(符号),然后通过动态执行能够将这个变量所满足的所有约束条件找到,这些约束条件可能是某些比较复杂的加密比较,但总之肯定有某种约束条件,将所有的约束条件收集好后工具就可以自动算出这个变量应该是什么。

因此我们需要首先找到两个东西或者三个东西,一个是输入存放的位置,一个是最终正确错误的分支点。我们先将输入转变为符号,执行后到达正确错误的分支点时也就已经收集好了所有的约束条件,然后得出结果即可。

这里以一个例子来讲解一下Ponce的简单使用。

测试文件的源代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int main(){
char input[50];
char enc[]={0x56,0x5c,0x51,0x57,0x4b,0x47,0x58,0x51,0x44,0x6f,0x59,0x43,0x6f,0x49,0x5f,0x45,0x42,0x6f,0x56,0x5c,0x51,0x57,0xf,0x4d};
scanf("%s",input);
int len=strlen(input);
if(len!=24){
printf("no!");
return 0;
}
for(int i=0;i<len;i++){
input[i]^=0x30;
if(input[i]!=enc[i]){
printf("no!");
return 0;
}
}
printf("yes!");
return 0;
}// flag{what_is_your_flag?}

首先在IDA里找到scanf的位置

image-20231030191651688

在这里scanf后下个断点,其中[rbp+Str]就是存放输入的位置

然后找到判断的位置

image-20231030191824400

在jz指令这里下个断点

然后开始动态调试,我们输入随便的字符串“123456789012345678901234”

然后程序断在了输入的位置

image-20231030191956323

点击[rbp+Str]找到输入

image-20231030192113981

选择符号化内存 Symbolize memory

image-20231030192217066

如图即为已经符号化成功

然后按下F9程序运行,断在jz指令

这个指令就是判断某个字符是否正确的指令

点击这个指令,右键选择SMT solver -> Negate and Inject to reach 0x……

image-20231030192531092

这个选项的意思时否定当前分支,并注入代码使得程序走入0x4016a6这个分支,同时在下方的输出框里工具也会将此时已经确定的 符号内存 告诉我们

image-20231030193236558

此时再按下F8,程序会走入正确的分支,这也就是注入代码然后走入正确分支,不需要手动修改EIP了。

接着继续按下F9,程序会循环,重新到这个jz指令,重复刚才的操作即可,如此即可得到所有的答案。

image-20231030194125681

但是有时候会有bug发生,重新来一遍就好了。

在选择 Negate and Inject to reach 0x……时也可以选择Solve Fomula 但是此时需要自己手动修改EIP进入正确的分支。

录制视频链接

https://www.bilibili.com/video/BV13N411s7T2/?spm_id_from=333.337.search-card.all.click&vd_source=a85daf8eb54f32264d9f6976d087fe98