s0m1ng

二进制学习中

线下赛知识查询

前言:

线下赛一般都断网,这里总结一些会用到的知识

Linux 底层函数查阅指南

如果忘记了某个系统调用或 C 库函数(比如 puts 是遇到 \x00 停还是 \n 停?read 的参数顺序是什么?)

用Linux 自带的查询命令:man (Manual) 手册

核心命令速查

查 C 语言标准库函数(第 3 章)
man 3 puts # 查 puts 的用法、返回值、截断机制
man 3 printf # 查格式化字符串

关于函数到底怎么处理数据的核心逻辑,写在 man 手册最开头的 DESCRIPTION (描述) 部分里。

查底层系统调用(第 2 章):在手搓 Shellcode 或 Ret2syscall 时必用。
man 2 read # 查 read 系统调用
man 2 execve # 查启动 shell 的核心调用

Man 手册快捷键 (Vim 逻辑)

  • j / k:上下滚动一行

  • Space (空格):向下翻一页

  • /关键字:全文搜索(例如 /return value 找返回值说明),按 n 找下一个,N 找上一个。

  • q:退出手册。

Pwntools

以下核心模板,能覆盖 90% 的常规题型。

环境与连接初始化

1
2
3
4
5
6
7
8
9
10
from pwn import *

# 极其重要:设置目标架构和操作系统,这会影响后续的汇编、打包解包操作!
context(arch='amd64', os='linux', log_level='debug') # 开启 debug 可见所有收发细节

# 本地调试与远程连接
# io = process('./vuln') # 启动本地进程
io = remote('127.0.0.1', 8888) # 连接远程靶机
elf = ELF('./vuln') # 加载本地 ELF,用于获取 plt/got 和符号
libc = ELF('./libc.so.6') # 加载本地指定的 Libc

交互与发包 (极其容易踩坑)

1
2
3
4
5
6
7
8
9
10
11
12
13
# --- 发送 ---
io.send(payload) # 纯净发送,不附加任何东西(泄露地址、覆盖 Canary 最低位时必用!)
io.sendline(payload) # 发送并在末尾追加 \n (0x0a)
io.sendafter(b'xxx', payload) # 接收到特定字符串后再发送
io.sendlineafter(b'xxx', payload)

# --- 接收 ---
io.recv(n) # 严格接收 n 个字节
io.recvuntil(b'xxx') # 一直接收并丢弃,直到遇到特定字符串为止(过滤垃圾数据神技!)
io.recvall() # 接收直到连接关闭

# --- 终极一跃 ---
io.interactive() # 将控制权交还给用户,享受你的 Shell!

数据转换与打包 (大小端转换)

1
2
3
4
5
6
7
# 将数字打包成字节流 (Little-Endian 小端序)
p32(0xdeadbeef) # 32 位打包:b'\xef\xbe\xad\xde'
p64(0xdeadbeef) # 64 位打包:b'\xef\xbe\xad\xde\x00\x00\x00\x00'

# 将字节流解包成数字 (泄露地址时必用)
# 注意:64位下泄露的地址通常只有 6 字节,需要用 \x00 在高位补齐 8 字节!
leak_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

ELF 与 Libc 快捷搜索

1
2
3
4
5
6
7
8
9
# ELF 对象神器
puts_plt = elf.plt['puts'] # 获取 puts 在 PLT 表的地址
puts_got = elf.got['puts'] # 获取 puts 在 GOT 表的地址
main_addr = elf.symbols['main'] # 获取 main 函数真实地址

# Libc 对象神器 (需先计算出 libc_base)
libc.address = libc_base # 给 libc 对象设置基址后,下面所有的 sym 都会自动加上基址!
system_addr = libc.sym['system']
binsh_addr = next(libc.search(b'/bin/sh')) # 搜索字符串地址

系统调用与 Shellcode (asm & shellcraft)

在打 ret2syscall 或需要手写系统调用时,不再需要死记硬背 execve 的调用号了,Pwntools 提供了一键生成与查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 获取系统调用号 (根据开头的 context.arch 自动适配 32位/64位)
sys_execve_num = constants.SYS_execve
print(f"execve 的系统调用号是: {sys_execve_num}") # 32位是 11,64位是 59

# 手写汇编转机器码 (自动替换宏)
assembly = '''
/* Pwntools 甚至支持直接在汇编里写系统调用名字 */
mov eax, SYS_execve
mov ebx, 0x0804a020 /* 假设这是 /bin/sh 的地址 */
mov ecx, 0
mov edx, 0
int 0x80
'''
machine_code = asm(assembly)

