This page looks best with JavaScript enabled

VS编译Shellcode(DLL脱库)

 ·  ☕ 2 min read · 👀... views

有的时候,因为一些特殊目的,需要编译不带导入表甚至无PE头和初始化函数的shellcode。这里记录两种方法。

纯shellcode

当shellcode比较短的时候,可以通过函数实现+输出机器码的方式得到纯shellcode。方法是先正常写一个exe,将shellcode的函数全部写在里面,exe的main函数中输出这部分内容的机器码形成shellcode。这种方法比较麻烦一些,也有一些注意点:

首先是shellcode起始和末尾部分的界明和入口点的处理。比较简单的方法是定义两个标志函数,将shellcode部分的函数定义在其中,然后输出标志函数之间的机器码。

1
2
3
4
5
void shellcode_begin(){}

// Other Shellcode Funtcions

void shellcode_end() {}

但是这种方法入口点就得手动处理了。所以有更高级的方法,在桩函数里call入口点。MalCode函数就是shellcode的入口点函数。输出shellcode时只需要输出MalCodeBegin函数和MalCodeEnd函数之间的机器码即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#ifndef _WIN64
	__declspec(naked) void MalCodeBegin() { __asm { jmp MalCode } };
#else
	void MalCodeBegin() { reinterpret_cast<void(__stdcall*)()>(MalCode)(); }
#endif

// Shellcode Funtcions

#ifndef _WIN64
	__declspec(naked) void MalCodeEnd() { };
#else
	void MalCodeEnd() {};
#endif

然后这样生成的shellcode还需要调整编译参数。

  1. C/C++ -> Optimization -> /O1, /Ob2, /Oi, /Os, /Oy-, /GL
  2. C/C++ -> Code Generation -> /MT, /GS-, /Gy
  3. Linker -> General -> /INCREMENTAL:NO

最后在main函数里dump输出即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
std::string buffer = "static const unsigned char Shellcode[] = {\n";
auto start = (DWORD_PTR)(&MalCodeBegin);
auto end = (DWORD_PTR)(&MalCodeEnd);;
int num = 0;
for (DWORD_PTR i = start; i < end; i++)
{
    char Temp[10] = "";

    if (i == end - 1)
        sprintf_s(Temp, "0x%0.2x", *(BYTE*)i);
    else
        sprintf_s(Temp, "0x%0.2x ,", *(BYTE*)i);
    buffer += Temp;
    num++;
    if ((num % 15) == 0 && num != 0)
        buffer += "\n";
}
buffer += "};";
std::ofstream fs(output_h, std::ios::ate | std::ios::out);
fs << buffer;
fs.close();

具有PE结构的无导入表DLL式shellcode

上面的方法确实是能输出纯粹的shellcode,但局限性很多。首先是编译起来很麻烦,要先实现到exe里再输出一个.bin再使用,而且若是代码比较复杂,多.c文件下就更难处理了。所以更多时候是希望能编译出具有动态链接库(DLL)PE结构但无导入表的shellcode。

首先正常创建一个动态链接库项目,然后配置Release项目属性

  1. C/C++ -> Code Generation -> /MT, /GS-, /Gy
  2. Linker -> General -> /INCREMENTAL:NO
  3. Linker -> Input -> /NODEFAULTLIB, 删除所有附加依赖项
  4. Linker -> Advance -> /ENTRY=DllMain

这样配置后,是无法使用所有API的,需要自己从PEB里通过getProcAddrByHash干出API函数地址使用。

Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer