使用SCA逆向VM虚拟机

354 阅读2分钟

前言

SCA又称侧信道攻击,核心思想就是通过运行时候各种信息对程序进行攻击。包括简单功耗分析攻击(SimplePower Analysis attacks,SPA)和差分功耗分析攻击(Differential Power Analysis attacks,DPA),与传统密码分析学相比,这种攻击手段攻击效果显著。

VM加密

这里使用的是自己写的一个CrackMe,不使用除了IO的任何系统函数

#include <stdio.h>
#include <string.h>

const char key[] = {104, 100, 107, 125, 108, 118, 100, 99, 96};

int main() {
    char input[10] = {0};
    scanf("%9s", input);
    if (strlen(input) != 9) {
        printf("ERROR The length of key is 9 bytes!\n");
        return -1;
    }
    for (int i = 0; i < 9; ++i) {
        if (input[i] != (key[9 - i - 1] ^ 5)) {
            printf("ERROR The key is incorrect!\n");
            return -1;
        }
    }
    printf("OK Thanks to your purchase!\n");
    return 0;
}

编译后使用VMProtect对main函数进行VM虚拟机保护

在这里插入图片描述

保护后使用IDA查看代码被替换了只剩下VM入口,代码是不可被直接阅读的

push xxx

call xxx

在这里插入图片描述

Block数量分析

这里使用的是angr对加密后的二进制文件模拟执行

def main():
    ql = Qiling([rf"{ROOTFS}/bin/main_vmp"], ROOTFS, stdin=pipe.SimpleInStream(sys.stdin.fileno()),
                stdout=pipe.SimpleOutStream(sys.stdout.fileno()))

对程序运行block进行hook,codeNum用来存放此次key所执行的block数

0x401010 - 0x504bB6 是VMP区段,我们仅仅记录该区段的block。

ql.hook_block(trace)

...
def trace(ql, addr, size):
    global codeNum
   
    if addr - ql.loader.images[0].base in range(0x401010, 0x504bB6):
        codeNum += 1

密钥的生成

该程序的密钥由9个字节组成,字节的范围是0~2^16-1

这里已知了该程序的密钥组成是a-z,所以使用a-z进行攻击

用a-z对每一位进行攻击直到block数量增多

`

src = string.ascii_letters
codeNum = 0
codeNum1 = 0
indexs = [0] * 9
cur = 0
key = ['a'] * 9
...

def start(_ql: Qiling):
    global data
    global key
    //前一个block数量
    global codeNum

    //当前block数量
    global codeNum1
    global cur
    
    
    //当前block > 前一个block
    if codeNum > codeNum1:
        codeNum1 = codeNum
    //前一个block > 当前block
    elif codeNum1 > codeNum:
        //该位正确
        indexs[cur] -=2
        //回溯
        key[cur] = src[indexs[cur]]
        //移动到下一位
        cur = cur + 1
    
    //新的一轮猜想
    codeNum = 0
    data = _ql.save()
    key[cur] = src[indexs[cur]]
    indexs[cur] +=1
    key1 = "".join(key).encode()

`

开始与结尾

为了提升程序运行效率,这里使用angr对VM头时的Context进行保存,拦截put函数对结果进行判断

``

def start(_ql: Qiling):
	...
    //存放当前的Context
    data = _ql.save()
    //写入猜想的key
    _ql.os.stdin.write(key1)


def end(_ql: Qiling):
    global codeNum
    //从edi寄存器取结果字符串
    read = str(_ql.mem.read(_ql.reg.rdi, 30))
    if "ERROR" in read:
        //包含ERROR则还原环境进行下一次攻击
        global data
        data = _ql.restore(data)
    else:
        cur = cur + 1
    global key
    print("密钥:", "".join(key[0:cur]).encode())

运行结果

等待了几十秒密钥便攻击出来了

在这里插入图片描述

放到crackme中

在这里插入图片描述

完整代码

#!/usr/bin/env python3

import sys
from qiling.extensions import pipe
import string

from qiling import Qiling

ROOTFS = r"/home/mrack/qiling/examples/rootfs/x8664_linux"

src = string.ascii_letters

global data
global key
global codeNum
global codeNum1
global isFirst

codeNum = 0
codeNum1 = 0

indexs = [0] * 9

cur = 0

key = ['a'] * 9


def start(_ql: Qiling):
    global data
    global key
    global codeNum
    global codeNum1
    global cur

    if codeNum > codeNum1:
        codeNum1 = codeNum
    elif codeNum1 > codeNum:
        indexs[cur] -= 2
        key[cur] = src[indexs[cur]]
        cur = cur + 1

    codeNum = 0
    data = _ql.save()

    key[cur] = src[indexs[cur]]

    indexs[cur] += 1

    key1 = "".join(key).encode()

    _ql.os.stdin.write(key1)


def end(_ql: Qiling):
    global cur
    global codeNum
    read = str(_ql.mem.read(_ql.reg.rdi, 30))
    print(codeNum)
    if "ERROR" in read:
        global data
        data = _ql.restore(data)
    else:
        cur = cur + 1
    global key
    print("密钥:", "".join(key[0:cur]).encode())


def trace(ql, addr, size):
    global codeNum
    if addr - ql.loader.images[0].base in range(0x401010, 0x504bB6):
        codeNum += 1


def main():
    ql = Qiling([rf"{ROOTFS}/bin/main_vmp"], ROOTFS, stdin=pipe.SimpleInStream(sys.stdin.fileno()),
                stdout=pipe.SimpleOutStream(sys.stdout.fileno()))
    ba = ql.loader.images[0].base
    ql.hook_address(start, ba + 0x504168)
    ql.hook_address(end, ba + 0x05E0)
    ql.hook_block(trace)
    ql.run()


if __name__ == "__main__":
    main()

关注我的技术公众号 不定期分析各种技术文章 在这里插入图片描述