吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5145|回复: 30
收起左侧

[原创] 借助Pin插桩技术解决CTF中的VMP问题

[复制链接]
wxcWxc 发表于 2020-11-10 21:48
本帖最后由 wxcWxc 于 2020-11-12 22:54 编辑

借助Pin插桩技术解决CTF中的VMP问题

前提知识

  • 插桩技术:在不改变程序原有运行顺序的基础下,在程序中插入一些用来进行信息记录的探针,通过探针的执行使程序自动抛出一些信息,再加以对这些信息的分析,获得程序控制流和数据流的信息。分为源代码插桩和二进制插桩。
  • 源码插桩:修改源代码,插入额外的代码,我们日常调试程序中打log的方法就是源代码插桩。
  • 二进制插桩:分为静态二进制插桩和动态二进制插桩。
    1. 静态二进制插桩:插入额外的汇编代码,生成一个新的可执行程序。
    2. 动态二进制插桩:用动态库的方式,运行时动态的注入代码,不会改变原有二进制程序。
  • Pin:用于IA-32、x86-64和MIC指令集体系结构的动态二进制插桩框架,支持创建动态程序分析工具。

例题及测试环境

  • 西湖论剑2020 RE flow
  • IDA7.0(Mac)
  • docker 镜像选择pwndocker:latest
  • pin-3.16-gcc-linux

初期准备

前期黑盒测试

静态分析flow,首先file

~/Downloads/西湖论剑/REVERSE/2 » file flow      127 ↵ wangxiaocheng@MacBook-Pro
flow: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=03ec3130ef3e8a8f345c120e56d0b3073900a11e, for GNU/Linux 3.2.0, stripped

接下来按我的做逆向的思路,会先用脚本做个黑盒测试,用pin的指令计数,看看能不能爆破出来flag。但是实际的结果是不可取的,猜测程序内部没有按位比对或者按块比对判断的机制。

静态分析

加载到IDA64中,找到main函数,直接反汇编

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // rax
  char s; // [rsp+0h] [rbp-30h]
  int i; // [rsp+2Ch] [rbp-4h]

  __isoc99_scanf(&unk_402008, &s, a3);
  if ( strlen(&s) == 32 )
  {
    str2hex(&s, (__int64)input);                // tohex
    for ( i = 0; i <= 49; ++i )
      byte_40C420[i] = 0xCCu;
    memcpy(dest, &off_40B280, 0x100uLL);
    sub_4017ED(byte_40C420, 306);
    puts("Here is your flag: retr0{retr0_yyds_orzzzzz_but_fake_flag}");
    result = 0LL;
  }
  else
  {
    puts(buf);
    result = 0LL;
  }
  return result;
}

可以看到程序的流程十分简单,输入为32位,然后经过一个str2hex的操作(手动改过名),后面还有一个输出假flag,很明显中间的程序会造成控制流的改变。

接着分析,先是填充了49个0xCC,0xCC一般用于debug版填充栈,猜测这里的0xCC是要做一种保护操作,然后接着两个memcpy,第一个写在了外面,另一个写在了一个函数里面,好家伙,看sub_4017ED函数,当场来一个栈溢出,猜测是通过rop劫持了控制流。

详细分析

动态跟踪

通过动态跟踪,清楚的可以发现程序到这个地方会飞出去。

接着动态分析了以后程序块跳来跳去,但每个程序块最后都有一个0xF的systemcall,之前学过一点点PWN,所以这个地方是用SROP来保护软件,思路清奇。 截屏2020-11-10 下午8.09.42.png

后面动态继续分析,前面几个代码块比较简单,可以直接反编译,反编译的结果是RC4算法,后面接着4个字节成一组把加密结果写入到一块内存地址中。当时也就做到这里,后面的虚拟机实在是难提取操作码序列。 截屏2020-11-10 下午8.08.59.png

接下来程序会在几个代码块中不断的跳转,开始一步一步记录控制流,但是程序流程还是挺长的,所以放弃了这个思路。

分析VMP OP

下面的思路是分析这几个代码块,简单分析,会发现这些代码块有汇编的影子,并且只用了寄存器来运算,接着一个函数一个函数分析,将每个函数重命名后,可以确认这是一个基于寄存器的虚拟机,VMopcode存于栈上,用srop的方式形成控制流。

到这里解决的办法就很多了,动态提取栈上的数据,然后写脚本模拟VM执行;用gdb脚本在每个块前设置断点,打印控制流;用Pin打log的方式记录虚拟机运行流程

我选择用Pin插桩的方式打印运行流程

使用Pin解决VM

Pintool初步构思

插桩粒度 API 执行时机
指令级插桩 INS_AddInstrumentFunction 执行一条新指令
轨迹级插桩 TRACE_AddInstrumentFunction 执行一个新trace
镜像级插桩 IMG_AddInstrumentFunction 加载新镜像时
函数级插桩 RTN_AddInstrumentFunction 执行一个新函数时
  • 插桩粒度使用指令级插桩都可以,轨迹级插桩无法传入寄存器类参数
  • 选择性插桩,加快执行,在插桩前进行判断,利用trace提供的API获取到指令地址,进行判断,若是我们虚拟机中的几个操作函数即进行插桩,并编辑格式打印出运行log,推荐使用c语言内联汇编格式,可以在十分复杂的情况下,用编译优化进行处理。

