0.1-菜鸟学PWN之工具篇

0x00 简介

本章介绍在CTF PWN中常用工具的使用及其快捷键,尽量用最短的学习时间快速入门这些工具的使用。随着工具的增多以及其功能的丰富,本章会不定期更新。

0x10 IDA Pro

0x11 快捷键

IDA快捷键如下表:

快捷键 功能
; 为当前指令添加全文交叉引用的注释
/ 在汇编中可见c代码
n 定义或修改名称,通常用来标注函数名
g 跳转到任意地址
Esc 返回到跳转前的位置
D 分别按字节、字、双字显示数据
A 按 ASCII 显示数据
u 取消定义一个函数或变量等
p 将一段16进制解析成函数(相当于u[undefine]相反的操作)
alt + p 编辑function, 可定义函数的范围(起始/终止地址)
x 查看交叉引用
h 16/10进制转换
r 转换成字符串
空格键 反汇编窗口切换文本跟图形
F9 动态调试程序(其实IDA主要用作静态分析用的)
F2 下断点
N 重命名
Y 设置变量类型
M 转换为枚举类型常量
ALT+G 转换局部变量为结构体
  1. IDAView下使用小键盘“-”,“+”快捷方式可以在代码同关系图之间切换。
  2. 使用快捷键”*”把变量重定义为数组.
  3. ALT+T:搜索字符串(文本搜索)
  4. alt+B:搜索opcode(二进制数据),搜索16进制:

0x12 常用插件

