使用unicorn engin还原Armariris字符串混淆

栏目: Python · 发布时间: 5年前

内容简介:git clone git@github.com:gossip-sjtu/Armariris.git编译

使用unicorn engin还原Armariris字符串混淆

编译Armariris

git clone git@github.com:gossip-sjtu/Armariris.git

编译

cd Armariris
mkdir build
cd build
cmake ../ -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="ARM;X86;AArch64"
make -j8

测试文件内容如下:

#include <stdio.h>

void fun(){
    printf("test 3333\n");
}

int main(int argc, char *argv[]) {
    printf("test 1111\n");
    printf("test 2222\n");
    fun();
    return 0;
}

使用编译好的llvm编译这个测试的文件

clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -mllvm -sobf test.c -o test

使用isysroot指定sdk,然后使用-mllvm -sobf开启字符串混淆

Armariris是如何进行字符串混淆的

我们直接看使用ida反汇编出来的代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  printf(aRcur7777, argv, envp);
  printf(&byte_100001036);
  fun();
  return 0;
}

可以看到有两个printf函数打印了一些数据出来,我们点第一个打印的字符串,双击aRcur7777跳转到 字符串定义位置,这个字符串在data段

使用unicorn engin还原Armariris字符串混淆

这个字符串我们本来输出的是test 1111这里显然不是,我们查看aRcur7777的交叉引用,发现两处, 其中一处是main函数中的printf,另一处应该就是还原这个字符串的位置了

使用unicorn engin还原Armariris字符串混淆

所以__datadiv_decode14953400483976599729这个函数就是还原这个字符的函数,我们看他是如何做的还原 。跳转过去按F5反编译,得到的结果如下:

__int64 datadiv_decode14953400483976599729()
{
  bool v0; // ST23_1
  bool v1; // ST17_1
  __int64 result; // rax
  bool v3; // ST0B_1
  unsigned int v4; // [rsp+8h] [rbp-1Ch]
  unsigned int v5; // [rsp+14h] [rbp-10h]
  unsigned int v6; // [rsp+20h] [rbp-4h]

  v6 = 0;
  do
  {
    aLKl[v6] ^= 0x38u;
    v0 = v6++ < 0xA;
  }
  while ( v0 );
  v5 = 0;
  do
  {
    aRcur7777[v5] ^= 6u;
    v1 = v5++ < 0xA;
  }
  while ( v1 );
  v4 = 0;
  do
  {
    byte_100001036[v4] ^= 0x71u;
    result = v4 - 10;
    v3 = v4++ < 0xA;
  }
  while ( v3 );
  return result;
}

我们可以看到aRcur7777的还原是和6做了异或操作,那我们来验证一下是否是我们看到的这样。

aRcur7777的原始数据是[0x72, 0x63, 0x75, 0x72, 0x26, 0x37, 0x37, 0x37, 0x37]

每一位和6异或之后的结果是[0x74, 0x65, 0x73, 0x74, 0x20, 0x31, 0x31, 0x31, 0x31]

对于的ascii字符串就是test 1111

他这里做字符串混淆用的是一个很简单的原理,一个数字两次异或同一个值,得到的结果是本事的值。也就是 第一次异或就给字符串混淆了,再异或一次就把数据还原了。

源码分析

字符串混淆的源文件在lib/Transforms/Obfuscation/StringObfuscation.cpp这个位置, 实现字符串混淆的是一个ModulePass,关于ModulePass可以参考 http://llvm.org/doxygen/classllvm11ModulePass.html#details 。在这个pass里面会遍历字符串,然后把字符串和生成的key异或,并替换原始的值,关键代码如下:

// Duplicate global variable
GlobalVariable *dynGV = new GlobalVariable(M,
                                          gv->getType()->getElementType(),
                                          !(gv->isConstant()), gv->getLinkage(),
                                          (Constant*) 0, gv->getName(),
                                          (GlobalVariable*) 0,
                                          gv->getThreadLocalMode(),
                                          gv->getType()->getAddressSpace());
// dynGV->copyAttributesFrom(gv);
dynGV->setInitializer(gv->getInitializer());

std::string tmp=gv->getName().str();
//  errs()<<"GV: "<<*gv<<"\n";

Constant *initializer = gv->getInitializer();
ConstantDataSequential *cdata = dyn_cast<ConstantDataSequential>(initializer);
if (cdata) {
        const char *orig = cdata->getRawDataValues().data();
        unsigned len = cdata->getNumElements()*cdata->getElementByteSize();

        encVar *cur = new encVar();
        cur->var = dynGV;
        cur->key = llvm::cryptoutils->get_uint8_t();
        // casting away const is undef. behavior in C++
        // TODO a clean implementation would retrieve the data, generate a new constant
        // set the correct type, and copy the data over.
        //char *encr = new char[len];
        //Constant *initnew = ConstantDataArray::getString(M.getContext(), encr, true);
        char *encr = const_cast<char *>(orig);
        // Simple xor encoding
        for (unsigned i = 0; i != len; ++i) {
                encr[i] = orig[i]^cur->key;
        }

        // FIXME Second part of the unclean hack.
        dynGV->setInitializer(initializer);

        // Prepare to add decode function for this variable
        encGlob.push_back(cur);
} else {
        // just copying default initializer for now
        dynGV->setInitializer(initializer);
}

