皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护

admin 2022年4月12日18:06:05评论281 views字数 8304阅读27分40秒阅读模式

皮蛋厂的学习日记系列为山东警察学院网安社成员日常学习分享,希望能与大家共同学习、共同进步~

  • 2020 绿冰壶 | 初探smc动态代码保护

    • 前言

    • 什么是smc?

    • 前置知识:PE文件结构

    • smc的 c实现

    • 在SMC中加入密码学算法

    • smc ctf实战

    • 后记

    • 参考文章


2020 绿冰壶 | 初探smc动态代码保护

前言

本文首发于奇安信攻防社区

缘起于2021mrctf逆向的Dynamic Debug。本菜鸡re复现路上的第一道smc保护的题目。

什么是smc?

先来看官方注释

SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果,而计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。

大白话:软件在运行前,先通过加密部分代码,达到绕过检测或阻止静态分析的目的,之后在运行程序时,再对代码进行解密,保证程序的正常运行。因此,遇到smc的题目的话,必须得动调了,静态分析再怎么修复也不可能是正确的。

前置知识:PE文件结构

在研究smc的实现之前,首先我们要了解前置知识:PE文件结构。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

为了防止大家看不懂英文,给大家附上一张中文版本

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

图片来源:https://blog.csdn.net/adam001521/article/details/84658708

作者:adam001521

下面浅浅介绍一下各部分用途:

MS-DOS header+DOS stub:早期为了DOS和Windows系统共存设计

PE文件标志:第一张图中没显示,应该是存在stub和PE header之间。4个字节,也就是“PE/0/0”

PE Header+Optional Header(可选头部):存放PE文件的很多重要信息,比如文件包含的段(Sections)数、时间戳、装入基址和程序入口点等信息。

段头部+段实体

smc的 c实现

通常来说,SMC使用汇编去写会比较好,因为它涉及更改机器码,但SMC也可以直接通过C、C++来实现。先来看一下展示smc思路的伪代码

IF .运行条件满足
  CALL DecryptProc (Address of MyProc);对某个函数代码解密
  ........
  CALL MyProc                           ;调用这个函数
  ........
  CALL EncryptProc (Address of MyProc);再对代码进行加密,防止程序被Dump

简单实现

首先需要新增一个段,例如我们实现创建 qaq这么一个新段

#pragma code_seg(".qaq")
void qaqq()
{
cout<<"WIN";
}
void d()
{
;
}
#pragma code_seg()
#pragma comment(linker,"/SECTION:.qaq,ERW")

我们想加密一个新段,就得先找到他,即实现寻址,最简单的办法莫过于指针赋值:

char * b1=(char*)abc;
char * c1=(char*)d;
int i=0;
for(;b1<c1;b1++)
{
i++;
}
void* a1=(char*)abc;
for(int i=0;i<32;i++)
*((BYTE*)a1+i)^=key;

这样大概我们就实现了一个简单的smc

#include<iostream>
#include<Windows.h>

using namespace std;

#pragma code_seg(".aaa")
void qaqq()
{
cout << "you WIN";
}
void d()
{
;
}
#pragma code_seg()
#pragma comment(linker, "/SECTION:.ddd,ERW")


int main()
{
int key;
cout << "input you key:" << endl;
cin >> key;
char* b1 = (char*)qaqq;
char* c1 = (char*)d;
int i = 0;
for (; b1 < c1; b1++)
{
i++;
}
void* a1 = (char*)qaqq;
for (int i = 0; i < 32; i++)
{
*((BYTE*)a1 + i) ^= key;
}
qaqq();
system("PAUSE");
}

运行时报错,找到原因是由于 在进行异或加密处理之后,机器码发生了变化,这导致加密代码无法被识别而报错。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

所以需要我们手改一下机器码。

利用studyPE+,PEid,exeinfo等查看一下段的地址,然后利用 winhex之类的工具修改为加密之后的机器码就可以了。这样程序就可以正常运行了。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img
皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

在SMC中加入密码学算法

