参考

  • ctfshow pwn入门 1-53

通用操作

chmod 777 pwn
checksec --file={pwn}
file pwn

ida

快捷键

  • “Segments”(段)窗口:Shift + F7
    • 看got一系列表地址

函数

strcmp

返回值 含义 说明
0 相等 两个字符串完全相同
> 0 str1 > str2 第一个不匹配字符在 str1 中的值大于 str2
< 0 str1 < str2 第一个不匹配字符在 str1 中的值小于 str2

puts

  • 输出数组直到遇到\0
  • 寻找方向是从内存低地址到高地址,在ida中显示为向下

gets(s)

  • 从标准输入读取字符串到s
  • 不检查边界:gets() 不管 s 指向的缓冲区有多大,都会一直读取直到遇到换行符 \n
  • 如果输入超过缓冲区大小,会覆盖栈上的其他数据(包括返回地址)

动态/静态编译判断:

  • ida
    • shift+F11
    • imports 超多 libc 函数 → 动态
    • imports 几乎为空 → 静态
  • file
    • statically linked:静态
    • dynamically linked + interpreter /lib/ld-linux.so.2:动态
  • checksec(本质上是通过一些检测机制来判断是否存在某种保护,这个检测机制有些时候是会有误报的)

动态

RWX

  • Has RWX segments
  • 二进制文件中存在至少一个段(通常是代码段),它同时拥有读、写、执行权限

NX

NX enabled

  • NX(或 DEP,Data Execution Prevention)将数据区域(如栈、堆)标记为不可执行,防止攻击者将 shellcode 写入栈/堆后直接跳转执行
  • NX→一定找pop_rdi/ret:
ROPgadget --binary "pwn" --only "pop|ret"
  • 动态找写入点:
gdb pwn
break main
run
vmmap
  1. 动态,开启NX保护,部分开启RELRO保护
    ret2libc:例题1例题2例题3
  2. 动态 + 静态栈保护函数
    爆破canary值绕过栈保护函数memcmp()

Stack

No canary found

  • 没有栈溢出保护
  1. 后门函数
    32位后门函数
    32位后门函数指定参数 + NX + Partial RELRO
    64位后门函数
  2. 有system()和/bin/sh
    32位
    64位
  3. 有system()和/sh:处理方式和/bin/sh一样
    alt text
  4. 只有system()
    32位
    64位
  5. 无system()和/bin/sh
    32位 + NX + Partial RELRO
    64位

canary found

  • 看下是不是伪保护
import subprocess
import sys
import re

def analyze_canary(filename):
print(f"[*] 正在分析文件: {filename}")

# 1. 获取文件位数
try:
file_info = subprocess.check_output(['file', filename]).decode()
is_64 = "64-bit" in file_info
arch_str = "64-bit" if is_64 else "32-bit"
print(f"[*] 架构识别: {arch_str}")
except Exception as e:
print(f"[!] 错误: 无法获取文件信息 - {e}")
return

# 2. 使用 objdump 提取汇编
try:
# 使用 -d 反汇编,-M intel 方便阅读
asm = subprocess.check_output(['objdump', '-d', '-M', 'intel', filename]).decode()
except Exception as e:
print(f"[!] 错误: 无法运行 objdump - {e}")
return

# 3. 定义特征码
# 64位看 fs:0x28,32位看 gs:0x14
canary_pattern = "fs:\[0x28\]" if is_64 else "gs:\[0x14\]"
fail_func_pattern = "__stack_chk_fail"

# 4. 执行匹配
has_setup = re.search(canary_pattern, asm)
has_fail_call = re.search(fail_func_pattern, asm)

print("-" * 40)
if has_setup and has_fail_call:
print("[+] 结果: 确认开启了真·栈保护!")
print(f" - 检测到 Canary 存取指令: {canary_pattern}")
print(f" - 检测到保护失败处理函数: {fail_func_pattern}")
elif has_fail_call and not has_setup:
print("[!] 结果: 检测到伪保护 (False Positive)!")
print(f" - 虽然存在 {fail_func_pattern} 符号,但没有发现 Canary 指令。")
print(" - 结论: checksec 可能会误报,但实际上可以直接溢出。")
else:
print("[-] 结果: 该程序完全没有栈保护。")
print("-" * 40)