编写起来还是很方便的,只用关注指令地址即可。

编写工程中我们用PinTools的模版进行编写。

Pintool实现

/*
 * Copyright 2002-2020 Intel Corporation.
 * 
 * This software is provided to you as Sample Source Code as defined in the accompanying
 * End User License Agreement for the Intel(R) Software Development Products ("Agreement")
 * section 1.L.
 * 
 * This software and the related documents are provided as is, with no express or implied
 * warranties, other than those that are expressly stated in the License.
 */

#include <stdio.h>
#include "pin.H"

FILE * trace;

VOID printip(ADDRINT * ip,ADDRINT *rax,ADDRINT *rdx,ADDRINT *rcx,ADDRINT *rbx) { 
  //打印虚拟机log  
  if((long int)ip == 0x004015F1){ //mov
        if(rbx){
            fprintf(trace, "%p\tMOV (%p)[%p],input[%p]\n", ip, rdx, rcx, rax);
        }else{
            fprintf(trace, "%p\tMOV (%p)[%p],ECX(%p)\n", ip, rdx, rax, rcx);
        }
    }else if ((long int)ip == 0x401594){ //add
        if(rbx){
            fprintf(trace, "%p\tADD RDX,input[%p]+input[%p]\n", ip, rax, rcx);
        }else{
            fprintf(trace, "%p\tADD RDX,input[%p]+%p\n", ip, rax, rcx);
        }
    }else if ((long int)ip == 0x401477){ //xor
        if(rbx){
            fprintf(trace, "%p\tXOR (%p)[%p],input[%p]\n", ip, rdx, rax, rcx);
        }else{
            fprintf(trace, "%p\tXOR (%p)[%p],%p\n", ip, rdx, rax, rcx);
        }
    }else if ((long int)ip == 0x401408){ //cmp
        if(rbx){
            fprintf(trace, "%p\tCMP (%p)[%p],(%p)[%p]\n", ip, rdx, rax, rdx, rcx);
        }else{
            fprintf(trace, "%p\tCMP (%p)[%p],%p\n", ip, rdx, rax, rcx);
        }
    }else if ((long int)ip == 0x401464){ //jmp
        if(rdx){
            fprintf(trace, "%p\tJNZ (%p) no \n", ip, rax);
        }else{
            fprintf(trace, "%p\tJNZ (%p)\n", ip, rax);
        }
    }else if ((long int)ip == 0x401535){ //shr
        if(rbx){
            fprintf(trace, "%p\tSHL (%p)[%p],(%p)[%p]\n", ip, rdx, rax, rdx, rcx);
        }else{
             fprintf(trace, "%p\tSHL (%p)[%p],%p\n", ip, rdx, rax,rcx);
        }
    }else if ((long int)ip == 0x4014D6){ //shl
        if(rbx){
            fprintf(trace, "%p\tSHL (%p)[%p],(%p)[%p]\n", ip, rdx, rax, rdx, rcx);
        }else{
            fprintf(trace, "%p\tSHL (%p)[%p],%p\n", ip, rdx, rax,rcx);
        }
    }
}
VOID Instruction(INS ins, VOID *v){
    long int opList[] = {0x004015F1, 0x401594, 0x401477, 0x401408, 0x401464, 0x401535, 0x4014D6};//需要插桩的地址
    long int ip = INS_Address(ins);
    bool flag = false;
    for (size_t i = 0; i < 7; i++){
        if(ip == opList[i]){
            flag = true;
            break;
        }
    }//进行指令级别插桩
    if(flag){//IPOINT_BEFORE在指令执行前插桩
        INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip,
                   IARG_INST_PTR,
                   IARG_REG_VALUE,REG_EAX,
                   IARG_REG_VALUE,REG_EDX,
                   IARG_REG_VALUE,REG_ECX,
                   IARG_REG_VALUE,REG_EBX,
                   IARG_END);
    }
  //以IARG_REG_VALUE,REG_EAX的形式将执行这条指令前的寄存器值传入
}

VOID Fini(INT32 code, VOID *v)
{
    fprintf(trace, "#eof\n");
    fclose(trace);
}

/* ===================================================================== */
/* Print Help Message                                                    */
/* ===================================================================== */

INT32 Usage()
{
    PIN_ERROR("This Pintool log flow\n" 
              + KNOB_BASE::StringKnobSummary() + "\n");
    return -1;
}

/* ===================================================================== */
/* Main                                                                  */
/* ===================================================================== */

int main(int argc, char * argv[])
{
    trace = fopen("itrace.out", "w");

    // Initialize pin
    if (PIN_Init(argc, argv)) return Usage();

    // Register Instruction to be called to instrument instructions
    INS_AddInstrumentFunction(Instruction, 0);
    // Register Fini to be called when the application exits
    PIN_AddFiniFunction(Fini, 0);

    // Start the program, never returns
    PIN_StartProgram();

    return 0;
}

