吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5809|回复: 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++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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, 2025-4-7 21:40

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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