if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python3 check_canary.py <文件名>")
else:
analyze_canary(sys.argv[1])

RELRO

当RELRO为Partial RELRO时,表示.got不可写而.got.plt可写。
当RELRO为FullRELRO时,表示.got不可写.got.plt也不可写。
当RELRO为No RELRO时,表示.got与.got.plt都可写。

.got.plt

  • 在同一次进程生命周期里,同一个共享库函数的加载地址是固定的;
  • 进程一重启,地址就可能换地方——这就是 ASLR(地址空间布局随机化)在起作用
  • .got.plt 里的值是绝对地址(64 位下 8 字节指针),不是相对偏移。它在运行过程中会变——从 0 → 第一次解析后变成真实函数地址。
    • GOT专门为PLT准备的节
  • .plt 本身代码段只读,只是无条件 jmp *.got.plt[n];
  • 如果 .got.plt[n] 还没填好,那个 jmp 就会落到一段“解析”代码里,由动态链接器负责把地址填上。

PIE

  • no pie:基址固定

静态

mprotect函数

ROPgadget --binary pwn --only "pop|ret" | grep "pop edi ; pop esi ; pop ebx ; ret"

零碎知识点

shell

  • 在大多数 Unix 或类 Unix 系统中,/bin/sh 是默认的 shell 解释器

重定向&&文件描述符

  • 通过重定向符>&0可以将命令的输出从默认的 stdout(文件描述符 1)重定向到标准输入 0。
  • 终端设备的双向性:
  • 在终端环境中,文件描述符 0(标准输入)、1(标准输出)、2(标准错误)均指向同一个终端设备。虽然 stdout 被关闭,但终端设备本身支持读写,因此通过 >&0 将输出写入文件描述符 0 时,仍能显示在终端上。
  • 此时输入ls >&0可以展开所有的目录文件
  • cat c* >&0:匹配所有以 c 开头的文件(如 catflag),将其内容输出到终端,于是ctfshow答案直接被显示出来了

gcc

  • 把各种文件编译成可执行文件
# .c
gcc flag.c -o flag
# .asm/.o
nasm -f elf32 XXX.asm -o XXX.o
ld -m elf_i386 XXX.o -o XXX
# .s
gcc -o flag flag.s
# 两种运行方式
# 第一种:
chmod 777 flag
./flag
# 第二种:
gdb XXX
run

ida操作

  1. 取消定义(Undefine) :
    • 按下键盘上的 U 键。
    • 这会把当前定义的数据“炸碎”,变成一堆原始的字节(通常显示为 db 且只有单个字节)。
  2. 重新定义为双字(Make Dword) :
    • 确保光标还在 080490E8 这个起始位置。
    • 按下键盘上的 D 键。
    • 按第一次,它可能变成 db (byte)。
    • 按第二次,变成 dw (word)。
    • 按第三次 ,它就会变成 dd (dword) 。
      此时,原本显示的 ‘Welc’ 就会变成十六进制数值 636C6557h
  3. 重新回到 ‘Welc’:A

汇编知识点

dd/db

  • dd (Define Double-word):把 4 个字节当作“一个整体”
    类型 :它定义的是一个 32位整数 (Integer) 。
    受端序影响 :是。
  • db (Define Byte):把数据当作“一串独立的字节”
    类型 :它定义的是 字节数组 (Array of Bytes) 或 字符串 。
    受端序影响 : 否 。

ptr

mov ecx, dword ptr aWelcomeToCtfsh
  • ai分析:
    • ecx :这是 目的地。 ecx 是一个 32 位的通用寄存器(Counter Register),这里被用作存放数据的容器。
    • dword ptr (Double Word Pointer) :这是 取多少数据 。
      • ptr 是指针的意思,指向内存中的某个位置。
      • dword (Double Word) 表示 4个字节 (32位)。
      • 合起来的意思是: “请去内存里抓取 4 个字节的数据” 。
    • aWelcomeToCtfsh :这是源地址 。
      • 这只是 IDA Pro 给内存地址起的一个 名字(标签) ,为了让你好记。
      • 它代表了字符串 “Welcome_to_CTFshow_PWN” 在内存中存放的 起始地址 。