shellcode学习

本文最后更新于:2021-08-09 晚上

shellcode学习

编译器的一些设置

第一步修改入口点

在编译器中修改程序的入口点,写代码时就可以使用新的入口点名

修改过后,体积变得很小,使用IDA查看也可以看到左边函数只有两个。

第二步关闭缓冲区安全检查

然后查看IDA,左边函数只剩一个了

第三步设置工程兼容XP

修改运行库为MT

第四步关闭生成清单

使用loadpe查看看到只有两个区段

第五步关闭调试信息

shellcode编写原则1

  • 杜绝双引号字符串的直接使用
  • 关闭VS自动优化没有使用到的变量
  • 自定义函数入口

#pragma comment(linker,”/entry:EntryMain”)

shellcode编写原则2

动态获取函数地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <Windows.h>

#pragma comment(linker,"/entry:EntryMain")

int EntryMain() {
typedef int (WINAPI* FN_MessageBoxA)(
__in_opt HWND hWnd,
__in_opt LPCSTR lpText,
__in_opt LPCSTR lpCaption,
__in UINT uType);
FN_MessageBoxA fn_MessageBoxA;
fn_MessageBoxA = (FN_MessageBoxA)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
fn_MessageBoxA(NULL, "b1ackie", "hhh", NULL);

return 0;
}

shellcode编写原则3

获取kernel32.dll基址和GetProcAddress地址获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(linker,"/entry:EntryMain")
#pragma comment(lib, "ucrtd.lib")
//#pragma comment(lib, "msvcrtd.lib")
//#pragma comment(lib, "vcruntimed.lib")
_declspec(naked) DWORD getKernel32() {
__asm {
mov eax, fs: [30h] //获取PEB
mov eax, [eax + 0Ch] //获取_PEB_LDR_DATA
mov eax, [eax + 14h] //InMemoryOrderModuleList,
mov eax, [eax] //程序自身
mov eax, [eax] //ntdll.dll
mov eax,[eax+10h] //kernel.dll,偏移10H是地址
ret
}
}
FARPROC _GetProcAddress(HMODULE hModule) {



PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

PIMAGE_EXPORT_DIRECTORY lpExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader +
(DWORD)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + lpExport->AddressOfNames);
PWORD lpAddressOfNameOrdinalArray = (PWORD)((DWORD)pDosHeader + lpExport->AddressOfNameOrdinals);
PDWORD lpAddressOfFuncArray = (PDWORD)((DWORD)pDosHeader + lpExport->AddressOfFunctions);
DWORD dwNumber = lpExport->NumberOfNames;
DWORD wHint = 0;
FARPROC lpFunc;
for (DWORD i = 0; i < dwNumber; i++) {
char *lpFuncName = (char*)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);
if (lpFuncName[0] == 'G'&&
lpFuncName[1] == 'e'&&
lpFuncName[2] == 't'&&
lpFuncName[3] == 'P'&&
lpFuncName[4] == 'r'&&
lpFuncName[5] == 'o'&&
lpFuncName[6] == 'c'&&
lpFuncName[7] == 'A'&&
lpFuncName[8] == 'd'&&
lpFuncName[9] == 'd'&&
lpFuncName[10] == 'r'&&
lpFuncName[11] == 'e'&&
lpFuncName[12] == 's'&&
lpFuncName[13] == 's') {
wHint = lpAddressOfNameOrdinalArray[i];
lpFunc = (FARPROC)((DWORD)pDosHeader + lpAddressOfFuncArray[wHint]);
break;
}
}
return lpFunc;
//PWORD
}

int EntryMain() {
HMODULE hAddr = (HMODULE)getKernel32();
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
FN_GetProcAddress fn_GetProcAddress;
fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(hAddr);
return 0;

}

shellcode编写原则4

  • 避免全局变量的使用

  • 确保已加载所使用的API的动态链接库

第一种shellcode生成框架

通过上述操作直接编写,编写一个拥有弹窗功能的shellcode

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <Windows.h>