// redirect references to new GV and remove old one
gv->replaceAllUsesWith(dynGV);
toDelConstGlob.push_back(gv);

在替换了之后为了保证程序可以正常运行,还得加一个函数输还原字符串,还原字符串的 函数生成代码在addDecodeFunction中。在这里添加了.datadiv_decode开始的函数 加上一串随机字符串,里面进行了异或操作,将数据还原。然后将这个函数加入到了entry,这个在 elf文件的话,就会被加入到.init_array,在mach-o文件中就会被加入到__mod_init_func。 代码也比较简单,可以参照源码看一下。

还原字符串

前面讲了原理其实很简单,那么怎么还原字符串呢,其实也有很多方式,第一种是内存dump,因为他会在 初始化程序的时候就把原始字符串还原回去。但是有时候我就行静态分析,不想执行之后去dump。如果只 静态分析,也可以去人工还原字符串。但是如果字符串很多,人工还原工作量很大。其实我们还可以使用 unicorn之类的工具,模拟去执行他的指令,把字符串进行还原。

还原混淆字符串的思路

  1. 找到所有.datadiv_decode开始的函数
  2. unicorn分配内存,将程序的.text段和.data段映射到unicorn分配的内存中
  3. 模拟执行所有.datadiv_decode开始的函数
  4. 最后将unicorn中分配的data读出来,patch到程序中

使用的工具

因为不同操作系统可执行文件格式不一样。为了简单点,我们直接写一个ida插件。所以需要以下工具:

  1. ida
  2. python2 (因为ida里面内置的 python 是python2)
  3. python2安装unicorn和keystone库

找到所有的.datadiv_decode开始的函数

idautils.Functions()可以遍历函数,遍历匹配含有datadiv_decode的函数,保存他们 的起始地址,代码很简单,如下:

import idaapi
import idc
import idautils

for func in idautils.Functions():
    func_name = idc.GetFunctionName(func)
    if "datadiv_decode" in func_name:
        func_data = idaapi.get_func(func)
        start = func_data.start_ea
        end = func_data.end_ea

unicorn分配内存

我这里分配内存的想法是直接用ida的api获取data段和text段的内容,以及起始地址,然后在 unicorn里面对于起始分配内存,将data段和text段写进去。

unicorn分配内存还是有些坑,不能直接在任意地址分配,必须得整除1024的才可以,所以需 要稍微计算一下分配的地址。这里对基地址减去对(1024 * 1024)求余的结果作为新的基地址, 然后分配内存的长度增加(1024 * 1024),实现的代码如下

def get_base_and_len(base, length):
    _base = base - (base % (1024 * 1024))
    _length = (length / (1024 * 1024) + 1) * 1024 * 1024
    return _base, _length

算出起始地址之后使用unicorn的mem_map方法分配内存即可

模拟执行,patch程序

模拟执行这里也比较简单,直接调用unicorn的emu_start方法,然后传入函数的起始地址即可开始 模拟执行,模拟执行完成之后将data段读出来,模拟执行下一个函数的时候使用这个data加载到内存中。 这样所有的.datadiv_decode函数执行之后data段就被还原了。将还原的data段用ida的patch去 修改掉原始的data,这个时候你看到的字符串就是原始的字符串了。

运行脚本前效果如下:

使用unicorn engin还原Armariris字符串混淆

运行脚本之后效果如下:

使用unicorn engin还原Armariris字符串混淆

使用unicorn engin还原Armariris字符串混淆

这个时候可以选中字符串,然后按a就能得到下面的效果

使用unicorn engin还原Armariris字符串混淆

使用unicorn engin还原Armariris字符串混淆

完整代码以及示例二进制文件

代码以及二进制文件存放在 https://github.com/smartdone/re_scripts/tree/master/ida 其中Armaririsstringobfuscationbypass.py是ida用来还原Armariris混淆过的字符串的插件。sample里面的testlinuxx8664和testmacosx86_64是示例二进制文件。

如果本文说的有错误的地方,请及时指正,谢谢。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

SCWCD Exam Study Kit Second Edition

SCWCD Exam Study Kit Second Edition

Hanumant Deshmukh、Jignesh Malavia、Matthew Scarpino / Manning Publications / 2005-05-20 / USD 49.95

Aimed at helping Java developers, Servlet/JSP developers, and J2EE developers pass the Sun Certified Web Component Developer Exam (SCWCD 310-081), this study guide covers all aspects of the Servlet an......一起来看看 《SCWCD Exam Study Kit Second Edition》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换