# 终极偷懒:一键生成 execve("/bin/sh", 0, 0) 的拿 Shell 机器码!
shellcode = asm(shellcraft.sh())
# 或者更具体的生成:
# shellcode = asm(shellcraft.execve('/bin/sh', 0, 0))

io.sendline(shellcode)

GDB-Pwndbg 动态调试真经

将 GDB 挂载到 Python 脚本中

Pwntools 提供了两种极其方便的挂载方式:

方式一:gdb.attach() (中途挂载,最常用)

1
2
3
4
5
6
7
8
9
io = process('./vuln')
# 先进行一些无关紧要的交互
io.sendlineafter(b'name', b'admin')

# 在发送致命 payload 前,把 GDB 挂载上去,并下断点
gdb.attach(io, 'b main\nb *0x401234')
pause() # 暂停脚本,此时终端会弹出 GDB 窗口。你在 GDB 里调试好后,在脚本按回车才会继续发包。

io.sendline(payload)

方式二:gdb.debug() (启动即挂载)

1
2
# 直接以 GDB 调试模式启动进程
io = gdb.debug('./vuln', 'b main\nc')

执行控制与断点配置

基础步进

c (continue):一直运行,直到遇到下一个断点或程序崩溃(SIGSEGV)。

n (next):源码级单步步过(不进入子函数)。

s (step):源码级单步步入(会跟进子函数)。

ni / si汇编级单步步过 / 步入(次执行一条汇编指令,不漏过任何寄存器变化)。

fin (finish):直接执行完当前函数并返回。

断点管理

b main:给特定函数名下断点。

b *0x401234:给绝对内存地址下断点(注意前面的星号 * 绝对不能漏!)。

b *$rebase(0x1234)PIE 开启时的神技! 给相对偏移下断点,Pwndbg 会自动帮你加上本次运行的随机化基址。

info b:查看当前所有断点状态。

del 1:删除编号为 1 的断点。

watch *0x404040:内存硬件断点(极其好用),只要这个内存地址里的值被修改,程序立刻断下!

内存观测与数据修改

万能的 x 命令 (Examine) 格式:x/<数量><格式><大小> <地址/寄存器>

  • x/10gx $rsp:以十六进制 (x)、8字节 (g) 为单位,打印栈顶 ($rsp) 往下的 10 个数据。

  • x/10wx $ebp-0x10:以十六进制 (x)、4字节 (w) 为单位,打印指定地址数据。

  • x/s 0x402000:把该地址的数据当作字符串打印出来(检查 /bin/sh 是否写入成功)。

  • x/20i $rip:把当前指令指针往后的 20 个字节当作汇编指令反汇编出来。

修改寄存器/内存

  • set $rax = 0:强行把 rax 寄存器的值改为 0(常用于绕过条件判断)。

  • set *0x404040 = 0x1234:强行向内存地址写入指定值。

Pwndbg 实战专属技巧

  • 全景视角:context 如果你清屏了或者界面乱了,直接输入 context 可以立刻重新唤出 Pwndbg 的四大面板(寄存器区、反汇编区、源码区、栈区)。

  • 内存权限雷达:vmmap 查看当前所有内存段(代码段、栈、堆、libc)的基址和权限。遇到段错误崩溃时,第一反应打 vmmap,看目标地址到底是不是 rwx(往往是因为想执行 shellcode 却跳到了 rw- 的不可执行段)。

  • 超级栈透视:telescope (简写 tele)

    • tele 20 或者 tele $rsp 20:以极度优雅的格式打印栈顶往下的 20 个单位。

    • 神级特性:它不仅印出数值,还会递归解析指针!如果栈上有一个地址指向 libc,它会直接在一旁标出 -> libc_system_addr,找链子极其方便。

  • 大海捞针:search

    • search "/bin/sh":在整个内存空间里搜索目标字符串。

    • search -p 0xdeadbeef:在内存里寻找有没有哪个指针刚好指向了这个特定的地址。

  • 计算溢出长度:cyclic 如果在 IDA 里看不出数组距离返回地址多远:

    1. 终端运行 cyclic 200 生成 200 个规律乱码字符(如 aaaabaaacaaa...)。

    2. 把乱码当作 payload 发送,程序崩溃报错,此时 ripebp 会被某个乱码(比如 0x61616164)覆盖。

    3. 回到 GDB 终端运行 cyclic -l 0x61616164,直接光速算出精确的 Padding 长度!

  • 追踪执行流:retaddrgot

    • retaddr:瞬间打印出当前栈上所有的返回地址链(相当于增强版的 Backtrace)。

    • got:打印程序当前的 GOT 表状况,直观看到哪个函数被我们劫持了。

    • libc:直接打印出当前加载的 libc 基地址,方便你做本地偏移计算验证。

您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道