我最近在做一个关于shellcode入门和开发的专题课👩🏻💻,主要面向对网络安全技术感兴趣的小伙伴。这是视频版内容对应的文字版材料,内容里面的每一个环境我都亲自测试实操过的记录,有需要的小伙伴可以参考。
我的个人主页:https://imbyter.com
在第二种shellcode编写实战(1)中实现了以C语言或C++类的方式完成多文件shellcode编写的框架,在第二种shellcode编写实战(2)中实现了以CAPI类处理函数动态调用的方式,使shellcode结构更加清晰。接下来处理最后一个问题,解决双引号字符串使用的问题。
有两种方式供选择:
- 我们可以直接以类似char szData = {'x','x','x',0};的方式去处理使用每一个字符串,优点是每个字符串的使用可控,缺点是编写比较麻烦。
- 可以使用ADVobfuscator开源项目,自动化处理所有用到的字符串,优点是编写简单,效率高。
在前面的编程中,我们都是使用的第一种方式。接下来我们尝试使用ADVobfuscator来提高shellcode字符换处理效率。
ADVobfuscator是Github中一个能够实现自动化处理字符串的开源项目,地址为:GitHub - andrivet/ADVobfuscator: Obfuscation library based on C++11/14 and metaprogramming
该项目通过提供的两个字符串混淆加密接口,能够非常简单高效的对指定的字符串进行加密混淆,使用方便,处理效果好。经实测具备以下几个特点:
- 默认仅在发布版(Release)编译时开启字符串混淆,调试版(Debug)不会启用;
- 对全局字符串和局部字符串均能有效处理;
- 每次编译均使用随机密钥对字符串进行加密;(即对同一个项目连续编译两次,每次生成的PE中字符串的加密结果都不相同)
- 正常情况下使用的明文字符串编译后存放在PE结构中的数据段中,此代码处理过的字符串会被加密存放在代码段中,更加隐蔽;
我们在原版的基础上做了适当的整合,使用时只需要包含一个obf.h文件即可:
obf.h:
#pragma once// reference https://github.com/andrivet/ADVobfuscator#if defined(_MSC_VER)
#define ALWAYS_INLINE __forceinline
#else
#define ALWAYS_INLINE __attribute__((always_inline))
#endif#include <iomanip>
#include <iostream>// std::index_sequence will be available with C++14 (C++1y). For the moment, implement a (very) simplified and partial version. You can find more complete versions on the Internet
// MakeIndex<N>::type generates Indexes<0, 1, 2, 3, ..., N>namespace andrivet {namespace ADVobfuscator {template<int... I>struct Indexes { using type = Indexes<I..., sizeof...(I)>; };template<int N>struct Make_Indexes { using type = typename Make_Indexes<N - 1>::type::type; };template<>struct Make_Indexes<0> { using type = Indexes<>; };}
}// Very simple compile-time random numbers generator.// For a more complete and sophisticated example, see:
// http://www.researchgate.net/profile/Zalan_Szgyi/publication/259005783_Random_number_generator_for_C_template_metaprograms/file/e0b49529b48272c5a6.pdf#include <random>namespace andrivet {namespace ADVobfuscator {namespace{// I use current (compile time) as a seedconstexpr char time[] = __TIME__; // __TIME__ has the following format: hh:mm:ss in 24-hour time// Convert time string (hh:mm:ss) into a numberconstexpr int DigitToInt(char c) { return c - '0'; }const int seed = DigitToInt(time[7]) +DigitToInt(time[6]) * 10 +DigitToInt(time[4]) * 60 +DigitToInt(time[3]) * 600 +DigitToInt(time[1]) * 3600 +DigitToInt(time[0]) * 36000;}// 1988, Stephen Park and Keith Miller// "Random Number Generators: Good Ones Are Hard To Find", considered as "minimal standard"// Park-Miller 31 bit pseudo-random number generator, implemented with G. Carta's optimisation:// with 32-bit math and without divisiontemplate<int N>struct MetaRandomGenerator{private:static constexpr unsigned a = 16807; // 7^5static constexpr unsigned m = 2147483647; // 2^31 - 1static constexpr unsigned s = MetaRandomGenerator<N - 1>::value;static constexpr unsigned lo = a * (s & 0xFFFF); // Multiply lower 16 bits by 16807static constexpr unsigned hi = a * (s >> 16); // Multiply higher 16 bits by 16807static constexpr unsigned lo2 = lo + ((hi & 0x7FFF) << 16); // Combine lower 15 bits of hi with lo's upper bitsstatic constexpr unsigned hi2 = hi >> 15; // Discard lower 15 bits of histatic constexpr unsigned lo3 = lo2 + hi;public:static constexpr unsigned max = m;static constexpr unsigned value = lo3 > m ? lo3 - m : lo3;};template<>struct MetaRandomGenerator<0>{static constexpr unsigned value = seed;};// Note: A bias is introduced by the modulo operation.// However, I do belive it is neglictable in this case (M is far lower than 2^31 - 1)template<int N, int M>struct MetaRandom{static const int value = MetaRandomGenerator<N + 1>::value % M;};}
}namespace andrivet {namespace ADVobfuscator {struct HexChar{unsigned char c_;unsigned width_;HexChar(unsigned char c, unsigned width) : c_{ c }, width_{ width } {}};inline std::ostream& operator<<(std::ostream& o, const HexChar& c){return (o << std::setw(c.width_) << std::setfill('0') << std::hex << (int)c.c_ << std::dec);}inline HexChar hex(char c, int w = 2){return HexChar(c, w);}}
}namespace andrivet {namespace ADVobfuscator {// Represents an obfuscated string, parametrized with an alrorithm number N, a list of indexes Indexes and a key Keytemplate<int N, char Key, typename Indexes>struct MetaString;// Partial specialization with a list of indexes I, a key K and algorithm N = 0// Each character is encrypted (XOR) with the same keytemplate<char K, int... I>struct MetaString<0, K, Indexes<I...>>{// Constructor. Evaluated at compile time.constexpr ALWAYS_INLINE MetaString(const char* str): key_{ K }, buffer_{ encrypt(str[I], K)... } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i]);buffer_[sizeof...(I)] = 0;//LOG("--- Implementation #" << 0 << " with key 0x" << hex(key_));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the keyconstexpr char key() const { return key_; }constexpr char ALWAYS_INLINE encrypt(char c, int k) const { return c ^ k; }constexpr char decrypt(char c) const { return encrypt(c, key()); }volatile int key_; // key. "volatile" is important to avoid uncontrolled over-optimization by the compilervolatile char buffer_[sizeof...(I) + 1]; // Buffer to store the encrypted string + terminating null byte};// Partial specialization with a list of indexes I, a key K and algorithm N = 1// Each character is encrypted (XOR) with an incremented key.template<char K, int... I>struct MetaString<1, K, Indexes<I...>>{// Constructor. Evaluated at compile time.constexpr ALWAYS_INLINE MetaString(const char* str): key_(K), buffer_{ encrypt(str[I], I)... } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i], i);buffer_[sizeof...(I)] = 0;//LOG("--- Implementation #" << 1 << " with key 0x" << hex(key_));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the keyconstexpr char key(size_t position) const { return static_cast<char>(key_ + position); }constexpr char ALWAYS_INLINE encrypt(char c, size_t position) const { return c ^ key(position); }constexpr char decrypt(char c, size_t position) const { return encrypt(c, position); }volatile int key_; // key. "volatile" is important to avoid uncontrolled over-optimization by the compilervolatile char buffer_[sizeof...(I) + 1]; // Buffer to store the encrypted string + terminating null byte};// Partial specialization with a list of indexes I, a key K and algorithm N = 2// Shift the value of each character and does not store the key. It is only used at compile-time.template<char K, int... I>struct MetaString<2, K, Indexes<I...>>{// Constructor. Evaluated at compile time. Key is *not* storedconstexpr ALWAYS_INLINE MetaString(const char* str): buffer_{ encrypt(str[I])..., 0 } { }// Runtime decryption. Most of the time, inlinedinline const char* decrypt(){for (size_t i = 0; i < sizeof...(I); ++i)buffer_[i] = decrypt(buffer_[i]);//LOG("--- Implementation #" << 2 << " with key 0x" << hex(K));return const_cast<const char*>(buffer_);}private:// Encrypt / decrypt a character of the original string with the key// Be sure that the encryption key is never 0.constexpr char key(char key) const { return 1 + (key % 13); }constexpr char ALWAYS_INLINE encrypt(char c) const { return c + key(K); }constexpr char decrypt(char c) const { return c - key(K); }// Buffer to store the encrypted string + terminating null byte. Key is not storedvolatile char buffer_[sizeof...(I) + 1];};// Helper to generate a keytemplate<int N>struct MetaRandomChar{// Use 0x7F as maximum value since most of the time, char is signed (we have however 1 bit less of randomness)static const char value = static_cast<char>(1 + MetaRandom<N, 0x7F - 1>::value);};}
}// Prefix notation
//#define DEF_OBFUSCATED(str) andrivet::ADVobfuscator::MetaString<andrivet::ADVobfuscator::MetaRandom<__COUNTER__, 3>::value, andrivet::ADVobfuscator::MetaRandomChar<__COUNTER__>::value, andrivet::ADVobfuscator::Make_Indexes<sizeof(str) - 1>::type>(str)
//#define OBFUSCATED(str) (DEF_OBFUSCATED(str).decrypt())#define DEF(str) andrivet::ADVobfuscator::MetaString<andrivet::ADVobfuscator::MetaRandom<__COUNTER__, 3>::value, andrivet::ADVobfuscator::MetaRandomChar<__COUNTER__>::value, andrivet::ADVobfuscator::Make_Indexes<sizeof(str) - 1>::type>(str)
#define O(str) (DEF(str).decrypt())
对于上述代码,表面看使用了双引号字符串,实际上在编译时对代码中O("xxx")的内容都是以宏定义的方式进行了转换处理,生成的PE文件中并没有双引号字符串的直接暴露,既符合shellcode编写规范,又一定程度提高了开发效率。
注意
使用ADVobfuscator来自动处理双引号字符串时,不能禁用项目“优化”选项,即工程 “属性”--->“C/C++”--->“优化”中,“优化”选项可以选择“/O1”、“/O2”,或者“/Ox”,不能使用“已禁用 (/Od)”,否则字符换处理无效。
如果有任何问题,可以在我们的知识社群中提问和沟通交流:
一个人走得再快,不如一群人走得更远!🤜🤛