【reverse】虚假控制流入门:Ubuntu20.04安装ollvm4.0踩坑记+用IDApython去除BCF

本文主要是介绍【reverse】虚假控制流入门:Ubuntu20.04安装ollvm4.0踩坑记+用IDApython去除BCF,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • 引言
      • 依赖
      • Ubuntu20.04安装ollvm+各种踩坑记录
        • 1、gcc和g++需要降级
        • 2、编译前要先修改源码
        • 3、注意权限问题
        • 4、给足虚拟机内存
        • 编译成功
      • 先看一个demo
      • 尝试用IDApython去除bcf
      • 参考资料

引言

虚假控制流(Bogus Control Flow,BCF),通过加入包含不透明谓词的条件跳转(也就是跳转与否在运行之前就已经确定的跳转,但IDA无法分析)和不可达的基本块,来干扰IDA的控制流分析和F5反汇编。

依赖

  • IDA7.7
  • 虚拟机Ubuntu20.04

Ubuntu20.04安装ollvm+各种踩坑记录

根据参考链接1,主要的命令就这些:

# 截至2022.09.25,这玩意已经5年没更新了……
git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
# 这里build文件夹和obfuscator-llvm-4.0文件夹同级
mkdir build-llvm-4.0 && sudo chmod 777 -R build-llvm-4.0 && cd build-llvm-4.0
cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator-llvm-4.0/
# 防止出现Permission Denied
sudo make -j5

但你先别急,这里水很深,不看完参考链接1以及我总结的踩坑记录的话,泥巴握不住!

作者:hans774882968以及hans774882968以及hans774882968

本文52pojie:https://www.52pojie.cn/thread-1692596-1-1.html

本文juejin:https://juejin.cn/post/7147302252846252046/

本文csdn:https://blog.csdn.net/hans774882968/article/details/127043163

1、gcc和g++需要降级

如果用的是9及以后的版本,make时会没有任何提示,忽然报错make: *** [Makefile:152:all] 错误 2。直接执行下面这些命令进行降级就行:

sudo apt install gcc-8 g++-8 -ysudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 8
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 9sudo update-alternatives --config gcc
sudo update-alternatives --config g++# 最后可以看看版本是否修改成功
gcc -v
g++ -v

效果

在这里插入图片描述

2、编译前要先修改源码

根据参考链接1,不修改源码会踩坑。找到<你的ollvm目录>/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetClient.h,按照下图把690行的readMem的返回类型从Expected<vector<char>>改为Expected<vector<uint8_t>>(这里参考链接1错误地说成了uint_8了)。

在这里插入图片描述

3、注意权限问题

如果你在编译时,看到make失败前有一大堆Permission Denied,说明你权限没给够。

  1. 建议编译前给到build-llvm-4.0obfuscator-llvm-4.0的父文件夹777权限:sudo chmod 777 -R <父文件夹名>,防止新生成的文件Permission Denied
  2. 不要在挂载点的文件夹进行编译,否则会有权限错误。
  3. 建议编译期间每次看到build-llvm-4.0/bin新生成一个文件,都给它777权限,防止Permission Denied造成失败(其实编译失败也没事,编译好的文件不会重新编译,不是很耽误时间。每次看到新生成的文件出现Permission Denied,先给它权限再重新编译即可)。
4、给足虚拟机内存

不给足内存的话虚拟机会死机。也可以选择降一降作业数,比如sudo make -j7降到sudo make -j5

编译成功

大概等了一小时,终于成功了!纪念一下!

在这里插入图片描述

先看一个demo

写个bcf_demo.cpp

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)void dbg() {puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...);
}
template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...);
}int main (int argc, char const *argv[]) {char name[100];scanf ("%s", name);if (strcmp (name, "Alice") == 0) {printf ("hello, %s.\n", name) ;} else if (strcmp (name, "Bob") == 0) {printf ("hello, %s\n", name);} else {printf ("no permission.\n");}return 0;
}

用clang正常编译

'build-llvm-4.0/bin/clang++ 的绝对路径' 'bcf_demo.cpp 的绝对路径' -o bcf_demo_normal

IDA反汇编效果:

int __cdecl main(int argc, const char **argv, const char **envp)
{char s1[112]; // [rsp+10h] [rbp-80h] BYREFconst char **v5; // [rsp+80h] [rbp-10h]int v6; // [rsp+88h] [rbp-8h]int v7; // [rsp+8Ch] [rbp-4h]v7 = 0;v6 = argc;v5 = argv;scanf("%s", s1);if ( !strcmp(s1, (const char *)(unsigned int)"Alice") ){printf("hello, %s.\n", s1);}else if ( !strcmp(s1, (const char *)(unsigned int)"Bob") ){printf("hello, %s\n", s1);}else{printf("no permission.\n");}return 0;
}