还记得吗,上文源码中采取了最简单的异或方式来进行加密,既然可以使用异或,当然也可以使用其他更复杂的加密方式。

使用更复杂的加密方式,其实和使用异或是一个道理。区别无非是要编写加解密函数,更换加密方式而已。仍然是smc加密三步走:

编写加解密函数-->计算加密前后机器码-->利用十六进制读取工具修改机器码。

详细过程可以通过上文体悟,也可以看下面这篇大佬实操文章。过程都是一样的,这里不再赘述。

https://bbs.pediy.com/thread-92375.htm

smc ctf实战

[2021羊城杯] BabySmc

相对其他smc题目来说,确实是挺baby的

奇怪的main函数

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

首先跟进一下main函数中的byte_140001085。发现一大串数据。

这里我们直接按c是无法恢复数据为代码的,问题应该是出在上面调用的sub_14001E30函数。

BOOL sub_140001E30()
{
  _BYTE *v0; // r9
  __int64 v1; // rdx
  DWORD flOldProtect; // [rsp+20h] [rbp-8h] BYREF

  VirtualProtect(lpAddress, qword_14002AD88 - (_QWORD)lpAddress, 0x40u, &flOldProtect);
  v0 = lpAddress;
  v1 = qword_14002AD88;
  if ( (unsigned __int64)lpAddress < qword_14002AD88 )
  {
    do
    {
      *v0 = __ROR1__(*v0, 3) ^ 0x5A;//关键代码,循环3次右移+异或
      ++v0;
      v1 = qword_14002AD88;
    }
    while ( (unsigned __int64)v0 < qword_14002AD88 );
    v0 = lpAddress;
  }
  return VirtualProtect(v0, v1 - (_QWORD)v0, flOldProtect, &flOldProtect);
}

在此处下个断点,开始动调。随便输入字符串,程序断在该位置

继续f8单步 弹出关于rip的一个对话框,这是由于下面不是代码区域,ida会报错,点否。此时数据发生了改变

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

现在要恢复这些看似为数据的代码:从main函数头开始选定到第一个retn,右键->analyze selected area->force 强制转换。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

至此,smc成功被破解。代码被恢复,就可以直接反编译了

这一段是base64加密,不过加了个异或(四位一循环,分别异或A6,A9,A3,AC)

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

flag字符串验证比较

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

理一下解题思路:

换表base64+异或

逆向的步骤就是 异或+换表base64

异或部分处理灰常简单,直接上python脚本

a=list('H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4')
x=[0XA6,0XA3,0XA9,0XAC]
for i in range(len(a)):
    a[i]=ord(a[i]) ^ x[i%4]
print(a)

下面处理base64加密部分,先找到密码表,shift+e导出数据

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

接着编写换表脚本。输出结果解密得到flag

a=list('H>oQn6aqLr{DH6odhdm0dMe`MBo?lRglHtGPOdobDlknejmGI|ghDb<4')
x=[0XA6,0XA3,0XA9,0XAC]
for i in range(len(a)):
a[i]=ord(a[i]) ^ x[i%4]
print(a)
b=[ 0xE4, 0xC4, 0xE7, 0xC7, 0xE6, 0xC6, 0xE1, 0xC1, 0xE0, 0xC0,
0xE3, 0xC3, 0xE2, 0xC2, 0xED, 0xCD, 0xEC, 0xCC, 0xEF, 0xCF,
0xEE, 0xCE, 0xE9, 0xC9, 0xE8, 0xC8, 0xEB, 0xCB, 0xEA, 0xCA,
0xF5, 0xD5, 0xF4, 0xD4, 0xF7, 0xD7, 0xF6, 0xD6, 0xF1, 0xD1,
0xF0, 0xD0, 0xF3, 0xD3, 0xF2, 0xD2, 0xFD, 0xDD, 0xFC, 0xDC,
0xFF, 0xDF, 0x95, 0x9C, 0x9D, 0x92, 0x93, 0x90, 0x91, 0x96,
0x97, 0x94, 0x8A, 0x8E]
base64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def base(a):
for i in range(len(a)):
k=b.index(a[i])
print(base64[k],end='')
base(a)
皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