插件链接及功能如下:

  • IDA FLIRT Signature Database -- 用于识别静态编译的可执行文件中的库函数
  • Find Crypt -- 寻找常用加密算法中的常数(需要安装 yara-python
  • IDA signsrch -- 寻找二进制文件所使用的加密、压缩算法
  • Ponce -- 污点分析和符号化执行工具
  • snowman decompiler -- C/C++反汇编插件(F3 进行反汇编)
  • CodeXplorer -- 自动类型重建以及对象浏览(C++)(jump to disasm)
  • IDA Ref -- 汇编指令注释(支持arm,x86,mips)
  • auto re -- 函数自动重命名
  • nao -- dead code 清除
  • HexRaysPyTools -- 类/结构体创建和虚函数表检测
  • DIE -- 动态调试增强工具,保存函数调用上下文信息
  • sk3wldbg -- IDA 动态调试器,支持多平台
  • idaemu -- 模拟代码执行(支持X86、ARM平台)
  • Diaphora -- 程序差异比较
  • Keypatch -- 基于 Keystone 的 Patch 二进制文件插件
  • FRIEND -- 哪里不会点哪里,提升汇编格式的可读性、提供指令、寄存器的文档等
  • SimplifyGraph -- 简化复杂的函数流程图
  • bincat -- 静态二进制代码分析工具包,2017 Hex-Rays 插件第一名
  • golang_loader_assist -- Golang编译的二进制文件分析助手
  • BinDiff

0x13 常用技巧

① 堆栈不平衡

某些函数在使用 f5 进行反编译时,会提示错误 "sp-analysis failed",导致无法正确反编译。原因可能是在代码执行中的 pop、push 操作不匹配,导致解析的时候 esp 发生错误。

解决办法步骤如下:

  1. 用 Option->General->Disassembly, 将选项 Stack pointer 打钩
  2. 仔细观察每条 call sub_xxxxxx 前后的堆栈指针是否平衡
  3. 有时还要看被调用的 sub_xxxxxx 内部的堆栈情况,主要是看入栈的参数与 ret xx 是否匹配
  4. 注意观察 jmp 指令前后的堆栈是否有变化
  5. 有时用 Edit->Functions->Edit function...,然后点击 OK 刷一下函数定义

② 分析选项

选择File菜单下的Open,打开想要逆向的可执行文件,会显示一个Load a new file的界面。这里可以选择: 

  1. 程序的类型; 
  2. 处理器的类型; 
  3. 加载的段地址和偏移量; 
  4. 是否允许分析; 
  5. 一些加载选项; 
  6. 内核和处理器的一些选项; 
  7. windows系统dll所在的目录。
Processor type中可选择CPU指令集
Processor type中可选择CPU指令集

​ 上图默认选择PE文件就可以,对于一些网络数据包或者其他格式的文件,可以使用二进制加载,自己进行解析。

③ 修改数据

  • 如何修改数据:(在Hex View窗口下) [S1]双击要修改的地方 [S2]使用F2捷方式修改当前字节 [S3]再按下F2快捷方式应用修改。
  • 如何改变执行流程  [1]修改跳转指令。  [2]修改内存数据。  [3]IDA View中使用下面的命令Jump to IP,Set IP, Run to cursor。 
  • 保存修改  使用下面的主菜单命令,直接把修改保存到输入文件中,即可。  [IDA Main Menu]->[Edit]->[PatchProgram]->[Apply patches to input file…]

④ 创建结构体

  1. 在IDA中打开Structures,快捷键是shift+ F9 ,然后按insert键,插入新结构体:

    输入结构题的名称
    输入结构题的名称
  2. ends后按d键,添加相应的成员,然后选中成员名,按N进行修改,选中类型,按d进行更改类型,构造完结构题以后,回到代码窗口

    修改成员名及其类型
    修改成员名及其类型
  3. 在变量堆栈结构里, Edit▶Struct Var(ALT+Q)命令显示一组已知的结构体

  4. 跳到要定义结构体的变量, 按y, 把它改成(新定义结构体名) *.

    创建及使用结构体
    创建及使用结构体

⑤ 解析C头文件

解析头文件,可以使用File▶Load File▶Parse C HeaderFile(文件▶加载文件▶解析C头文件)选择你想要解析的头文件。如果一切正常,IDA会通知你Compilation successful(编译完成)。如果解析器遇到任何问题,IDA将会在输出窗口中显示错误消息

⑥ 定义数组

当看到这样的多个变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v2 = 102;
v3 = 109;
v4 = 99;
v5 = 100;
v6 = 127;
v7 = 107;
v8 = 55;
v9 = 100;
v10 = 59;
v11 = 86;
v12 = 96;
v13 = 59;
v14 = 110;
v15 = 112;
堆栈视图的数组
堆栈视图的数组

可以考虑只定义一个数组, 按*, 会弹出如下窗口:

定义数组
定义数组

要特别注意array size.

⑦ 动态调试 & 远程调试

  1. 如果在win系统上调试exe文件, 则可以直接用本地debugger调试, 按F9之后会跳出如下界面:

    本地动态调试
    本地动态调试

    Local .... 即可动态调试. (调试时记得在关键点按F2下断点)

  2. 如果在win下动态调elf或so文件, 则需要利用到远程调试(一般是本地ida远程连接linux虚拟机来调试)

    Step1: 将IDA安装目录下的linux_server/linux_server64复制到linux虚拟机,在linux下执行linux_server(32/64, 看程序而定)

    Step2: 在Windows下打开需要反汇编的ELF格式文件,并在IDA上配置远程调试的环境:Debugger——Attach——Remote Linux debugger,选Remote Linux debugger:

    选Remote Linux debugger

    Step3: 在Debug options勾选如上选项:

    在Debug options勾选如上选项

    Step4: Hostname为虚拟机ip,Password为空

    Hostname为虚拟机ip,Password为空
  3. 如果是so文件, 跟上述方法差不多, 需要额外注意adb shell下的端口转发.

0x14 IDA Python

1
2
3
4
5
6
7
import ida_bytes

buf = ida_bytes.get_bytes(0x600B00, 182)
patch = ""
for i in buf:
truepatch += chr(ord(i) ^ 0x0C)
ida_bytes.patch_bytes(0x600B00,patch)

IDA Python API: https://www.hex-rays.com/products/ida/support/idapython_docs/


0x20 GDB

0x21 gdb 的组成架构

img
img

0x22 gdb 基本工作原理

gdb 通过系统调用 ptrace 来接管一个进程的执行。ptrace 系统调用提供了一种方法使得父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。它主要用来实现断点调试和系统调用跟踪。ptrace 系统调用的原型如下:

1
2
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

对应的参数如下:

  • pid_t pid:指示 ptrace 要跟踪的进程。
  • void *addr:指示要监控的内存地址。
  • void *data:存放读取出的或者要写入的数据。
  • enum __ptrace_request request:决定了系统调用的功能,几个主要的选项:
    • PTRACE_TRACEME:表示此进程将被父进程跟踪,任何信号(除了 SIGKILL)都会暂停子进程,接着阻塞于 wait() 等待的父进程被唤醒。子进程内部对 exec() 的调用将发出 SIGTRAP 信号,这可以让父进程在子进程新程序开始运行之前就完全控制它。
    • PTRACE_ATTACH:attach 到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次 PTRACE_TRACEME 操作。但需要注意的是,虽然当前进程成为被跟踪进程的父进程,但是子进程使用 getppid() 的到的仍将是其原始父进程的 pid。
    • PTRACE_CONT:继续运行之前停止的子进程。可同时向子进程交付指定的信号。

gdb 的三种调试方式

  1. 运行并调试一个新进程
    • 运行 gdb,通过命令行或 file 命令指定目标程序。
    • 输入run命令, gdb 执行下面的操作:
      • 通过 fork() 系统调用创建一个新进程
      • 在新创建的子进程中执行操作:ptrace(PTRACE_TRACEME, 0, 0, 0)
      • 在子进程中通过 execv() 系统调用加载用户指定的可执行文件
  2. attach 并调试一个已经运行的进程
    • 用户确定需要进行调试的进程 PID
    • 运行 gdb,输入 attach <pid>,gdb 将对指定进程执行操作: ptrace(PTRACE_ATTACH, pid, 0, 0)
  3. 远程调试目标机上新创建的进程
    • gdb 运行在调试机上,gdbserver 运行在目标机上,两者之间的通信数据格式由 gdb 远程串行协议(Remote Serial Protocol)——定义RSP协议数据的基本格式为: $..........#xx
    • gdbserver 的启动方式相当于运行并调试一个新创建的进程

注意,在你将 gdb attach 到一个进程时,可能会出现这样的问题:

1
2
3
gdb-peda$ attach 9091
Attaching to process 9091
ptrace: Operation not permitted.

这是因为开启了内核参数 ptrace_scope

1
2
$ cat /proc/sys/kernel/yama/ptrace_scope
1

1 表示 True,此时普通用户进程是不能对其他进程进行 attach 操作的,当然你可以用 root 权限启动 gdb,但最好的办法还是关掉它:

1
# echo 0 > /proc/sys/kernel/yama/ptrace_scope

断点的实现

断点的功能是通过内核信号实现的,在 x86 架构上,内核向某个地址打入断点,实际上就是往该地址写入断点指令 INT 3,即 0xCC。目标程序运行到这条指令之后会触发 SIGTRAP 信号,gdb 捕获这个信号,并根据目标程序当前停止的位置查询 gdb 维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。

0x23 gdb 基本操作

使用 -tui 选项可以将代码显示在一个漂亮的交互式窗口中。

break -- b

  • break 当不带参数时,在所选栈帧中执行的下一条指令处设置断点。
  • break <function> 在函数体入口处打断点。
  • break <line> 在当前源码文件指定行的开始处打断点。
  • break -N break +N 在当前源码行前面或后面的 N 行开始处打断点,N 为正整数。
  • break <filename:line> 在源码文件 filenameline 行处打断点。
  • break <filename:function> 在源码文件 filenamefunction 函数入口处打断点。
  • break <address> 在程序指令的地址处打断点。
  • break ... if <cond> 设置条件断点,... 代表上述参数之一(或无参数),cond 为条件表达式,仅在 cond 值非零时停住程序。

info

  • info breakpoints -- i b 查看断点,观察点和捕获点的列表。
    • info breakpoints [list…]
  • info break [list…]
    • list… 用来指定若干个断点的编号(可省略),可以是 21-32 5等。
  • info display 打印自动显示的表达式列表,每个表达式都带有项目编号,但不显示其值。
  • info reg 显示当前寄存器信息。
  • info threads 打印出所有线程的信息,包含 Thread ID、Target ID 和 Frame。
  • info frame 打印出指定栈帧的详细信息。
  • info proc 查看 proc 里的进程信息。

disable -- dis

禁用断点,参数使用空格分隔。不带参数时禁用所有断点。

  • disable [breakpoints] [list…] breakpointsdisable 的子命令(可省略),list…info breakpoints 中的描述。

enable

启用断点,参数使用空格分隔。不带参数时启用所有断点。

  • enable [breakpoints] [list…] 启用指定的断点(或所有定义的断点)。
  • enable [breakpoints] once list… 临时启用指定的断点。GDB 在停止您的程序后立即禁用这些断点。
  • enable [breakpoints] delete list… 使指定的断点启用一次,然后删除。一旦您的程序停止,GDB 就会删除这些断点。等效于用 tbreak 设置的断点。

breakpointsdisable 中的描述。

clear

在指定行或函数处清除断点。参数可以是行号,函数名称或 * 跟一个地址。

  • clear 当不带参数时,清除所选栈帧在执行的源码行中的所有断点。
  • clear <function>, clear <filename:function> 删除在命名函数的入口处设置的任何断点。
  • clear <line>, clear <filename:line> 删除在指定的文件指定的行号的代码中设置的任何断点。
  • clear <address> 清除指定程序指令的地址处的断点。

delete -- d

删除断点。参数使用空格分隔。不带参数时删除所有断点。

  • delete [breakpoints] [list…]

tbreak

设置临时断点。参数形式同 break 一样。当第一次命中时被删除。

watch

为表达式设置观察点。每当一个表达式的值改变时,观察点就会停止执行您的程序。

  • watch [-l|-location] <expr> 如果给出了 -l 或者 -location,则它会对 expr 求值并观察它所指向的内存。

另外 rwatch 表示在访问时停止,awatch 表示在访问和改变时都停止。

step -- s

单步执行程序,直到到达不同的源码行。

  • step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

reverse-step

反向步进程序,直到到达另一个源码行的开头。

  • reverse-step [N] 参数 N 表示执行 N 次(或由于另一个原因直到程序停止)。

next -- n

单步执行程序,执行完子程序调用。

  • next [N]step 不同,如果当前的源代码行调用子程序,则此命令不会进入子程序,而是继续执行,将其视为单个源代码行。

reverse-next

反向步进程序,执行完子程序调用。

  • reverse-next [N]

如果要执行的源代码行调用子程序,则此命令不会进入子程序,调用被视为一个指令。

return

您可以使用 return 命令取消函数调用的执行。如果你给出一个表达式参数,它的值被用作函数的返回值。

  • return <expression>expression 的值作为函数的返回值并使函数直接返回。

finish -- fin

执行直到选定的栈帧返回。

  • finish

until -- u

执行程序直到大于当前栈帧或当前栈帧中的指定位置(与 break 命令相同的参数)的源码行。此命令常用于通过一个循环,以避免单步执行。

  • until <location> 继续运行程序,直到达到指定的位置,或者当前栈帧返回。

continue -- c

在信号或断点之后,继续运行被调试的程序。

  • continue [N]

如果从断点开始,可以使用数字 N 作为参数,这意味着将该断点的忽略计数设置为 N - 1(以便断点在第 N 次到达之前不会中断)。

求表达式 expr 的值并打印。可访问的变量是所选栈帧的词法环境,以及范围为全局或整个文件的所有变量。

  • print [expr]
  • print /f [expr] 通过指定 /f 来选择不同的打印格式,其中 f 是一个指定格式的字母

x

检查内存。

  • x/nfu <addr>
  • x <addr>
    • n, f, 和 u 都是可选参数,用于指定要显示的内存以及如何格式化。
    • addr 是要开始显示内存的地址的表达式。
    • n 重复次数(默认值是 1),指定要显示多少个单位(由 u 指定)的内存值。
    • f 显示格式(初始默认值是 x),显示格式是 print('x','d','u','o','t','a','c','f','s') 使用的格式之一,再加 i(机器指令)。
    • u 单位大小,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。

display

每次程序停止时打印表达式 expr 的值。

  • display <expr>
  • display/fmt <expr>
  • display/fmt <addr>

fmt 用于指定显示格式。对于格式 is,或者包括单位大小或单位数量,将表达式 addr 添加为每次程序停止时要检查的内存地址。

disassemble -- disas

反汇编命令。

  • disas <func> 反汇编指定函数
  • disas <addr> 反汇编某地址所在函数
  • disas <begin_addr> <end_addr> 反汇编从开始地址到结束地址的部分

undisplay

取消某些表达式在程序停止时自动显示。参数是表达式的编号(使用 info display查询编号)。不带参数表示取消所有自动显示表达式。

disable display

禁用某些表达式在程序停止时自动显示。禁用的显示项目被再次启用。参数是表达式的编号(使用 info display 查询编号)。不带参数表示禁用所有自动显示表达式。

enable display

启用某些表达式在程序停止时自动显示。参数是重新显示的表达式的编号(使用 info display 查询编号)。不带参数表示启用所有自动显示表达式。

help -- h

打印命令列表。

  • help <class> 您可以获取该类中各个命令的列表。
  • help <command> 显示如何使用该命令的简述。

attach

挂接到 GDB 之外的进程或文件。将进程 ID 或设备文件作为参数。

  • attach <process-id>

run -- r

启动被调试的程序。可以直接指定参数,也可以用 set args 设置(启动所需的)参数。还允许使用 >, <, 或 >> 进行输入和输出重定向。

甚至可以运行一个脚本,如:

1
run `python2 -c 'print "A"*100'`

backtrace -- bt

打印整个栈的回溯。

  • bt 打印整个栈的回溯,每个栈帧一行。
  • bt n 类似于上,但只打印最内层的 n 个栈帧。
  • bt -n 类似于上,但只打印最外层的 n 个栈帧。
  • bt full n 类似于 bt n,还打印局部变量的值。

注意:使用 gdb 调试时,会自动关闭 ASLR,所以可能每次看到的栈地址都不变。

ptype

打印类型 TYPE 的定义。

  • ptype[/FLAGS] TYPE-NAME | EXPRESSION

参数可以是由 typedef 定义的类型名, 或者 struct STRUCT-TAG 或者 class CLASS-NAME 或者 union UNION-TAG 或者 enum ENUM-TAG

set follow-fork-mode

当程序 fork 出一个子进程的时候,gdb 默认会追踪父进程(set follow-fork-mode parent),但也可以使用命令 set follow-fork-mode child 让其追踪子进程。

另外,如果想要同时追踪父进程和子进程,可以使用命令 set detach-on-fork off(默认为on),这样就可以同时调试父子进程,在调试其中一个进程时,另一个进程被挂起。如果想让父子进程同时运行,可以使用 set schedule-multiple on(默认为off)。

但如果程序是使用 exec 来启动了一个新的程序,可以使用 set follow-exec-mode new(默认为same) 来新建一个 inferior 给新程序,而父进程的 inferior 仍然保留。

thread apply all bt

打印出所有线程的堆栈信息。

generate-core-file

将调试中的进程生成内核转储文件。

directory -- dir

设置查找源文件的路径。

或者使用 gdb 的 -d 参数,例如:gdb a.out -d /search/code/

0x24 gdb-peda (gdb-gef)

当 gdb 启动时,它会在当前用户的主目录中寻找一个名为 .gdbinit 的文件;如果该文件存在,则 gdb 就执行该文件中的所有命令。通常,该文件用于简单的配置命令。但是 .gdbinit 的配置十分繁琐,因此对 gdb 的扩展通常用插件的方式来实现,通过 python 的脚本可以很方便的实现需要的功能。

PEDA(Python Exploit Development Assistance for GDB)是一个强大的 gdb 插件。它提供了高亮显示反汇编代码、寄存器、内存信息等人性化的功能。同时,PEDA 还有一些实用的新命令,比如 checksec 可以查看程序开启了哪些安全机制等等。

安装

安装 peda 需要的软件包:

1
2
$ sudo apt-get install nasm micro-inetd
$ sudo apt-get install libc6-dbg vim ssh

安装 peda:

1
2
3
$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit
$ echo "DONE! debug your program with gdb and enjoy"

如果系统为 Arch Linux,则可以直接安装:

1
$ yaourt -S peda

peda命令

aslr

显示/设置 gdb 的 ASLR

asmsearch

Search for ASM instructions in memory

  • asmsearch "int 0x80"
  • asmsearch "add esp, ?" libc ##### assemble > On the fly assemble and execute instructions using NASM
1
2
3
4
assemble $pc
> mov al, 0xb
> int 0x80
> end
checksec
  • 检查二进制文件的安全选项
cmpmem
  • Compare content of a memory region with a file
  • cmpmem 0x08049000 0x0804a000 data.mem
context
  • Display various information of current execution context
  • context_code -- Display nearby disassembly at $PC of current execution context
  • context_register -- Display register information of current execution context
  • context_stack- - Display stack of current execution context
  • context reg
  • context code
  • context stack
crashdump
  • Display crashdump info and save to file
deactive
  • Bypass a function by ignoring its execution (eg sleep/alarm)
  • deactive setresuid
  • deactive chdir
distance
  • Calculate distance between two addresses
dumpargs
  • 在调用指令停止时显示传递给函数的参数
dumpmem
  • Dump content of a memory region to raw binary file -dumpmem libc.mem libc
dumprop
  • 在特定的内存范围显示 ROP gadgets
  • dumprop binary "pop"
eflags
  • Display/set/clear/toggle value of eflags register
  • elfheader
    • 获取正在调试的 ELF 文件的头信息
    • elfheader
  • elfheader .got
elfsymbol
  • 从 ELF 文件中获取没有调试信息的符号信息
  • elfsymbol
  • elfsymbol printfgennop
gennop
  • Generate abitrary length NOP sled using given characters
  • gennop 500
  • gennop 500 "\x90"
getfile
  • Get exec filename of current debugged process
getpid
  • Get PID of current debugged process
goto
  • Continue execution at an address
help
  • Print the usage manual for PEDA commands
hexdump
  • Display hex/ascii dump of data in memory
  • hexdump $sp 64
  • hexdump $sp /20
hexprint
  • Display hexified of data in memory
  • hexprint $sp 64
  • hexprint $sp /20
jmpcall
  • Search for JMP/CALL instructions in memory
  • jmpcall eax
  • jmpcall esp libc
loadmem
  • Load contents of a raw binary file to memory
  • loadmem stack.mem 0xbffdf000
lookup
  • 搜索属于内存范围的地址的所有地址/引用
  • lookup address stack libc
  • lookup pointer stack ld-2
nearpc
  • Disassemble instructions nearby current PC or given address
  • nearpc 20
  • nearpc 0x08048484
nextcall
  • Step until next 'call' instruction in specific memory range
  • nextcall cpy
nextjmp
  • Step until next 'j*' instruction in specific memory range
  • nextjmp
nxtest

-- Perform real NX test to see if it is enabled/supported by OS

patch
  • 使用字符串/十六进制字符串/整形数
  • patch $esp 0xdeadbeef
  • patch $eax "the long string"
  • patch (multiple lines)
pattern
  • 生成,搜索或写入循环 pattern 到内存
  • pattern_arg -- Set argument list with cyclic pattern
  • pattern_create -- Generate a cyclic pattern
  • pattern_env -- Set environment variable with a cyclic patter
  • pattern_offset -- Search for offset of a value in cyclic pattern
  • pattern_patch -- Write a cyclic pattern to memory
  • pattern_search -- Search a cyclic pattern in registers and memory
  • pattern create 2000
  • pattern create 2000 input
  • pattern offset $pc
  • pattern search
  • pattern patch 0xdeadbeef 100
payload
  • Generate various type of ROP payload using ret2plt
  • payload copybytes
  • payload copybytes target "/bin/sh"
  • payload copybytes 0x0804a010 offset
pdisass
  • Format output of gdb disassemble command with colors
  • pdisass $pc /20
pltbreak
  • Set breakpoint at PLT functions match name regex
  • pltbreak cpy
procinfo
  • 显示调试进程的 /proc/pid/
  • procinfo
  • procinfo fd
profile
  • Simple profiling to count executed instructions in the program
pyhelp
  • Wrapper for python built-in help
  • pyhelp peda
  • pyhelp hex2str
pshow
  • 显示各种 PEDA 选项和其他设置
  • pshow
  • pshow option context
pset
  • 设置各种 PEDA 选项和其他设置
  • pset arg '"A"*200'
  • pset arg 'cyclic_pattern(200)'
    • pset env EGG 'cyclic_pattern(200)'
    • pset option context "code,stack"
    • pset option badchars "\r\n"
readelf
  • 获取 ELF 的文件头信息
  • readelf libc .text
refsearch
  • Search for all references to a value in memory ranges
  • refsearch "/bin/sh"
  • refsearch 0xdeadbeef
reload
  • Reload PEDA sources, keep current options untouch
ropgadget
  • 获取二进制或库的常见 ROP gadgets
  • ropgadget
  • ropgadget libc
ropsearch
  • 搜索内存中的 ROP gadgets
  • ropsearch "pop eax"
  • ropsearch "xchg eax, esp" libc
searchmem|find
  • 搜索内存中的 pattern, 支持正则表达式搜索
  • find "/bin/sh" libc
  • find 0xdeadbeef all
  • find "..\x04\x08" 0x08048000 0x08049000
  • searchmem -- Search for a pattern in memory; support regex search
  • session -- Save/restore a working gdb session to file as a script
set
  • Set various PEDA options and other settings
  • set exec-wrapper ./exploit.py
sgrep
  • Search for full strings contain the given pattern
shellcode
  • 生成或下载常见的 shellcode
  • shellcode x86/linux exec
show
  • Show various PEDA options and other settings
skeleton
  • 生成 python exploit 代码模板
  • skeleton argv exploit.py
skipi
  • Skip execution of next count instructions
snapshot
  • Save/restore process's snapshot to/from file
  • snapshot save
  • snapshot restore
start
  • Start debugged program and stop at most convenient entry
stepuntil
  • Step until a desired instruction in specific memory range
  • stepuntil cmp
  • stepuntil xor
strings
  • Display printable strings in memory
  • strings
  • strings binary 4
substr
  • Search for substrings of a given string/number in memory
telescope
  • Display memory content at an address with smart dereferences
  • telescope 40
  • telescope 0xb7d88000 40
tracecall
  • Trace function calls made by the program
  • tracecall
  • tracecall "cpy,printf"
  • tracecall "-puts,fflush"
traceinst
  • Trace specific instructions executed by the program
  • traceinst 20
  • traceinst "cmp,xor"
unptrace
  • Disable anti-ptrace detection
  • unptrace
utils
  • Miscelaneous utilities from utils module
vmmap
  • 在调试过程中获取段的虚拟映射地址范围
  • cmmap
  • vmmap binary / libc
    • vmmap 0xb7d88000
waitfor
  • Try to attach to new forked process; mimic "attach -waitfor"
  • waitfor
  • waitfor myprog -c
xinfo
  • Display detail information of address/registers
  • xinfo register eax
  • xinfo 0xb7d88000
xormem
  • 用一个 key 来对一个内存区域执行 XOR 操作
  • xormem 0x08049000 0x0804a000 “thekey”
xprint
  • Extra support to GDB's print command
xrefs
  • Search for all call/data access references to a function/variable
xuntil
  • Continue execution until an address or function

使用 PEDA 和 Python 编写 gdb 脚本

pedacmd
1
- 交互式命令
  • 没有返回值
    • 例如:pedacmd.context_register()
    peda
    1
    - 与 gdb 交互的后端功能
  • 有返回值
    • 例如:peda.getreg("eax")
小工具
  • 例如:to_int()format_address()
  • 获得帮助
    • pyhelp peda
    • pyhelp hex2str
单行/交互式使用
  • gdb-peda$ python print peda.get_vmmap()

1
2
3
4
5
gdb-peda$ python
> status = peda.get_status()
> while status == "BREAKPOINT":
> peda.execute("continue")
> end

外部脚本
1
2
3
4
5
# myscript.py
def myrun(size):
argv = cyclic_pattern(size)
peda.execute("set arg %s" % argv)
peda.execute("run")
1
2
gdb-peda$ source myscript.py
gdb-peda$ python myrun(100)

0x30 pwntools

0x31 安装

  1. 安装binutils:

    1
    2
    3
    4
    5
    git clone https://github.com/Gallopsled/pwntools-binutils
    sudo apt-get install software-properties-common
    sudo apt-add-repository ppa:pwntools/binutils
    sudo apt-get update
    sudo apt-get install binutils-arm-linux-gnu
  2. 安装capstone:

    1
    2
    3
    4
    git clone https://github.com/aquynh/capstone
    cd capstone
    make
    sudo make install
  3. 安装pwntools:

    1
    2
    sudo apt-get install libssl-dev
    sudo pip install pwntools

如果你在使用 Arch Linux,则可以通过 AUR 直接安装,这个包目前是由我维护的,如果有什么问题,欢迎与我交流:

1
2
3
$ yaourt -S python2-pwntools
或者
$ yaourt -S python2-pwntools-git

但是由于 Arch 没有 PPA 源,如果想要支持更多的体系结构(如 arm, aarch64 等),只能手动编译安装相应的 binutils,使用下面的脚本,注意将变量 VARCH 换成你需要的。binutils源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/env bash

V = 2.29 # binutils version
ARCH = arm # target architecture

cd /tmp
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.xz.sig

# gpg --keyserver keys.gnupg.net --recv-keys C3126D3B4AE55E93
# gpg --verify binutils-$V.tar.xz.sig

tar xf binutils-$V.tar.xz

mkdir binutils-build
cd binutils-build

export AR=ar
export AS=as

../binutils-$V/configure \
--prefix=/usr/local \
--target=$ARCH-unknown-linux-gnu \
--disable-static \
--disable-multilib \
--disable-werror \
--disable-nls

make
sudo make install

测试安装是否成功:

1
2
3
4
5
>>> from pwn import *
>>> asm('nop')
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'

0x32 模块简介

Pwntools 分为两个模块,一个是 pwn,简单地使用 from pwn import * 即可将所有子模块和一些常用的系统库导入到当前命名空间中,是专门针对 CTF 比赛的;而另一个模块是 pwnlib,它更推荐你仅仅导入需要的子模块,常用于基于 pwntools 的开发。

下面是 pwnlib 的一些子模块(常用模块和函数加粗显示):

  • adb:安卓调试桥
  • args:命令行魔法参数
  • asm:汇编和反汇编,支持 i386/i686/amd64/thumb 等
  • constants:对不同架构和操作系统的常量的快速访问
  • config:配置文件
  • context:设置运行时变量
  • dynelf:用于远程函数泄露
  • encoders:对 shellcode 进行编码
  • elf:用于操作 ELF 可执行文件和库
  • flag:提交 flag 到服务器
  • fmtstr:格式化字符串利用工具
  • gdb:与 gdb 配合使用
  • libcdb:libc 数据库
  • log:日志记录
  • memleak:用于内存泄露
  • rop:ROP 利用模块,包括 rop 和 srop
  • runner:运行 shellcode
  • shellcraft:shellcode 生成器: system('/bin/sh')
  • term:终端处理
  • timeout:超时处理
  • tubes:能与 sockets, processes, ssh 等进行连接
  • ui:与用户交互
  • useragents:useragent 字符串数据库
  • util:一些实用小工具

0x33 使用 Pwntools

下面我们对常用模块和函数做详细的介绍。

tubes

在一次漏洞利用中,首先当然要与二进制文件或者目标服务器进行交互,这就要用到 tubes 模块。

主要函数在 pwnlib.tubes.tube 中实现,子模块只实现某管道特殊的地方。四种管道和相对应的子模块如下:

  • pwnlib.tubes.process:进程
    • >>> p = process('/bin/sh')
  • pwnlib.tubes.serialtube:串口
  • ·pwnlib.tubes.sock·:套接字
    • >>> r = remote('127.0.0.1', 1080)
    • >>> l = listen(1080)
  • pwnlib.tubes.ssh:SSH
    • >>> s = ssh(host='example.com, user='name', password='passwd')`
  • pwnlib.tubes.tube 中的主要函数:
    • interactive():可同时读写管道,相当于回到 shell 模式进行交互,在取得 shell 之后调用
    • recv(numb=1096, timeout=default):接收指定字节数的数据
    • recvall():接收数据直到 EOF
    • recvline(keepends=True):接收一行,可选择是否保留行尾的 \n
    • recvrepeat(timeout=default):接收数据直到 EOF 或 timeout
    • recvuntil(delims, timeout=default):接收数据直到 delims 出现
    • send(data):发送数据
    • sendline(data):发送一行,默认在行尾加 \n
    • close():关闭管道

下面是一个例子,先使用 listen 开启一个本地的监听端口,然后使用 remote 开启一个套接字管道与之交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
>>> from pwn import *
>>> l = listen()
[x] Trying to bind to 0.0.0.0 on port 0
[x] Trying to bind to 0.0.0.0 on port 0: Trying 0.0.0.0
[+] Trying to bind to 0.0.0.0 on port 0: Done
[x] Waiting for connections on 0.0.0.0:46147
>>> r = remote('localhost', l.lport)
[x] Opening connection to localhost on port 46147
[x] Opening connection to localhost on port 46147: Trying ::1
[x] Opening connection to localhost on port 46147: Trying 127.0.0.1
[+] Opening connection to localhost on port 46147: Done
>>> [+] Waiting for connections on 0.0.0.0:46147: Got connection from 127.0.0.1 on port 38684

>>> c = l.wait_for_connection()
>>> r.send('hello\n')
>>> c.recv()
'hello\n'
>>> r.send('hello\n')
>>> c.recvline()
'hello\n'
>>> r.sendline('hello')
>>> c.recv()
'hello\n'
>>> r.sendline('hello')
>>> c.recvline()
'hello\n'
>>> r.sendline('hello')
>>> c.recvline(keepends=False)
'hello'
>>> r.send('hello world')
>>> c.recvuntil('hello')
'hello'
>>> c.recv()
' world'
>>> c.close()
[*] Closed connection to 127.0.0.1 port 38684
>>> r.close()
[*] Closed connection to localhost port 46147

下面是一个与进程交互的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> p = process('/bin/sh')
[x] Starting local process '/bin/sh'
[+] Starting local process '/bin/sh': pid 26481
>>> p.sendline('sleep 3; echo hello world;')
>>> p.recvline(timeout=1)
'hello world\n'
>>> p.sendline('sleep 3; echo hello world;')
>>> p.recvline(timeout=1)
''
>>> p.recvline(timeout=5)
'hello world\n'
>>> p.interactive()
[*] Switching to interactive mode
whoami
firmy
^C[*] Interrupted
>>> p.close()
[*] Stopped process '/bin/sh' (pid 26481)

shellcraft

使用 shellcraft 模块可以生成对应架构和 shellcode 代码,直接使用链式调用的方法就可以得到,首先指定体系结构,再指定操作系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> print shellcraft.i386.nop().strip('\n')
nop
>>> print shellcraft.i386.linux.sh()
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push '/bin///sh\x00' */
push 0x68
push 0x732f2f2f
push 0x6e69622f
mov ebx, esp
/* push argument array ['sh\x00'] */
/* push 'sh\x00\x00' */
push 0x1010101
xor dword ptr [esp], 0x1016972
xor ecx, ecx
push ecx /* null terminate */
push 4
pop ecx
add ecx, esp
push ecx /* 'sh\x00' */
mov ecx, esp
xor edx, edx
/* call execve() */
push SYS_execve /* 0xb */
pop eax
int 0x80

shellcraft.sh()

asm

该模块用于汇编和反汇编代码。

体系结构,端序和字长需要在 asm()disasm() 中设置,但为了避免重复,运行时变量最好使用 pwnlib.context 来设置。

汇编:(pwnlib.asm.asm)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> asm('nop')
'\x90'
>>> asm(shellcraft.nop())
'\x90'
>>> asm('nop', arch='arm')
'\x00\xf0 \xe3'
>>> context.arch = 'arm'
>>> context.os = 'linux'
>>> context.endian = 'little'
>>> context.word_size = 32
>>> context
ContextType(arch = 'arm', bits = 32, endian = 'little', os = 'linux')
>>> asm('nop')
'\x00\xf0 \xe3'
>>> asm('mov eax, 1')
'\xb8\x01\x00\x00\x00'
>>> asm('mov eax, 1').encode('hex')
'b801000000'

请注意,这里我们生成了 i386 和 arm 两种不同体系结构的 nop,当你使用不同与本机平台的汇编时,需要安装该平台的 binutils,方法在上面已经介绍过了。

反汇编:(pwnlib.asm.disasm)

1
2
3
4
5
6
7
>>> print disasm('\xb8\x01\x00\x00\x00')
0: b8 01 00 00 00 mov eax,0x1
>>> print disasm('6a0258cd80ebf9'.decode('hex'))
0: 6a 02 push 0x2
2: 58 pop eax
3: cd 80 int 0x80
5: eb f9 jmp 0x0

构建具有指定二进制数据的 ELF 文件:(pwnlib.asm.make_elf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> context.clear(arch='amd64')
>>> context
ContextType(arch = 'amd64', bits = 64, endian = 'little')
>>> bin_sh = asm(shellcraft.amd64.linux.sh())
>>> bin_sh
'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'
>>> filename = make_elf(bin_sh, extract=False)
>>> filename
'/tmp/pwn-asm-V4GWGN/step3-elf'
>>> p = process(filename)
[x] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf'
[+] Starting local process '/tmp/pwn-asm-V4GWGN/step3-elf': pid 28323
>>> p.sendline('echo hello')
>>> p.recv()
'hello\n'

这里我们生成了 amd64,即 64 位 /bin/sh 的 shellcode,配合上 asm 函数,即可通过 make_elf 得到 ELF 文件。

另一个函数 pwnlib.asm.make_elf_from_assembly 允许你构建具有指定汇编代码的 ELF 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>>> asm_sh = shellcraft.amd64.linux.sh()
>>> print asm_sh
/* execve(path='/bin///sh', argv=['sh'], envp=0) */
/* push '/bin///sh\x00' */
push 0x68
mov rax, 0x732f2f2f6e69622f
push rax
mov rdi, rsp
/* push argument array ['sh\x00'] */
/* push 'sh\x00' */
push 0x1010101 ^ 0x6873
xor dword ptr [rsp], 0x1010101
xor esi, esi /* 0 */
push rsi /* null terminate */
push 8
pop rsi
add rsi, rsp
push rsi /* 'sh\x00' */
mov rsi, rsp
xor edx, edx /* 0 */
/* call execve() */
push SYS_execve /* 0x3b */
pop rax
syscall

>>> filename = make_elf_from_assembly(asm_sh)
>>> filename
'/tmp/pwn-asm-ApZ4_p/step3'
>>> p = process(filename)
[x] Starting local process '/tmp/pwn-asm-ApZ4_p/step3'
[+] Starting local process '/tmp/pwn-asm-ApZ4_p/step3': pid 28429
>>> p.sendline('echo hello')
>>> p.recv()
'hello\n'

与上一个函数不同的是,make_elf_from_assembly 直接从汇编生成 ELF 文件,并且保留了所有的符号,例如标签和局部变量等。

elf

该模块用于 ELF 二进制文件的操作,包括符号查找、虚拟内存、文件偏移,以及修改和保存二进制文件等功能。(pwnlib.elf.elf.ELF)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> e = ELF('/bin/cat')
[*] '/bin/cat'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
>>> print hex(e.address)
0x400000
>>> print hex(e.symbols['write'])
0x401680
>>> print hex(e.got['write'])
0x60b070
>>> print hex(e.plt['write'])
0x401680

上面的代码分别获得了 ELF 文件装载的基地址、函数地址、GOT 表地址和 PLT 表地址。

我们常常用它打开一个 libc.so,从而得到 system 函数的位置,这在 CTF 中是非常有用的:

1
2
3
4
5
6
7
8
9
>>> e = ELF('/usr/lib/libc.so.6')
[*] '/usr/lib/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
>>> print hex(e.symbols['system'])
0x42010

我们甚至可以修改 ELF 文件的代码:

1
2
3
4
5
6
7
>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)
'ELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(file('/tmp/quiet-cat','rb').read(1))
' 0: c3 ret'

下面是一些常用函数:

  • asm(address, assembly):汇编指定指令并插入到 ELF 的指定地址处,需要使用 ELF.save() 保存
  • bss(offset):返回 .bss 段加上 offset 后的地址
  • checksec():打印出文件使用的安全保护
  • disable_nx():关闭 NX
  • disasm(address, n_bytes):返回对指定虚拟地址进行反汇编后的字符串
  • offset_to_vaddr(offset):将指定偏移转换为虚拟地址
  • vaddr_to_offset(address):将指定虚拟地址转换为文件偏移
  • read(address, count):从指定虚拟地址读取 count 个字节的数据
  • write(address, data):在指定虚拟地址处写入 data
  • section(name):获取 name 段的数据
  • debug():使用 gdb.debug() 进行调试

最后还要注意一下 pwnlib.elf.corefile,它用于处理核心转储文件(Core Dump),当我们在写利用代码时,核心转储文件是非常有用的,关于它更详细的内容已经在前面 Linux基础一章中讲过,这里我们还是使用那一章中的示例代码,但使用 pwntools 来操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>>> core = Corefile('/tmp/core-a.out-30555-1507796886')
[x] Parsing corefile...
[*] '/tmp/core-a.out-30555-1507796886'
Arch: i386-32-little
EIP: 0x565cd57b
ESP: 0x4141413d
Exe: '/home/firmy/a.out' (0x565cd000)
Fault: 0x4141413d
[+] Parsing corefile...: Done
>>> core.registers
{'xds': 43, 'eip': 1448924539, 'xss': 43, 'esp': 1094795581, 'xgs': 99, 'edi': 0, 'orig_eax': 4294967295, 'xcs': 35, 'eax': 1, 'ebp': 1094795585, 'xes': 43, 'eflags': 66182, 'edx': 4151195744, 'ebx': 1094795585, 'xfs': 0, 'esi': 4151189032, 'ecx': 1094795585}
>>> print core.maps
565cd000-565ce000 r-xp 1000 /home/firmy/a.out
565ce000-565cf000 r--p 1000 /home/firmy/a.out
565cf000-565d0000 rw-p 1000 /home/firmy/a.out
57b3c000-57b5e000 rw-p 22000
f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so
f76df000-f76e0000 ---p 1000 /usr/lib32/libc-2.26.so
f76e0000-f76e2000 r--p 2000 /usr/lib32/libc-2.26.so
f76e2000-f76e3000 rw-p 1000 /usr/lib32/libc-2.26.so
f76e3000-f76e6000 rw-p 3000
f7722000-f7724000 rw-p 2000
f7724000-f7726000 r--p 2000 [vvar]
f7726000-f7728000 r-xp 2000 [vdso]
f7728000-f774d000 r-xp 25000 /usr/lib32/ld-2.26.so
f774d000-f774e000 r--p 1000 /usr/lib32/ld-2.26.so
f774e000-f774f000 rw-p 1000 /usr/lib32/ld-2.26.so
ffe37000-ffe58000 rw-p 21000 [stack]
>>> print hex(core.fault_addr)
0x4141413d
>>> print hex(core.pc)
0x565cd57b
>>> print core.libc
f7510000-f76df000 r-xp 1cf000 /usr/lib32/libc-2.26.so

dynelf

1
pwnlib.dynelf.DynELF

该模块是专门用来应对无 libc 情况下的漏洞利用。它首先找到 glibc 的基地址,然后使用符号表和字符串表对所有符号进行解析,直到找到我们需要的函数的符号。

fmtstr

1
pwnlib.fmtstr.FmtStr`,`pwnlib.fmtstr.fmtstr_payload

该模块用于格式化字符串漏洞的利用,格式化字符串漏洞是 CTF 中一种常见的题型,我们会在后面的章节中详细讲述,关于该模块的使用也会留到那儿。

gdb

1
pwnlib.gdb

在写漏洞利用的时候,常常需要使用 gdb 动态调试,该模块就提供了这方面的支持。

两个常用函数:

  • gdb.attach(target, gdbscript=None):在一个新终端打开 gdb 并 attach 到指定 PID 的进程,或是一个 pwnlib.tubes 对象。
  • gdb.debug(args, gdbscript=None):在新终端中使用 gdb 加载一个二进制文件。

上面两种方法都可以在开启的时候传递一个脚本到 gdb,可以很方便地做一些操作,如自动设置断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# attach to pid 1234
gdb.attach(1234)

# attach to a process
bash = process('bash')
gdb.attach(bash, '''
set follow-fork-mode child
continue
''')
bash.sendline('whoami')
# Create a new process, and stop it at 'main'
io = gdb.debug('bash', '''
# Wait until we hit the main executable's entry point
break _start
continue

# Now set breakpoint on shared library routines
break malloc
break free
continue
''')

memleak

1
pwnlib.memleak

该模块用于内存泄露的利用。可用作装饰器。它会将泄露的内存缓存起来,在漏洞利用过程中可能会用到。

util

1
pwnlib.util.packing`, `pwnlib.util.cyclic

util 其实是一些模块的集合,包含了一些实用的小工具。这里主要介绍两个,packing 和 cyclic。

packing 模块用于将整数打包和解包,它简化了标准库中的 struct.packstruct.unpack 函数,同时增加了对任意宽度整数的支持。

使用 p32, p64, u32, u64 函数分别对 32 位和 64 位整数打包和解包,也可以使用 pack() 自己定义长度,另外添加参数 endiansigned 设置端序和是否带符号。

1
2
3
4
5
6
7
8
9
10
11
12
>>> p32(0xdeadbeef)
'\xef\xbe\xad\xde'
>>> p64(0xdeadbeef).encode('hex')
'efbeadde00000000'
>>> p32(0xdeadbeef, endian='big', sign='unsigned')
'\xde\xad\xbe\xef'
>>> u32('1234')
875770417
>>> u32('1234', endian='big', sign='signed')
825373492
>>> u32('\xef\xbe\xad\xde')
3735928559

cyclic 模块在缓冲区溢出中很有用,它帮助生成模式字符串,然后查找偏移,以确定返回地址。

1
2
3
4
>>> cyclic(20)
'aaaabaaacaaadaaaeaaa'
>>> cyclic_find(0x61616162)
4

0x34 Pwntools 在 CTF 中的运用

可以在下面的仓库中找到大量使用 pwntools 的 write-up: pwntools-write-ups


0x40 OllyDbg

0x41 OllyDbg调试器基本操作

OD界面介绍
OD界面介绍

① 加载程序

  1. 加载程序:OllyDbg可以用两种方式加载目标程序进行调试,一种是通过CreateProcess创建进程;另外一种是利用DebugActiveProcess函数将调试器捆绑到一个正在运行的进程上。 2.利用CreateProcess创建进程:单击菜单“File/Open”或者按快捷键F3打开目标文件,这样会调用CreateProcess创建一个用以调试的进进程。OllyDbg将接收目标进程发送的调试事件,并对相应的对调试事件进行处理。
  2. OllyDbg除了直接加载目标程序,也支持带参数的程序,方法是:在打开对话框中的“Arguments”栏中输入参数。如下图所示:
    1543164648498
  1. 将OllyDbg附加到一个正在运行的进程:OllyDbg一个实用的功能是可以调试正在运行的程序,这个功能称为”附加(Attach)“。其原理是利用DebugActiveProcess函数将调试器捆绑到一个正在运行的进程上,如果执行成功,则类似利用CreateProcess创建新进程。
  2. 单击菜单”File/Attach“打开附加对话框。选择正在运行的目标进程,单击Attach按钮即可附加到目标进程。附加后,目标程序会暂停在Ntdll.dll中的DbgBreakPoint处,按下F9或者shift+F9就可以让程序继续运行。接着对目标程序进行调试分析。
  3. 如果进程是隐藏的,OllyDbg有一个-p启动参数,只要得到进程的pid就可以附加了。可以利用IceSword等工具获得隐藏进程的PID,然后在控制台下用 -p 参数附加即可。注意,pid的值是十进制
  4. 单步跟踪:调试器一个最基本的功能就是动态跟踪,OllyDbg在菜单“Debug”里有控制运行的命令,各个菜单项有对应的快捷键。
OllyDbg功能键 功能
F7 单步步进,遇到Call跟进
F8 单步步过,遇到CALL跳过,不跟进
Ctrl+F9 直到出现RET指令时中断
Alt+F9 若进入系统领空,此命令可以回到应用程序领空。
F9 运行程序
  1. F8键在调试中用的很频繁,可以一句句地单步执行汇编指令,遇到CALL指令不会跟进,而是路过。而F7的差别就主要在于,遇到CALL、LOOP等指令是会跟进去

  2. 如果是重复多次调用F7或者F8时,OllyDbg提供了“Ctrl+F7”和“Ctrl+F8”快捷键,直到按下Esc键、F12键或者遇到断点时停止。

  3. 位于某个CALL中,想回到调用这个CALL的地方时,可以按下“Ctrl+F9”快捷键,执行“执行到返回(Execute till return)”功能。OllyDbg就会停在遇到的第一个返回命令(RET、RETF或者IRET。可以设置为停在刚执行完RET等的位置).

  4. 而如果跟进系统DLL提供的API函数中,此时想返回到应用程序领空里,可以按快捷键“Alt+F9”执行“Execute till user code”(执行到用户代码)命令。

② 设置断点

  1. 设置断点:断点是调试器的一个重要功能,可以让程序中断在需要的地方,从而方便分析。最常用的断点是INT3断点,其原理是OllyDbg将断点地址处代码修改为INT3 指令。设置断点的快捷键是F2再按一次F2取消断点;也可以用鼠标双击“Hex dump”列来设置断点。
  2. 调试分析:字符串通常利用Window文件框输入。为了检查输入字符,程序通常采用下面的函数将文本框中的内容读取出来。
字符串读取函数 32位(ANSI版本) 32位(Unicode版本)
GetDlgItemText GetDlgItemTextA GetDlgItemTextW
GetWindowText GetWindowTextA GetWindowTextW
  1. 一般情况下事先不知道程序具体会调用什么函数来取字符串,只好多试几次(经验比较重要)

  2. 还可以利用输入表设置断点。在OllyDbg中,按下"Ctrl+N"这个快捷键打开应用程序输入表,会发现应用程序调用的其他模块的导出函数,找到相应的函数后,通过Enter键即可切换到相应的代码,接下来按F2设置断点

  3. 另一种方法:使用“Ctrl+G“键打开跟随表达式的窗口,输入GetDlgItemTextA字符串,单击OK按钮,会来到GetDlgItemTextA函数入口处,在0x77D6AC1E处下断点.

  4. 在阅读代码时:

  • 要搞清楚各个API函数的定义(查看相关API手册)。
  • API函数基本采用的是**_stdcall调用约定,即函数入口参数按从右到左的顺序入栈,由被调用者清理栈中参数,返回值放在eax中**。因此,对相关的API函数要分析其前的push指令,这些指令将参数放进堆栈以供API调用。整个过程要关注堆栈的变化。
  • C代码中的子程序采用的是C调用约定,参数入口按照从右到左的顺序入栈,由调用者清理栈总参数。

③ 找关键比较

  • 找到关键比较位置:程序在进行验证时,一般会在关键位置进行比较,如果验证通过,则程序会跳转到正确的流程;如果程序验证不通过,则会跳转到错误的流程执行,在进行破解时一般只要找到比较的关键位置,将代码修改后然后保存到文件中即可完成对程序的暴力破解。
  • 目前修改的是内存中的数据,为了使修改一直有效,就必须将这个变化写进磁盘文件中。
  • 保存方法:用鼠标选中修改过的代码,单击鼠标右键,执行“Copy to excutable/Selection”命令,将打开文件编辑窗口,单击鼠标右键,执行命令“Save File ”即可将修改保存到文件中。
  • 像这种通过修改文件然后屏蔽程序某些功能或改变程序流程,使程序保护方法失效的方法称为patch(补丁)或“爆破”
文章目录
  1. 1. 0x00 简介
  2. 2. 0x10 IDA Pro
    1. 2.1. 0x11 快捷键
    2. 2.2. 0x12 常用插件
    3. 2.3. 0x13 常用技巧
      1. 2.3.1. ① 堆栈不平衡
      2. 2.3.2. ② 分析选项
      3. 2.3.3. ③ 修改数据
      4. 2.3.4. ④ 创建结构体
      5. 2.3.5. ⑤ 解析C头文件
      6. 2.3.6. ⑥ 定义数组
      7. 2.3.7. ⑦ 动态调试 & 远程调试
    4. 2.4. 0x14 IDA Python
  3. 3. 0x20 GDB
    1. 3.1. 0x21 gdb 的组成架构
    2. 3.2. 0x22 gdb 基本工作原理
      1. 3.2.1. gdb 的三种调试方式
      2. 3.2.2. 断点的实现
    3. 3.3. 0x23 gdb 基本操作
      1. 3.3.1. break -- b
      2. 3.3.2. info
      3. 3.3.3. disable -- dis
      4. 3.3.4. enable
      5. 3.3.5. clear
      6. 3.3.6. delete -- d
      7. 3.3.7. tbreak
      8. 3.3.8. watch
      9. 3.3.9. step -- s
      10. 3.3.10. reverse-step
      11. 3.3.11. next -- n
      12. 3.3.12. reverse-next
      13. 3.3.13. return
      14. 3.3.14. finish -- fin
      15. 3.3.15. until -- u
      16. 3.3.16. continue -- c
      17. 3.3.17. print -- p
      18. 3.3.18. x
      19. 3.3.19. display
      20. 3.3.20. disassemble -- disas
      21. 3.3.21. undisplay
      22. 3.3.22. disable display
      23. 3.3.23. enable display
      24. 3.3.24. help -- h
      25. 3.3.25. attach
      26. 3.3.26. run -- r
      27. 3.3.27. backtrace -- bt
      28. 3.3.28. ptype
      29. 3.3.29. set follow-fork-mode
      30. 3.3.30. thread apply all bt
      31. 3.3.31. generate-core-file
      32. 3.3.32. directory -- dir
    4. 3.4. 0x24 gdb-peda (gdb-gef)
      1. 3.4.1. 安装
      2. 3.4.2. peda命令
        1. 3.4.2.1. aslr
        2. 3.4.2.2. asmsearch
        3. 3.4.2.3. checksec
        4. 3.4.2.4. cmpmem
        5. 3.4.2.5. context
        6. 3.4.2.6. crashdump
        7. 3.4.2.7. deactive
        8. 3.4.2.8. distance
        9. 3.4.2.9. dumpargs
        10. 3.4.2.10. dumpmem
        11. 3.4.2.11. dumprop
        12. 3.4.2.12. eflags
        13. 3.4.2.13. elfsymbol
        14. 3.4.2.14. gennop
        15. 3.4.2.15. getfile
        16. 3.4.2.16. getpid
        17. 3.4.2.17. goto
        18. 3.4.2.18. help
        19. 3.4.2.19. hexdump
        20. 3.4.2.20. hexprint
        21. 3.4.2.21. jmpcall
        22. 3.4.2.22. loadmem
        23. 3.4.2.23. lookup
        24. 3.4.2.24. nearpc
        25. 3.4.2.25. nextcall
        26. 3.4.2.26. nextjmp
        27. 3.4.2.27. nxtest
        28. 3.4.2.28. patch
        29. 3.4.2.29. pattern
        30. 3.4.2.30. payload
        31. 3.4.2.31. pdisass
        32. 3.4.2.32. pltbreak
        33. 3.4.2.33. procinfo
        34. 3.4.2.34. profile
        35. 3.4.2.35. pyhelp
        36. 3.4.2.36. pshow
        37. 3.4.2.37. pset
        38. 3.4.2.38. readelf
        39. 3.4.2.39. refsearch
        40. 3.4.2.40. reload
        41. 3.4.2.41. ropgadget
        42. 3.4.2.42. ropsearch
        43. 3.4.2.43. searchmem|find
        44. 3.4.2.44. set
        45. 3.4.2.45. sgrep
        46. 3.4.2.46. shellcode
        47. 3.4.2.47. show
        48. 3.4.2.48. skeleton
        49. 3.4.2.49. skipi
        50. 3.4.2.50. snapshot
        51. 3.4.2.51. start
        52. 3.4.2.52. stepuntil
        53. 3.4.2.53. strings
        54. 3.4.2.54. substr
        55. 3.4.2.55. telescope
        56. 3.4.2.56. tracecall
        57. 3.4.2.57. traceinst
        58. 3.4.2.58. unptrace
        59. 3.4.2.59. utils
        60. 3.4.2.60. vmmap
        61. 3.4.2.61. waitfor
        62. 3.4.2.62. xinfo
        63. 3.4.2.63. xormem
        64. 3.4.2.64. xprint
        65. 3.4.2.65. xrefs
        66. 3.4.2.66. xuntil
      3. 3.4.3. 使用 PEDA 和 Python 编写 gdb 脚本
        1. 3.4.3.1. pedacmd
        2. 3.4.3.2. peda
        3. 3.4.3.3. 小工具
        4. 3.4.3.4. 单行/交互式使用
        5. 3.4.3.5. 外部脚本
  4. 4. 0x30 pwntools
    1. 4.1. 0x31 安装
    2. 4.2. 0x32 模块简介
    3. 4.3. 0x33 使用 Pwntools
      1. 4.3.1. tubes
      2. 4.3.2. shellcraft
      3. 4.3.3. asm
      4. 4.3.4. elf
      5. 4.3.5. dynelf
      6. 4.3.6. fmtstr
      7. 4.3.7. gdb
      8. 4.3.8. memleak
      9. 4.3.9. util
    4. 4.4. 0x34 Pwntools 在 CTF 中的运用
  5. 5. 0x40 OllyDbg
    1. 5.1. 0x41 OllyDbg调试器基本操作
      1. 5.1.1. ① 加载程序
      2. 5.1.2. ② 设置断点
      3. 5.1.3. ③ 找关键比较
,