流程图:

在这里插入图片描述

加上bcf,编译:

'build-llvm-4.0/bin/clang++ 的绝对路径' -mllvm -bcf 'bcf_demo.cpp 的绝对路径' -o bcf_demo

IDA反汇编效果:

int __cdecl main(int argc, const char **argv, const char **envp)
{char s1[112]; // [rsp+20h] [rbp-80h] BYREFconst char **v5; // [rsp+90h] [rbp-10h]int v6; // [rsp+98h] [rbp-8h]int v7; // [rsp+9Ch] [rbp-4h]v7 = 0;v6 = argc;v5 = argv;scanf("%s", s1);if ( !strcmp(s1, (const char *)(unsigned int)"Alice") ){if ( y_12 >= 10 && ((((_BYTE)x_11 - 1) * (_BYTE)x_11) & 1) != 0 )goto LABEL_9;while ( 1 ){printf("hello, %s.\n", s1);if ( y_12 < 10 || ((((_BYTE)x_11 - 1) * (_BYTE)x_11) & 1) == 0 )break;
LABEL_9:printf("hello, %s.\n", s1);}}else if ( !strcmp(s1, (const char *)(unsigned int)"Bob") ){printf("hello, %s\n", s1);}else{printf("no permission.\n");}return 0;
}

流程图:

在这里插入图片描述

对比两图,bcf确实让程序更复杂了。

这些跳转中的x_11y_12位于**.bss段**,并且通过交叉引用发现没有被修改过,也就是说x_11y_12在运行过程中一直为0。这里的x_11y_12被称为不透明谓词,所谓不透明,就是IDA难以推断其在运行时的值,但我们都知道它就是0。

简单分析一下bcf加入的干扰语句。y_12 >= 10 && ((((_BYTE)x_11 - 1) * (_BYTE)x_11) & 1) != 0:因为相邻两个数的乘积必为偶数,故此式总是false。根据德摩根定律,y_12 < 10 || ((((_BYTE)x_11 - 1) * (_BYTE)x_11) & 1) == 0就总是true。因此printf("hello, %s.\n", s1);恰好只执行一次。那些永远不会执行到的代码块,就叫做不可达的基本块。这些跳转和不可达基本块并不会影响程序原有的逻辑,但会干扰我们的分析,这就是虚假控制流混淆达到的效果。

尝试用IDApython去除bcf

我们把上面的demo写得更复杂一点:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)void dbg() {puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {cout << f << " ";dbg (r...);
}
template<typename Type>inline void read (Type &xx) {Type f = 1;char ch;xx = 0;for (ch = getchar(); ch < '0' || ch > '9'; ch = getchar() ) if (ch == '-') f = -1;for (; ch >= '0' && ch <= '9'; ch = getchar() ) xx = xx * 10 + ch - '0';xx *= f;
}
void read() {}
template<typename T, typename ...R>void read (T &x, R &...r) {read (x);read (r...);
}int main (int argc, char const *argv[]) {char name[100];scanf ("%s", name);if (strcmp (name, "Alice") == 0) {printf ("hello, %s.\n", name) ;} else if (strcmp (name, "Bob") == 0) {printf ("hello, %s\n", name);} else {printf ("no permission.\n") ;return 0;}re_ (i, 0, 10) {if (i & 1) dbg (i << 1);else dbg (i << 1 | 1);}return 0;
}

加上bcf编译,用IDA看看patch前的效果:

int __cdecl main(int argc, const char **argv, const char **envp)
{int v4; // [rsp+24h] [rbp-8Ch] BYREFint v5; // [rsp+28h] [rbp-88h] BYREFint i; // [rsp+2Ch] [rbp-84h]char s1[112]; // [rsp+30h] [rbp-80h] BYREFconst char **v8; // [rsp+A0h] [rbp-10h]int v9; // [rsp+A8h] [rbp-8h]int v10; // [rsp+ACh] [rbp-4h]v10 = 0;v9 = argc;v8 = argv;scanf("%s", s1);if ( !strcmp(s1, (const char *)(unsigned int)"Alice") ){printf("hello, %s.\n", s1);}else{if ( strcmp(s1, (const char *)(unsigned int)"Bob") ){if ( y_13 >= 10 && ((((_BYTE)x_12 - 1) * (_BYTE)x_12) & 1) != 0 )goto LABEL_18;while ( 1 ){printf("no permission.\n");v10 = 0;if ( y_13 < 10 || ((((_BYTE)x_12 - 1) * (_BYTE)x_12) & 1) == 0 )return v10;
LABEL_18:printf("no permission.\n");v10 = 0;}}printf("hello, %s\n", s1);}for ( i = 0; ; ++i ){while ( y_13 >= 10 && ((((_BYTE)x_12 - 1) * (_BYTE)x_12) & 1) != 0 );if ( i >= 10 )break;if ( (i & 1) != 0 ){v5 = 2 * i;dbg<int>(&v5);continue;}if ( y_13 >= 10 && ((((_BYTE)x_12 - 1) * (_BYTE)x_12) & 1) != 0 ){
LABEL_20:v4 = (2 * i) | 1;dbg<int>(&v4);}v4 = (2 * i) | 1;dbg<int>(&v4);if ( y_13 >= 10 && ((((_BYTE)x_12 - 1) * (_BYTE)x_12) & 1) != 0 )goto LABEL_20;}return 0;
}