[2020网鼎杯] joker

IDA打开程序就看到有保护,头疼。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

尝试f5,发现因为堆栈不平衡,无法直接反编译,所以修改一下

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

勾选堆栈指针,快捷键alt+k,将SP修改为零,如果下面还遇到同理

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

浅re一下

omg 和wrong 两个函数

char *__cdecl wrong(char *a1)
{
char *result; // eax
int i; // [esp+Ch] [ebp-4h]

for ( i = 0; i <= 23; ++i )
{
result = &a1[i];
if ( (i & 1) != 0 )
a1[i] -= i;
else
a1[i] ^= i;
}
return result;
}

wrong函数首先对输入的flag的每个字节根据 与运算 1 后是否为真进行了异或下标的操作

int __cdecl omg(char *a1)
{
int result; // eax
int v2[24]; // [esp+18h] [ebp-80h]
int i; // [esp+78h] [ebp-20h]
int v4; // [esp+7Ch] [ebp-1Ch]

v4 = 1;
qmemcpy(v2, &unk_4030C0, sizeof(v2));
for ( i = 0; i <= 23; ++i )
{
if ( a1[i] != v2[i] )
v4 = 0;
}
if ( v4 == 1 )
result = puts("hahahaha_do_you_find_me?");
else
result = puts("wrong ~~ But seems a little program");
return result;
}

omg:wrong得到的结果跟一个全局变量unk_4030C0比较(flag字符串比较函数)

result="fkcdx7fagd;Vka{&;Pc_MZqx0c7f"
i=0
flag=""
for j in result:
    if(i&1):
        flag+=chr(ord(j)+i)
    else:
        flag+=chr(ord(j)^i)
    i+=1
print flag

浅逆一下 出了一个 fake flag 出题人 he tui

这样的话 ,main函数中剩余的那个encrypt函数应该就是真正的加密函数了。然鹅由于加了smc保护,无法反汇编该函数

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img
皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

回去观察一下main函数,通过上图标注的,涉及encrypt函数的循环 可以推测,出题者这里先给函数的代码段进行了加密,然后在运行的时候再用这层循环进行解密,相当于加了一层壳。还能怎么办呢,动态调试呗,定位函数调用,之前下个断点 把程序跑起来

利用od自带的中文搜索引擎定位到该函数

可以看到这里,00401805~0040182b非常符合for循环优化后的指令序列,[ebp-0xC]就是i,jocker.00401500就是ecrypt()的地址。因此我们下断点到循环结束的地方,然后F9,

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

在f7步入解密后的ecrypt函数内部

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

接下来使用olldump脱壳

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

脱壳后的程序用ida打开,encrypt函数已经被解密,被命名为start函数

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

浅逆一下,得到前19位flag还缺5位

result2="x0ex0dx09x06x13x05x58x56x3ex06x0cx3cx1fx57x14x6bx57x59x0d"
flag=""
haha="hahahaha_do_you_find_me?"
for i in range(19):
    flag+=chr(ord(haha[i])^ord(result2[i]))
print(flag)

还剩一个finally函数,用脱壳后的程序分析一下

int __cdecl sub_40159A(_BYTE *a1)
{
unsigned int v1; // eax
int result; // eax
char v3; // [esp+13h] [ebp-15h]
char v4; // [esp+14h] [ebp-14h]
char v5; // [esp+15h] [ebp-13h]
char v6; // [esp+16h] [ebp-12h]
char v7; // [esp+17h] [ebp-11h]
int v8; // [esp+18h] [ebp-10h]
int v9; // [esp+1Ch] [ebp-Ch]

v3 = '%';
v4 = 't';
v5 = 'p';
v6 = '&';
v7 = ':';
v1 = time(0);
srand(v1);
v9 = rand() % 100;
v8 = 0;
if ( (*a1 != '%') == v9 )
result = puts("Really??? Did you find it?OMG!!!");
else
result = puts("I hide the last part, you will not succeed!!!");
return result;
}

