目录
一、SMC简介
SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果。
特征:在汇编代码中夹杂大片的数据
基本原理:是在编译可执行文件时,将需要加密的代码区段(如函数、代码块) 单独编译成一个section(段),并将其标记为可读,可写,不可执行,然后通过某种方法在程序运行的时候将section解密为可执行代码,并将其标记为可读、可执行、不可写。这样就不能直接在内存里面找到加密的代码,从而无法执行或修改加密的代码
SMC的实现方式有很多种,可以通过修改PE文件的Section Header、使用API Hook实现代码加密和解密、使用VMProtect等第三方加密工具等。
机密时一般采用异或等简单的加密算法,解密是通过相同的算法对密文进行解密。
通俗来讲,就是程序可以自己对自己底层的字节码进行操作,就是所谓的自解密技术。其在ctf比赛中常见的就是可以将一段关键代码进行某种加密,然后程序运行的时候就直接解密回来,这样就可以干扰解题者的静态分析,在免杀方面也是非常好用的技术。可以利用该技术隐藏关键代码。
二、如何识别SMC?
SMC的实现是需要对目标内存进行修改的,.text一般是没有写权限的。
那么就需要拥有修改目标内存的权限:
- 在 linux系统 中,可以通过 mprotect函数 修改目标内存的权限
- 在 Windows系统 中,VirtualProtect函数 实现内存权限的修改
因此也可以在IDA的导入表import中观察是否调用这两个函数来判断是否进行了SMC
mprotect函数
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
mprotect()
系统调用修改起始位置为addr
,长度为length
字节的虚拟内存区域中分页上的保护。addr
取值必须为分页大小的整数倍,length
会被向上舍入到系统分页大小的下一个整数倍。prot
参数是一个位掩码。
VirtualProtect()
#include <Memoryapi.h>
BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
VirtualProtect()
函数有4个参数,lpAddress
是要改变属性的内存起始地址,dwSize
是要改变属性的内存区域大小,flAllocationType
是内存新的属性类型,lpflOldProtect
内存原始属性类型保存地址。而flAllocationType
部分值如下表。在 SMC 中常用的是 0x40。
三、CTF中的SMC
SMC一般有两种破解方法
第一种是找到对代码或数据加密的函数后通过 idapython 写解密脚本。
第二种是 动态调试 到SMC解密结束的地方dump出来。
SCM在CTF里面有很多的运用,主要是用来对抗反调试和反编译等工具:
1、局部代码加密:CTF比赛中又很多加密的二进制程序,利用SMC技术可以对关键的代码进行加密
2、加密字符和常量:有很多加密的字符串和常量,这些字符串和常量通常用来存储关键信息,如密钥、密码等。利用 SMC 技术可以对这些字符串和常量进行加密,增加分析难度,提高程序的安全性
3、防止调试;有很多程序会使用调试器进行逆向分析,利用 SMC 技术可以对程序进行调试器检测和防御,防止调试器的使用
4、防止反编译:CTF 比赛中有很多程序会被反编译,利用 SMC 技术可以对程序进行反编译检测和防御,防止程序被反编译
四.【扩展】抵御静态分析技术
1.常见的抵御静态分析技术
抵御静态分析本质上就是降低反汇编代码的可读性,不过这种保护方式通常只是起到了扰乱作用,如果只是单独使用其中一种,那就只是纸老虎而已,理论上通过动态分析的方法,可以完全无视这样的保护,所以这些保护技术需要和其他保护技术相互配合才能达到良好的效果。
1.花指令
2.smc
3.信息隐藏
4.代码与数据结合
2.smc
self-modify-code,SMC技术比较惹人喜爱,它将一块局部的代码进行加密,而在程序运行过程中对这块代码进行解密,可以达到隐藏关键代码的效果。
最简单的SMC保护效果也是很弱的,因为在程序运行的某一时刻,它一定是解密完成的,这时也就暴露了,使用动态分析运行到这一时刻即可过掉保护;
其次是找到修改代码段的算法,即只要根据静态分析获得解密算法,就可直接写出解密脚本提前解密这段代码。所以SMC通常是配合反追踪技术或是嵌套的使用。
反追踪技术用于对抗动态分析,最常见的就是当程序发现自身正在被调试时,直接使调试结束。之前说到SMC由于在程序运行某一时刻一定是解密完成的,可通过动态分析直接获得解密结果,如果我们加上反追踪技术,就会使得动态分析的难度加大。
不过即使这样,只要分析人员找到了反追踪的关键函数,直接对其进行patch,之后过掉SMC也就顺理成章了。
所以还有更阴险的招数,就是把反调试的代码作为解密SMC的密钥,当我们对反调试代码进行修改时,解密就无法成功。如果下断点来修改寄存器的值来绕过反调试,而不进行代码修改怎么办呢?那么只需要使加密算法以字节为单位进行加密即可,在十分庞大的循环次数下,是不能总是手动去修改寄存器的值的。
那么如何使静态分析解密SMC的算法变得困难呢?这就主要靠嵌套型的SMC了,每段SMC中又包含了另一段SMC,只需要3,4层,就能使静态分析变得极为繁琐。
五、RE SMC例题
[网鼎杯 2020 青龙组]jocker
查壳,无壳,32bit文件
一打开就可以发现VitualProtect函数,对内存权限进行了修改,是SMC
往下看,看到两个加
很清楚的逻辑,直接逆
#include<iostream>
using namespace std;
int main() {
char a1[] = {
0x66, 0x6B, 0x63, 0x64, 0x7F, 0x61, 0x67, 0x64,
0x3B, 0x56, 0x6B, 0x61, 0x7B, 0x26, 0x3B, 0x50,
0x63, 0x5F, 0x4D, 0x5A, 0x71, 0x0C, 0x37, 0x66
};
int i;
for(i=0; i<=23; i++) {
if((i&1)!=0)
a1[i]+=i;
else
a1[i]^=i;
}
for(i=0; i<=23; i++)
cout<<a1[i];
return 0;
}//flag{fak3_alw35_sp_me!!}
这里解出一个flag{fak3_alw35_sp_me!!} 是个假flag。
法一:动态调试
接着往下看,可以看到encrypt,并且encrypt函数打开出错了。
可以判断是在encrypt处进行了SMC加密,随便下个断点,用 Local windows debugger调试
F7单步走到00401833的call处,
单步进入_Z7函数内
点击__Z7encryptPc 先用U将其设为无定义
接着选中下面所有数据内容
按C转换为代码。
之后再点击_Z7进行P定义为函数,
再用F5反编译得到encrypt函数
下面还有一个函数不要忽略了,
同样P定义为函数,
再F5反编译
先看第一段加密
unsigned char ida_chars[] =
{
0x0E, 0x0D, 0x09, 0x06, 0x13,
0x05, 0x58, 0x56, 0x3E, 0x06,
0x0C, 0x3C, 0x1F, 0x57, 0x14
0x6B, 0x57, 0x59, 0x0D
};
.rdata:00404012 Buffer db 'hahahaha_do_you_find_me?',0 ; DATA XREF: encrypt(char *)+3C↑o
exp
#include<iostream>
using namespace std;
int main() {
char Buffer[]= "hahahaha_do_you_find_me?";
char v2[] = {
0x0E, 0x0D, 0x09, 0x06, 0x13,
0x05, 0x58, 0x56, 0x3E, 0x06,
0x0C, 0x3C, 0x1F, 0x57, 0x14,
0x6B, 0x57, 0x59, 0x0D
};
char a1[19];
int i;
for(i=0; i<=18; i++) {
a1[i]=Buffer[i]^v2[i];
}
for(i=0; i<=18; i++)
cout<<a1[i];
return 0;
}
flag{d07abccf8a410c
明显少一段,那一定在第二部分
继续看
到这里,我没看懂这段代码的逻辑含义,但是它提示了:我把最后一部分藏起来,你不会成功的!!!
还记得前面用的异或加密算法吗,所以v3应该就是相同的方法继续加密后的五个密文字符,所以我们就需要知道他用什么key来加密的。前面用的是hahaha字符的前19个,这里我们继续用后面五个进行解密的话肯定不是flag。但是flag的最后一位肯定是},所以我们猜测是用某个数字和 '}' 异或,可以得到 ':' 这个字符。于是进一步猜测前面四个都是用相同的key加密的,于是我们解密:
true_flag=[]
v3 = [37, 116, 112, 38, 58]
key = ord('}') ^ 58
for i in range(5):
true_flag.append(chr(v3[i] ^ key))
print(''.join(true_flag))
b37a}
在Python中,字符串是不可变的对象,因此没有append
方法来添加字符。相反,您可以使用字符串拼接操作符+
或使用+=
运算符来构建新的字符串。
true_flag = ""
v3 = [37, 116, 112, 38, 58]
key = ord('}') ^ 58
for i in range(5):
true_flag += chr(v3[i] ^ key)
print(true_flag)
flag{d07abccf8a410cb37a}
法二:编写idc patch
然后按全选按C 再将红色部分按P
得到这两个从原来的程序还原的函数
这个函数同理
所以回到前面的问题,为什么调用前面的两个函数,会出现栈失衡的问题?
因为IDA在静态反汇编时,encrypt函数并没有解密,指令序列是乱的,IDA根据反汇编的结果进行栈的操作,自然在调用前后出现栈失衡的问题。
[羊城杯 2021]BabySmc
知道了相关函数,那我们就在IDA的导入表Import中看看有没有调用这些函数。
发现第一个就是,跟踪一下。
根据VirtualProtect()函数参数知道,lpAddress是进行修改的首地址处,qword_7FF6F613AD88则是末尾地址。
通过动态调试得到这两个地址具体是多少
然后分析一下代码可知,最关键的是ROR1操作,通过参阅资料这是循环位移的一个操作
所以代码中的ROR1则是循环位移三次在和0x5A异或。完成自修改。然后根据修改的起始地址看一下汇编代码。
发现这里有大量的数据,根据SMC特征基本可以判断这里就是SMC修改的地方。接下来就把他修改成正确的代码就好。
法一:动态调试
下断点
然后动态运行,之后f8运行,其中会有一个弹窗,是否让IDA自己根据RIP生成代码,这里选no,因为IDA不能区分主函数的结构,
然后从main函数头到第一个retn的代码段,因为IDA这时已经识别不了main函数块的结束部分了,然后右键->Analyze selected area选中Force强迫分析,并且不重新定义已存在的代码段,因为其它不用自修改的代码段都是正确的。
Force后点yes,在f5就可以看到正确的代码了
方法二:IDA的python脚本,静态修补
写python脚本把代码变为正确的,然后像上面一样,f8等等一系列操作。
写这个代码主要是用到循环位移ROR代码
from idc_bc695 import * #7.5版本使用,否则会出现无PatchByte
def ROR(i,index):
tmp = bin(i)[2:].rjust(8,"0")
for _ in range(index):
tmp = tmp[-1] + tmp[:-1]
return int(tmp, 2)
addr1=0x140001085
addr2=0x140001d00
for i in range(addr2-addr1): #循环次数
PatchByte(addr1+i,ROR(Byte(addr1+i),3)^90)
print('successful')
[NSSRound#2 Able]findxenny
在imports窗口看到SMC特征函数,判断有SMC
跟踪到sub_140018420函数
void *__fastcall sub_140018420(__int64 a1, __int64 a2, __int64 a3)
{
char *v3; // rdi
__int64 i; // rcx
void *result; // rax
char v6; // [rsp+0h] [rbp-20h] BYREF
char v7[10]; // [rsp+30h] [rbp+10h]
char v8[8]; // [rsp+3Ah] [rbp+1Ah] BYREF
char v9[94]; // [rsp+42h] [rbp+22h] BYREF
char v10[128]; // [rsp+A0h] [rbp+80h]
char v11[104]; // [rsp+120h] [rbp+100h]
void *lpAddress; // [rsp+188h] [rbp+168h]
void *v13; // [rsp+1A8h] [rbp+188h]
void *v14; // [rsp+1C8h] [rbp+1A8h]
int j; // [rsp+1E4h] [rbp+1C4h]
int k; // [rsp+204h] [rbp+1E4h]
int m; // [rsp+224h] [rbp+204h]
DWORD flOldProtect[101]; // [rsp+244h] [rbp+224h] BYREF
v3 = &v6;
for ( i = 250i64; i; --i )
{
*(_DWORD *)v3 = -858993460;
v3 += 4;
}
j___CheckForDebuggerJustMyCode(&unk_14002F034, a2, a3);
v7[0] = 23;
v7[1] = -36;
v7[2] = -77;
v7[3] = 31;
v7[4] = 23;
v7[5] = -42;
v7[6] = 83;
v7[7] = 123;
v7[8] = -104;
v7[9] = 27;
strcpy(v8, "{W07");
v8[5] = 38;
v8[6] = -104;
v8[7] = 27;
strcpy(v9, "{S0*");
v9[5] = 57;
v9[6] = -44;
v9[7] = 99;
v9[8] = 123;
v9[9] = -44;
v9[10] = 43;
v9[11] = 123;
v9[12] = 87;
v9[13] = 102;
v9[14] = -88;
v9[15] = 42;
v9[16] = 74;
v9[17] = -44;
v9[18] = 35;
v9[19] = 123;
v9[20] = 91;
v9[21] = -44;
v9[22] = 43;
v9[23] = 123;
v9[24] = 83;
v9[25] = 102;
v9[26] = -88;
v9[27] = 42;
v9[28] = 86;
v9[29] = 23;
v9[30] = -104;
v9[31] = -97;
v9[32] = 95;
v9[33] = 95;
v9[34] = 95;
v9[35] = 95;
v9[36] = -76;
v9[37] = 82;
v9[38] = 23;
v9[39] = -104;
v9[40] = -97;
v9[41] = 95;
v9[42] = 95;
v9[43] = 95;
v9[44] = 95;
v9[45] = 23;
v9[46] = -36;
v9[47] = -73;
v9[48] = 94;
v9[49] = -76;
v9[50] = 95;
v9[51] = 23;
v9[52] = -36;
v9[53] = -101;
v9[54] = 31;
v9[55] = -100;
v10[0] = 63;
v10[1] = -12;
v10[2] = -101;
v10[3] = 55;
v10[4] = 63;
v10[5] = -2;
v10[6] = 123;
v10[7] = 83;
v10[8] = -80;
v10[9] = 51;
v10[10] = 83;
v10[11] = 103;
v10[12] = 24;
v10[13] = 2;
v10[14] = 25;
v10[15] = 19;
v10[16] = -80;
v10[17] = 51;
v10[18] = 83;
v10[19] = 99;
v10[20] = 40;
v10[21] = 24;
v10[22] = 2;
v10[23] = 5;
v10[24] = 63;
v10[25] = -80;
v10[26] = -74;
v10[27] = 119;
v10[28] = 119;
v10[29] = 119;
v10[30] = 119;
v10[31] = 63;
v10[32] = -80;
v10[33] = -75;
v10[34] = 127;
v10[35] = 119;
v10[36] = 119;
v10[37] = 119;
v10[38] = 63;
v10[39] = 70;
v10[40] = -73;
v10[41] = 63;
v10[42] = 70;
v10[43] = -84;
v10[44] = -3;
v10[45] = 115;
v10[46] = 123;
v10[47] = -3;
v10[48] = 43;
v10[49] = 123;
v10[50] = 103;
v10[51] = 79;
v10[52] = -81;
v10[53] = 2;
v10[54] = 103;
v10[55] = 63;
v10[56] = -120;
v10[57] = -74;
v10[58] = 63;
v10[59] = 78;
v10[60] = -90;
v10[61] = 11;
v10[62] = -102;
v10[63] = 63;
v10[64] = 70;
v10[65] = -73;
v10[66] = 63;
v10[67] = -12;
v10[68] = -77;
v10[69] = 55;
v10[70] = -76;
v10[71] = 63;
v10[72] = -80;
v10[73] = -73;
v10[74] = 119;
v10[75] = 119;
v10[76] = 119;
v10[77] = 119;
v10[78] = 63;
v10[79] = -12;
v10[80] = -97;
v10[81] = 118;
v10[82] = 63;
v10[83] = -12;
v10[84] = -77;
v10[85] = 55;
v10[86] = -76;
v11[0] = 46;
v11[1] = -27;
v11[2] = -118;
v11[3] = 70;
v11[4] = 46;
v11[5] = -17;
v11[6] = 106;
v11[7] = 66;
v11[8] = 39;
v11[9] = -36;
v11[10] = 65;
v11[11] = 72;
v11[12] = 97;
v11[13] = 26;
v11[14] = 39;
v11[15] = -25;
v11[16] = -108;
v11[17] = 30;
v11[18] = 48;
v11[19] = 82;
v11[20] = 116;
v11[21] = -19;
v11[22] = 90;
v11[23] = 66;
v11[24] = 34;
v11[25] = 95;
v11[26] = -79;
v11[27] = 19;
v11[28] = 120;
v11[29] = -19;
v11[30] = 26;
v11[31] = 66;
v11[32] = 98;
v11[33] = 39;
v11[34] = -36;
v11[35] = 41;
v11[36] = 92;
v11[37] = 3;
v11[38] = -31;
v11[39] = 39;
v11[40] = -25;
v11[41] = -108;
v11[42] = 71;
v11[43] = 37;
v11[44] = 3;
v11[45] = -31;
v11[46] = 34;
v11[47] = 95;
v11[48] = -79;
v11[49] = 19;
v11[50] = 110;
v11[51] = 46;
v11[52] = 87;
v11[53] = -90;
v11[54] = 46;
v11[55] = -27;
v11[56] = -94;
v11[57] = 70;
v11[58] = -91;
v11[59] = 46;
v11[60] = -27;
v11[61] = -94;
v11[62] = 70;
v11[63] = 46;
v11[64] = 87;
v11[65] = -90;
v11[66] = 46;
v11[67] = -27;
v11[68] = -114;
v11[69] = 103;
v11[70] = -91;
lpAddress = malloc(0x4Aui64);
v13 = malloc(0x57ui64);
v14 = malloc(0x47ui64);
j_memset(lpAddress, 0, 0x4Aui64);
j_memset(v13, 0, 0x57ui64);
j_memset(v14, 0, 0x47ui64);
for ( j = 0; (unsigned __int64)j < 0x4A; ++j )
*((_BYTE *)lpAddress + j) = v7[j] ^ 0x5F;
for ( k = 0; (unsigned __int64)k < 0x57; ++k )
*((_BYTE *)v13 + k) = v10[k] ^ 0x77;
for ( m = 0; (unsigned __int64)m < 0x47; ++m )
*((_BYTE *)v14 + m) = v11[m] ^ 0x66;
flOldProtect[0] = 0;
VirtualProtect(lpAddress, 0x1000ui64, 0x40u, flOldProtect);
VirtualProtect(v13, 0x1000ui64, 0x40u, flOldProtect);
VirtualProtect(v14, 0x1000ui64, 0x40u, flOldProtect);
qword_140029370 = (__int64 (__fastcall *)(_QWORD))lpAddress;
qword_140029378 = (__int64 (__fastcall *)(_QWORD))v13;
result = v14;
qword_140029380 = (__int64 (__fastcall *)(_QWORD))v14;
return result;
}
需要先求三个函数的地址
法一:动态调试
还没装~(sorry~
法二:加密函数手动解密
缺相关.dll文件,无法动调
v11=[0x2E,0xE5,0x8A,0x46,0x2E,0xEF,0x6A,0x42,0x27,0xDC,
0x41,0x48,0x61,0x1A,0x27,0xE7,0x94,0x1E,0x30,0x52,
0x74,0xED,0x5A,0x42,0x22,0x5F,0xB1,0x13,0x78,0xED,
0x1A,0x42,0x62,0x27,0xDC,0x29,0x5C,3,0xE1,0x27,
0xE7,0x94,0x47,0x25,3,0xE1,0x22,0x5F,0xB1,0x13,
0x6E,0x2E,0x57,0xA6,0x2E,0xE5,0xA2,0x46,0xA5,0x2E,
0xE5,0xA2,0x46,0x2E,0x57,0xA6,0x2E,0xE5,0x8E,0x67,
0xA5]
v10=[0x3F,0xF4,0x9B,0x37,0x3F,0xFE,0x7B,0x53,0xB0,0x33,
0x53,0x67,0x18,0x02,0x19,0x13,0xB0,0x33,0x53,0x63,
0x28,0x18,2,5,0x3F,0xB0,0xB6,0x77,0x77,0x77,
0x77,0x3F,0xB0,0xB5,0x7F,0x77,0x77,0x77,0x3F,0x46,
0xB7,0x3F,0x46,0xAC,0xFD,0x73,0x7B,0xFD,0x2B,0x7B,
0x67,0x4F,0xAF,2,0x67,0x3F,0x88,0xB6,0x3F,0x4E,
0xA6,0xB,0x9A,0x3F,0x46,0xB7,0x3F,0xF4,0xB3,0x37,
0xB4,0x3F,0xB0,0xB7,0x77,0x77,0x77,0x77,0x3F,0xF4,
0x9F,0x76,0x3F,0xF4,0xB3,0x37,0xB4]
v7=[0x17,0xDC,0xB3,0x1F,0x17,0xD6,0x53,0x7B,0x98,0x1B,
0x7B,0x57,0x30,0x37,0x00,0x26,0x98,0x1B,0x7B,0x53,
0x30,0x2A,0x00,0x39,0xD4,0x63,0x7B,0xD4,0x2B,0x7B,
0x57,0x66,0xA8,0x2A,0x4A,0xD4,0x23,0x7B,0x5B,0xD4,
0x2B,0x7B,0x53,0x66,0xA8,0x2A,0x56,0x17,0x98,0x9F,
0x5F,0x5F,0x5F,0x5F,0xB4,0x52,0x17,0x98,0x9F,0x5F,
0x5F,0x5F,0x5F,0x17,0xDC,0xB7,0x5E,0xB4,0x5F,0x17,
0xDC,0x9B,0x1F,0x9C]
lpAddress=[]
v13=[]
v14=[]
# 对v7进行异或操作并添加到lpAddress列表
for i in range(len(v7)):
lpAddress.append(hex(v7[i] ^ 0x5F))
# 对v10进行异或操作并添加到v13列表
for k in range(len(v10)):
v13.append(hex(v10[k] ^ 0x77))
# 对v11进行异或操作并添加到v14列表
for m in range(len(v11)):
v14.append(hex(v11[m] ^ 0x66))
# 将三个列表合并并打印
result = lpAddress + v13 + v14
print(' '.join(result))
for i in range(74):
lpAddress[i] = hex(v7[i] ^ 0x5F)
for k in range(87):
v13[k] = hex(v10[k] ^ 0x77)
for m in range(71):
v14[m] = hex(v11[m] ^ 0x66)
print(lpAddress + v13 + v14)
在Python中,不能通过索引向一个空列表赋值,是因为索引赋值的前提是列表中已经有了足够的元素。换句话说,列表的索引赋值操作是替换而非新增。如果尝试对一个不存在的索引位置进行赋值,Python就会抛出IndexError
错误,因为它找不到要替换的元素。
正确的添加元素到列表的方法是使用.append()
方法(添加到列表末尾)、.insert()
方法(插入到指定位置)、或者使用.extend()
方法(用于添加另一个集合中的多个元素)。
这些方法都可以在不关心当前列表长度的情况下添加元素,而不会引发索引相关的错误。
得到
将解密的十六进制数据保存为一个二进制文件(而不是文本文件)。
这可以通过编程(如使用Python脚本)或者使用一些十六进制编辑工具来完成。
在IDA中,你可以通过“File”->“Load file”->“Binary file...”来导入这个二进制文件。
这里我们使用十六进制编辑工具010Editor
另存为1.exe文件
拖入IDA分析
红色部分按P键转换成函数,然后就可以按F5反编译了
可以看到三个函数
将三个函数的字符串提取出来即是flag
NSSCTF{oh_you_found_our_x3nny}
[HGAME 2023 week3]patchme
不会pwn的re手不是一个好CTFer!游戏规则:修复程序中的二进制安全漏洞,要求能严格执行原程序的正常功能且不变动文件大小,如果修复成功,在运行后输入任何内容即可输出flag。
hint
使用ida打开,看到一个可疑函数对文件地址进行操作,怀疑是smc文件加密技术。(注意在 linux系统 中是通过 mprotect函数修改目标内存的权限
可以看到,mprotect修改的内存区域与之后的加密区域是重合的
所以下面的加密操作其实是对内存也就是text进行的,解法有对text段操作,或者有shellcode
法一:idapython脚本解密
import idc
addr = 0x14C6 #起始地址
for i in range(961):
idc.patch_byte(addr+i, idc.get_wide_byte(i+addr) ^ 0x66)
然后有单独的一个数据按C,再在sub_14C6()函数头头按P创建函数体,F5即可
静态分析代码
int sub_14C6()
{
int result; // eax
__WAIT_STATUS stat_loc; // [rsp+Ch] [rbp-2C4h] BYREF
int i; // [rsp+14h] [rbp-2BCh]
__int64 v3; // [rsp+18h] [rbp-2B8h]
int pipedes[2]; // [rsp+20h] [rbp-2B0h] BYREF
int v5[2]; // [rsp+28h] [rbp-2A8h] BYREF
char *argv[4]; // [rsp+30h] [rbp-2A0h] BYREF
char v7[48]; // [rsp+50h] [rbp-280h] BYREF
__int64 v8; // [rsp+80h] [rbp-250h]
__int64 v9[5]; // [rsp+E0h] [rbp-1F0h]
int v10; // [rsp+108h] [rbp-1C8h]
__int16 v11; // [rsp+10Ch] [rbp-1C4h]
char v12; // [rsp+10Eh] [rbp-1C2h]
__int64 v13[5]; // [rsp+110h] [rbp-1C0h]
int v14; // [rsp+138h] [rbp-198h]
__int16 v15; // [rsp+13Ch] [rbp-194h]
char v16; // [rsp+13Eh] [rbp-192h]
char buf[80]; // [rsp+140h] [rbp-190h] BYREF
char s1[8]; // [rsp+190h] [rbp-140h] BYREF
__int64 v19; // [rsp+198h] [rbp-138h]
char v20[280]; // [rsp+1A0h] [rbp-130h] BYREF
int v21; // [rsp+2B8h] [rbp-18h]
unsigned __int64 v22; // [rsp+2C8h] [rbp-8h]
v22 = __readfsqword(0x28u);
result = dword_4028;
if ( dword_4028 <= 1 )
{
pipe(pipedes);
pipe(v5);
if ( fork() )
{
close(pipedes[0]);
close(v5[1]);
HIDWORD(stat_loc.__iptr) = 0;
while ( SHIDWORD(stat_loc.__iptr) <= 35 )
{
buf[2 * HIDWORD(stat_loc.__iptr)] = 37;
buf[2 * HIDWORD(stat_loc.__iptr)++ + 1] = 110;
}
buf[72] = 10;
buf[73] = 0;
write(pipedes[1], buf, 0x4AuLL);
*(_QWORD *)s1 = 0LL;
v19 = 0LL;
memset(v20, 0, sizeof(v20));
v21 = 0;
read(v5[0], s1, 0x12CuLL);
wait((__WAIT_STATUS)&stat_loc);
buf[23] = 0;
if ( !LODWORD(stat_loc.__uptr) && !strcmp(s1, buf) )
{
v9[0] = 0x5416D999808A28FALL;
v9[1] = 0x588505094953B563LL;
v9[2] = 0xCE8CF3A0DC669097LL;
v9[3] = 0x4C5CF3E854F44CBDLL;
v9[4] = 0xD144E49916678331LL;
v10 = -631149652;
v11 = -17456;
v12 = 85;
v13[0] = 0x3B4FA2FCEDEB4F92LL;
v13[1] = 0x7E45A6C3B67EA16LL;
v13[2] = 0xAFE1ACC8BF12D0E7LL;
v13[3] = 0x132EC3B7269138CELL;
v13[4] = 0x8E2197EB7311E643LL;
v14 = -1370223935;
v15 = -13899;
v16 = 40;
result = putchar(10);
for ( i = 0; i <= 46; ++i )
result = putchar((char)(*((_BYTE *)v9 + i) ^ *((_BYTE *)v13 + i)));
}
else
{
return puts("\\nthere are still bugs...");
}
}
else
{
fflush(stdin);
sub_1AB0(*(_QWORD *)qword_4020, v7);
v3 = v8;
if ( v8 == 14472 )
{
close(pipedes[1]);
close(v5[0]);
dup2(pipedes[0], 0);
dup2(v5[1], 1);
dup2(v5[1], 2);
argv[0] = *(char **)qword_4020;
argv[1] = "1";
argv[2] = 0LL;
return execve(*(const char **)qword_4020, argv, 0LL);
}
else
{
puts("\\nyou cannot modify the file size");
return 0;
}
}
}
return result;
}
加密就是简单异或
exp
#include<stdio.h>
int main()
{
long long v9[6];
long long v10[6];
v9[0] = 0x5416D999808A28FALL;
v9[1] = 0x588505094953B563LL;
v9[2] = 0xCE8CF3A0DC669097LL;
v9[3] = 0x4C5CF3E854F44CBDLL;
v9[4] = 0xD144E49916678331LL;
v9[5] = 0x55BBD0DA616BAC;
v10[0] = 0x3B4FA2FCEDEB4F92LL;
v10[1] = 0x7E45A6C3B67EA16LL;
v10[2] = 0xAFE1ACC8BF12D0E7LL;
v10[3] = 0x132EC3B7269138CELL;
v10[4] = 0x8E2197EB7311E643LL;
v10[5] = 0x28C9B5AE540AC1;
for (int i = 0; i <= 46; i++) {
putchar(*((char*)v9 + i) ^ *((char*)v10 + i));
}
return 0;
}//hgame{You_4re_a_p@tch_master_0r_reverse_ma5ter}
六、参考文章
https://bbs.kanxue.com/thread-271790.htm#一.网鼎杯2020青龙组jocker
https://zhuanlan.zhihu.com/p/66797526
CTF Reverse逆向学习之SMC动态代码加密技术,题目复现(NSSCTF)([网鼎杯 2020 青龙组]jocker)_逆向 smc-优快云博客
[原创]smc自修改代码相关题目-- 网鼎杯2020青龙组jocker-软件逆向-看雪-安全社区|安全招聘|kanxue.com