我们随便找个例子,看看干扰代码的汇编长什么样:

.text:000000000040151C 8B 04 25 B4 41 40 00          mov     eax, ds:x_12
.text:0000000000401523 8B 0C 25 9C 41 40 00          mov     ecx, ds:y_13
.text:000000000040152A 89 C2                         mov     edx, eax
.text:000000000040152C 83 EA 01                      sub     edx, 1
.text:000000000040152F 0F AF C2                      imul    eax, edx
.text:0000000000401532 83 E0 01                      and     eax, 1
.text:0000000000401535 83 F8 00                      cmp     eax, 0
.text:0000000000401538 40 0F 94 C6                   setz    sil
.text:000000000040153C 83 F9 0A                      cmp     ecx, 0Ah
.text:000000000040153F 40 0F 9C C7                   setl    dil
.text:0000000000401543 40 08 FE                      or      sil, dil
.text:0000000000401546 40 F6 C6 01                   test    sil, 1
.text:000000000040154A 0F 85 05 00 00 00             jnz     loc_401555
.text:000000000040154A
.text:0000000000401550 E9 F3 01 00 00                jmp     loc_401748

我们不需要管这些干扰指令具体是true还是false,只需要知道:它们不影响原有代码。我们简单分析可知,这里的jnz loc_401555一定会执行,因此我们只需要把jnz loc_401555改成jmp loc_401555,即可去除所有的干扰效果。

IDApython脚本:

import idcdef next_instr(addr):# item_size返回addr处指令长度return addr + idc.get_item_size(addr)def main():print('-' * 40)st_addr = 0x401470ed_addr = 0x401793addr = st_addrwhile addr < ed_addr:next = next_instr(addr)if 'x_12' in idc.GetDisasm(addr):# 向下找到jnzwhile addr < ed_addr and 'jnz' not in idc.GetDisasm(addr):addr = nextnext = next_instr(addr)if addr >= ed_addr:breakprint(idc.GetDisasm(addr))  # dbg# 获取jnz跳转的目的地址dest = idc.get_operand_value(addr, 0)print('dest', hex(dest))  # dbg# 将jnz patch成jmpidc.patch_byte(addr, 0xE9)# 计算目的地址相对addr的偏移offsetoffset = dest - (addr + 5)# 将jmp操作数patch为offsetidc.patch_dword(addr + 1, offset)# patch jnz指令最后一个字节为nopidc.patch_byte(addr + 5, 0x90)addr = nextprint('-' * 40)main()

输出:

jnz     loc_401555
dest 0x401555
jnz     loc_4015AC
dest 0x4015ac
jnz     loc_4015F9
dest 0x4015f9
jnz     loc_401642
dest 0x401642
jnz     loc_4016C0
dest 0x4016c0
jnz     loc_401717
dest 0x401717

在上述例子中,patch后,只有一条指令被修改了:

.text:000000000040151C 8B 04 25 B4 41 40 00          mov     eax, ds:x_12
.text:0000000000401523 8B 0C 25 9C 41 40 00          mov     ecx, ds:y_13
.text:000000000040152A 89 C2                         mov     edx, eax
.text:000000000040152C 83 EA 01                      sub     edx, 1
.text:000000000040152F 0F AF C2                      imul    eax, edx
.text:0000000000401532 83 E0 01                      and     eax, 1
.text:0000000000401535 83 F8 00                      cmp     eax, 0
.text:0000000000401538 40 0F 94 C6                   setz    sil
.text:000000000040153C 83 F9 0A                      cmp     ecx, 0Ah
.text:000000000040153F 40 0F 9C C7                   setl    dil
.text:0000000000401543 40 08 FE                      or      sil, dil
.text:0000000000401546 40 F6 C6 01                   test    sil, 1
.text:000000000040154A E9 06 00 00 00                jmp     loc_401555
.text:000000000040154A
.text:000000000040154A                               ; ---------------------------------------------------------------------------
.text:000000000040154F 90                            db  90h
.text:0000000000401550 E9 F3 01 00 00                jmp     loc_401748

patch后反汇编效果:

int __cdecl main(int argc, const char **argv, const char **envp)
{int v4; // [rsp+24h] [rbp-8Ch] BYREFint v5; // [rsp+28h] [rbp-88h] BYREFint i; // [rsp+2Ch] [rbp-84h]char s1[112]; // [rsp+30h] [rbp-80h] BYREFconst char **v8; // [rsp+A0h] [rbp-10h]int v9; // [rsp+A8h] [rbp-8h]int v10; // [rsp+ACh] [rbp-4h]v10 = 0;v9 = argc;v8 = argv;scanf("%s", s1);if ( !strcmp(s1, (const char *)(unsigned int)"Alice") ){printf("hello, %s.\n", s1);}else{if ( strcmp(s1, (const char *)(unsigned int)"Bob") ){printf("no permission.\n");return 0;}printf("hello, %s\n", s1);}for ( i = 0; i < 10; ++i ){if ( (i & 1) != 0 ){v5 = 2 * i;dbg<int>(&v5);}else{v4 = (2 * i) | 1;dbg<int>(&v4);}}return 0;
}

效果不错,去得很干净。

地球人用angr去除bcf的做法以后(下辈子)再学。

参考资料

  1. Ubuntu20.04安装ollvm各种踩坑记录:https://www.bilibili.com/read/cv13148974/
  2. 地球人yyds%%%:https://bbs.pediy.com/thread-266005.htm

这篇关于【reverse】虚假控制流入门:Ubuntu20.04安装ollvm4.0踩坑记+用IDApython去除BCF的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/295296

相关文章

虚拟机Centos7安装MySQL数据库实践

《虚拟机Centos7安装MySQL数据库实践》用户分享在虚拟机安装MySQL的全过程及常见问题解决方案,包括处理GPG密钥、修改密码策略、配置远程访问权限及防火墙设置,最终通过关闭防火墙和停止Net... 目录安装mysql数据库下载wget命令下载MySQL安装包安装MySQL安装MySQL服务安装完成

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads

SQL server数据库如何下载和安装

《SQLserver数据库如何下载和安装》本文指导如何下载安装SQLServer2022评估版及SSMS工具,涵盖安装配置、连接字符串设置、C#连接数据库方法和安全注意事项,如混合验证、参数化查... 目录第一步:打开官网下载对应文件第二步:程序安装配置第三部:安装工具SQL Server Manageme

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

Python中win32包的安装及常见用途介绍

《Python中win32包的安装及常见用途介绍》在Windows环境下,PythonWin32模块通常随Python安装包一起安装,:本文主要介绍Python中win32包的安装及常见用途的相关... 目录前言主要组件安装方法常见用途1. 操作Windows注册表2. 操作Windows服务3. 窗口操作

Spring如何使用注解@DependsOn控制Bean加载顺序

《Spring如何使用注解@DependsOn控制Bean加载顺序》:本文主要介绍Spring如何使用注解@DependsOn控制Bean加载顺序,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录1.javascript 前言2. 代码实现总结1. 前言默认情况下,Spring加载Bean的顺

基于Python开发Windows屏幕控制工具

《基于Python开发Windows屏幕控制工具》在数字化办公时代,屏幕管理已成为提升工作效率和保护眼睛健康的重要环节,本文将分享一个基于Python和PySide6开发的Windows屏幕控制工具,... 目录概述功能亮点界面展示实现步骤详解1. 环境准备2. 亮度控制模块3. 息屏功能实现4. 息屏时间

Python如何去除图片干扰代码示例

《Python如何去除图片干扰代码示例》图片降噪是一个广泛应用于图像处理的技术,可以提高图像质量和相关应用的效果,:本文主要介绍Python如何去除图片干扰的相关资料,文中通过代码介绍的非常详细,... 目录一、噪声去除1. 高斯噪声(像素值正态分布扰动)2. 椒盐噪声(随机黑白像素点)3. 复杂噪声(如伪

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的