分析VM log

正常运行,输入32个字符,观察输出文件。
截屏2020-11-10 下午9.09.57.png

截屏2020-11-10 下午9.10.24.png

由于这个地方的汇编比较简单,没有打印为C语言内联汇编形式,再去让编译器优化后反编译。

此时被虚拟机保护起来的控制流已经被恢复,直接阅读汇编代码即可,看到9e3449b9条件反射是tea类的加密,四个密钥就是0x79757361、0x79796473、0xdeadbeef、0xaa114514,结合jmp,将前段代码复制后全局搜索
截屏2020-11-10 下午9.17.36.png

截屏2020-11-10 下午9.19.14.png
可以看到这段重复了64次并且在文档的最中间有一段小不同,可以判断是进行了两次32次的循环,结合我们输入会转化为16个字节,4字节分为一组,可以判断这里是做了两次的TEA加密。汇编流程也很简单,就是TEA的加密过程。

解密

跟踪到最后,会找到比对的数据,提取出来先解密TEA再RC4解密即可。



免费评分

参与人数 5吾爱币 +7 热心值 +5 收起 理由
小朋友呢 + 2 + 1 我很赞同!
爱飞的猫 + 3 + 1 用心讨论,共获提升!
hodic + 1 我很赞同!
18023999 + 1 + 1 真的很厉害
抱薪风雪雾 + 1 + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

mcwindy 发表于 2020-11-11 00:58
太棒了,学到许多
小朋友呢 发表于 2020-11-25 16:06
wxcWxc 发表于 2020-11-25 15:52
啊,想了一下,我觉得内联的方式怪麻烦的,可以直接吧log的导出方式换为C语言的语法,然后编译优化一下。 ...

下面是我自己实验时,将它转成这种C语法,但是它和汇编的阅读方法一样,如何让编译器优化它呢
[C++] 纯文本查看 复制代码
a2[4] = 0x79757361
a2[5] = 0x79796473
a2[6] = 0xdeadbeef
a2[7] = 0xaa114514

a2[13] = 0x0
a2[11] = a2[0]
a2[12] = a2[1]

a2[13] = a2[13] + 0x9e3779b9
a2[8] = a2[11]
a3 = a2[13]
a2[8] = a2[8] + a3
a2[9] = a2[11]
a2[9] = a2[9] >> 0x5
a3 = a2[5]
a2[9] = a2[9] + a3
a2[8] = a2[9] ^ a2[8]
a2[9] = a2[11]
a2[9] = a2[9] << 0x4
a3 = a2[4]
a2[9] = a2[9] + a3
a2[8] = a2[9] ^ a2[8]
a3 = a2[8]
a2[12] = a2[12] + a3
a2[8] = a2[12]
a3 = a2[13]
a2[8] = a2[8] + a3
a2[9] = a2[12]
a2[9] = a2[9] >> 0x5
a3 = a2[7]
a2[9] = a2[9] + a3
a2[8] = a2[9] ^ a2[8]
a2[9] = a2[12]
a2[9] = a2[9] << 0x4
a3 = a2[6]
a2[9] = a2[9] + a3
a2[8] = a2[9] ^ a2[8]
a3 = a2[8]
a2[11] = a2[11] + a3
a2[10] = a2[10] + 0x1
if a2[10] != 0x20
continue
 楼主| wxcWxc 发表于 2020-11-10 21:50
抱歉。。第一次写帖子不太熟练,格式有点问题

点评

把文章放到 中可解决。  详情 回复 发表于 2020-11-11 01:03
6767 发表于 2020-11-10 22:15
markdown格式全乱了,
要不本地导出一份pdf当附件,或者丢csdn备份一下也行
Ritt3r 发表于 2020-11-10 23:12
没按正常格式显示
爱飞的猫 发表于 2020-11-11 01:03
wxcWxc 发表于 2020-11-10 21:50
抱歉。。第一次写帖子不太熟练,格式有点问题

把文章放到
[MD][/MD]
中可解决。
深蓝海琼 发表于 2020-11-11 09:18
很不错,来看看
 楼主| wxcWxc 发表于 2020-11-11 12:50
jixun66 发表于 2020-11-11 01:03
把文章放到  中可解决。

好的,谢谢你,还有就是这个帖子是不是发布就没法修改呀

点评

可以修改的,你拉到1楼底部(二楼上去一点点)的左侧(“本帖被以下淘专辑推荐:”下面),应该会有一个编辑链接  详情 回复 发表于 2020-11-12 02:30
 楼主| wxcWxc 发表于 2020-11-11 12:51
Ritt3r 发表于 2020-11-10 23:12
没按正常格式显示

看到了,谢谢提醒
 楼主| wxcWxc 发表于 2020-11-11 12:52
mcwindy 发表于 2020-11-11 00:58
太棒了,学到许多

哈哈,谢谢支持
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-25 21:41

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表