DWORD getKernel32();
FARPROC _GetProcAddress(HMODULE hModule);
int EntryMain() {
HMODULE hAddr = (HMODULE)getKernel32();
typedef FARPROC(WINAPI* FN_GetProcAddress)(
_In_ HMODULE hModule,
_In_ LPCSTR lpProcName
);
FN_GetProcAddress fn_GetProcAddress;
fn_GetProcAddress = (FN_GetProcAddress)_GetProcAddress(hAddr);
typedef HMODULE(WINAPI* FN_LoadLibraryA)(
_In_ LPCSTR lpLibFileName);
char szLoadLibraryA[] = { 'L','o','a','d','L','i','b','r','a','r','y','A',0 };
FN_LoadLibraryA fn_LoadLibraryA = (FN_LoadLibraryA)fn_GetProcAddress(hAddr, szLoadLibraryA);
char szMessageBoxA[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'A', 0 };
typedef int(WINAPI* FN_MessageBoxA)(
_In_opt_ HWND hWnd,
_In_opt_ LPCSTR lpText,
_In_opt_ LPCSTR lpCaption,
_In_ UINT uType);
char szUser32[] = { 'U','s','e','r','3','2','.','d','l','l',0 };
FN_MessageBoxA fn_MessageBoxA = (FN_MessageBoxA)fn_GetProcAddress(fn_LoadLibraryA(szUser32), szMessageBoxA);
char szHello[] = { 'b','1','a','c','k','i','e','!',0 };
char szTitle[] = { 't','e','s','t',0 };
fn_MessageBoxA(NULL, szHello, szTitle, NULL);
return 0;
}
_declspec(naked) DWORD getKernel32() {
__asm {
mov eax, fs: [30h] //获取PEB
mov eax, [eax + 0Ch] //获取_PEB_LDR_DATA
mov eax, [eax + 14h] //InMemoryOrderModuleList,
mov eax, [eax] //程序自身
mov eax, [eax] //ntdll.dll
mov eax, [eax + 10h] //kernel.dll,偏移10H是地址
ret
}
}

FARPROC _GetProcAddress(HMODULE hModule) {
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY lpExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader +
(DWORD)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD lpAddressOfNamesArray = (PDWORD)((DWORD)pDosHeader + lpExport->AddressOfNames);
PWORD lpAddressOfNameOrdinalArray = (PWORD)((DWORD)pDosHeader + lpExport->AddressOfNameOrdinals);
PDWORD lpAddressOfFuncArray = (PDWORD)((DWORD)pDosHeader + lpExport->AddressOfFunctions);
DWORD dwNumber = lpExport->NumberOfNames;
DWORD wHint = 0;
FARPROC lpFunc;
for (DWORD i = 0; i < dwNumber; i++) {
char* lpFuncName = (char*)((DWORD)pDosHeader + lpAddressOfNamesArray[i]);
if (lpFuncName[0] == 'G' &&
lpFuncName[1] == 'e' &&
lpFuncName[2] == 't' &&
lpFuncName[3] == 'P' &&
lpFuncName[4] == 'r' &&
lpFuncName[5] == 'o' &&
lpFuncName[6] == 'c' &&
lpFuncName[7] == 'A' &&
lpFuncName[8] == 'd' &&
lpFuncName[9] == 'd' &&
lpFuncName[10] == 'r' &&
lpFuncName[11] == 'e' &&
lpFuncName[12] == 's' &&
lpFuncName[13] == 's') {
wHint = lpAddressOfNameOrdinalArray[i];
lpFunc = (FARPROC)((DWORD)pDosHeader + lpAddressOfFuncArray[wHint]);
break;
}
}
return lpFunc;
}

然后使用PEID查看偏移,可以看到是200,

然后使用十六进制编辑器将其中的机器码拷贝出来。

选取一个替代的程序,查看其偏移

然后进入编辑器,从偏移开始粘贴我们的机器码

然后打开这个程序就会实现shellcode编写的弹窗效果了

第二种shellcode生成框架

单文件的函数生成位置规律

单文件的函数生成规律,与函数实现的先后顺序有关,与定义的顺序无关。

如这样一个程序,定义的顺序是先A,后B

在IDA中可以看到顺序是先B后A。

多文件生成规律

与包含的文件位置无关,与实际调用顺序有关。

在文件中的.vcxproj文件,如图此时是这个顺序,可以看到编译顺序一致。

修改一下顺序,编译顺序也会改变

实际编写

在其中定义几个文件

  • 0.entry.cpp:入口点
  • a.start.cpp:shellcode执行
  • z.end.cpp:shellcode结束

a-z之间可以放shellcode的所有功能的具体实现。在0.entry中写创建文件,根据文件的生成规律,可以知道文件的大小就是a.start.cpp中的ShellcodeEnd - z.end.cpp中的ShellcodeStart。

1
2
3
4
5
HANDLE hBin = CreateFileA("sh.bin", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwSize = (DWORD)ShellcodeEnd - (DWORD)ShellcodeStart;
DWORD dwWrite;
WriteFile(hBin, ShellcodeStart, dwSize, &dwWrite, NULL);
CloseHandle(hBin);
shellcode加载器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <Windows.h>
#include <stdio.h>

int main(int argc,char* argv[])
{
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("open failed\n");
return -1;
}
DWORD dwSize;
dwSize = GetFileSize(hFile, NULL);
LPVOID lpAddr = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL) {
printf("virtual failed\n");
CloseHandle(hFile);
return -1;
}
DWORD dwRead;
ReadFile(hFile, lpAddr, dwSize, &dwRead, 0);
__asm {
call lpAddr
}
_flushall();
system("pause");
return 0;
}


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!