最后五位为v3~v7与一个随机数异或的结果,然而这个随机数只是看似随机。因为flag最后一个字节一定是‘}’,那么用‘:’^‘}’=0x47计算出随机数,然后使用“%tp&:”分别异或0x47得到最后5个字节。

完整脚本:

result2="x0ex0dx09x06x13x05x58x56x3ex06x0cx3cx1fx57x14x6bx57x59x0dx47x47x47x47x47"
flag=""
haha="hahahaha_do_you_fin%tp&:"
for i in range(24):
    flag+=chr(ord(haha[i])^ord(result2[i]))
print(flag)

[MRCTF2021]Dynamic_debug

64位elf文件,话不多说,直接开始动调

绕过长度检测

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

smc加密伪代码修复

按c强制转换代码

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

在差不多的随意位置下断点。动调,输入32位字符绕过长度限制

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img
皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

开始位置按p创建函数,f5反编译,看到了主函数

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

跟进关键解密函数发现,得到的伪代码没有变量识别,非常难看。就在这卡了好久。看到了wjh大佬的blog。了解到这里可以尝试修复堆栈我们可以尝试着在这部分之上使用 Keypatch 手动加入一个 push rbp; mov rbp, rsp让 IDA 能够识别出堆栈上的变量,紧接着再 F5,就可以看到比较舒服的伪代码了。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

tea算法识别+破解

一眼tea好吧。通过循环执行了 32 次,并且在循环内部对一个变量反复增加 delta 常数 (0x9E3779B9),循环内部出现了 TEA 运算逻辑等特征。

皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护
img

最后标准tea解密解码即可

#include <cstdio>
void encrypt(unsigned int* v, const unsigned int* k)
{
    unsigned int v0 = v[0], v1 = v[1], sum = 0, i;
    unsigned int delta = 0x9E3779B9;
    unsigned int k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i < 32; i++)
    {
        sum += delta;
        v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
    }
    v[0] = v0;
    v[1] = v1;
}
void decrypt(unsigned int* v, unsigned int* k)
{
    unsigned long v0 = v[0], v1 = v[1], sum = 0xC6EF3720, i;
    unsigned long delta = 0x9e3779b9;
    unsigned long k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
    for (i = 0; i < 32; i++)
    {
        v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
        v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
        sum -= delta;
    }
    v[0] = v0;
    v[1] = v1;
}
int main()
{
    unsigned int v[] = {
        0x5585A1990x7E825D680x944D00390x717269430x6A514306,c 0x4B14AD000x64D20D3F0x9F37DB150
    };
    unsigned int k[4] = { 0x6B696C690x79645F650x696D616E0x67626463 };
    for (int i = 0; i < 8; i += 2) decrypt(v + i, k);
    printf("%s", v);
    return 0;
}

后记

浅浅总结一下学习SMC的感受趴

最简单的SMC保护效果是很弱的,因为在程序运行的某一时刻,它一定是解密完成的,这时也就暴露了,使用动态分析运行到这一时刻即可过掉保护

复杂一点需要你找到修改代码段的算法,但是同样的,你只需要根据静态分析获得解密算法,就可直接写出解密脚本提前解密这段代码。所以SMC通常是配合反追踪技术或是嵌套的使用。

更深入的关于smc的知识,期待自己进一步的学习。

参考文章

https://www.52pojie.cn/thread-1184425-1-1.html

https://bbs.pediy.com/thread-263816-1.htm

https://bbs.pediy.com/thread-140865.htm

https://bbs.pediy.com/thread-92375.htm

https://blog.wjhwjhn.com/archives/220/

https://bbs.pediy.com/thread-271790.htm

https://blog.csdn.net/qq_32072825/article/details/121657090


原文始发于微信公众号(山警网络空间安全实验室):皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月12日18:06:05
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   皮蛋厂的学习日记 | 2022.4.12 初探smc动态代码保护https://cn-sec.com/archives/903582.html

发表评论

匿名网友 填写信息