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 | 转换局部变量为结构体 |
- IDAView下使用小键盘“-”,“+”快捷方式可以在代码同关系图之间切换。
- 使用快捷键”*”把变量重定义为数组.
- ALT+T:搜索字符串(文本搜索)
- 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 发生错误。
解决办法步骤如下:
- 用 Option->General->Disassembly, 将选项 Stack pointer 打钩
- 仔细观察每条 call sub_xxxxxx 前后的堆栈指针是否平衡
- 有时还要看被调用的 sub_xxxxxx 内部的堆栈情况,主要是看入栈的参数与 ret xx 是否匹配
- 注意观察 jmp 指令前后的堆栈是否有变化
- 有时用 Edit->Functions->Edit function...,然后点击 OK 刷一下函数定义
② 分析选项
选择File菜单下的Open,打开想要逆向的可执行文件,会显示一个Load a new file的界面。这里可以选择:
- 程序的类型;
- 处理器的类型;
- 加载的段地址和偏移量;
- 是否允许分析;
- 一些加载选项;
- 内核和处理器的一些选项;
- windows系统dll所在的目录。
上图默认选择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…]
④ 创建结构体
在IDA中打开Structures,快捷键是
shift+ F9
,然后按insert键,插入新结构体:在
ends
后按d
键,添加相应的成员,然后选中成员名,按N
进行修改,选中类型,按d
进行更改类型,构造完结构题以后,回到代码窗口在变量堆栈结构里,
Edit▶Struct Var(ALT+Q)
命令显示一组已知的结构体跳到要定义结构体的变量, 按
y
, 把它改成(新定义结构体名) *
.
⑤ 解析C头文件
解析头文件,可以使用File▶Load File▶Parse C HeaderFile(文件▶加载文件▶解析C头文件)选择你想要解析的头文件。如果一切正常,IDA会通知你Compilation successful(编译完成)。如果解析器遇到任何问题,IDA将会在输出窗口中显示错误消息
⑥ 定义数组
当看到这样的多个变量:
1 | v2 = 102; |
可以考虑只定义一个数组, 按*
, 会弹出如下窗口:
要特别注意array size
.
⑦ 动态调试 & 远程调试
如果在win系统上调试
exe
文件, 则可以直接用本地debugger调试, 按F9
之后会跳出如下界面:选
Local ...
. 即可动态调试. (调试时记得在关键点按F2
下断点)如果在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
:Step3: 在Debug options勾选如上选项:
Step4: Hostname为虚拟机ip,Password为空
如果是so文件, 跟上述方法差不多, 需要额外注意adb shell下的端口转发.
0x14 IDA Python
1 | import ida_bytes |
IDA Python API: https://www.hex-rays.com/products/ida/support/idapython_docs/
0x20 GDB
0x21 gdb 的组成架构
0x22 gdb 基本工作原理
gdb 通过系统调用 ptrace
来接管一个进程的执行。ptrace 系统调用提供了一种方法使得父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。它主要用来实现断点调试和系统调用跟踪。ptrace 系统调用的原型如下: 1
2
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:继续运行之前停止的子进程。可同时向子进程交付指定的信号。
- PTRACE_TRACEME:表示此进程将被父进程跟踪,任何信号(除了
gdb 的三种调试方式
- 运行并调试一个新进程
- 运行 gdb,通过命令行或
file
命令指定目标程序。 - 输入
run
命令, gdb 执行下面的操作:- 通过
fork()
系统调用创建一个新进程 - 在新创建的子进程中执行操作:
ptrace(PTRACE_TRACEME, 0, 0, 0)
- 在子进程中通过
execv()
系统调用加载用户指定的可执行文件
- 通过
- 运行 gdb,通过命令行或
- attach 并调试一个已经运行的进程
- 用户确定需要进行调试的进程 PID
- 运行 gdb,输入
attach <pid>
,gdb 将对指定进程执行操作:ptrace(PTRACE_ATTACH, pid, 0, 0)
- 远程调试目标机上新创建的进程
- gdb 运行在调试机上,gdbserver 运行在目标机上,两者之间的通信数据格式由 gdb 远程串行协议(Remote Serial Protocol)——定义RSP协议数据的基本格式为:
$..........#xx
- gdbserver 的启动方式相当于运行并调试一个新创建的进程
- gdb 运行在调试机上,gdbserver 运行在目标机上,两者之间的通信数据格式由 gdb 远程串行协议(Remote Serial Protocol)——定义RSP协议数据的基本格式为:
注意,在你将 gdb attach 到一个进程时,可能会出现这样的问题:
1 | gdb-peda$ attach 9091 |
这是因为开启了内核参数 ptrace_scope
:
1 | $ cat /proc/sys/kernel/yama/ptrace_scope |
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>
在源码文件filename
的line
行处打断点。break <filename:function>
在源码文件filename
的function
函数入口处打断点。break <address>
在程序指令的地址处打断点。break ... if <cond>
设置条件断点,...
代表上述参数之一(或无参数),cond
为条件表达式,仅在cond
值非零时停住程序。
info
info breakpoints -- i b
查看断点,观察点和捕获点的列表。info breakpoints [list…]
info break [list…]
list…
用来指定若干个断点的编号(可省略),可以是2
,1-3
,2 5
等。
info display
打印自动显示的表达式列表,每个表达式都带有项目编号,但不显示其值。info reg
显示当前寄存器信息。info threads
打印出所有线程的信息,包含 Thread ID、Target ID 和 Frame。info frame
打印出指定栈帧的详细信息。info proc
查看 proc 里的进程信息。
disable -- dis
禁用断点,参数使用空格分隔。不带参数时禁用所有断点。
disable [breakpoints] [list…]
breakpoints
是disable
的子命令(可省略),list…
同info breakpoints
中的描述。
enable
启用断点,参数使用空格分隔。不带参数时启用所有断点。
enable [breakpoints] [list…]
启用指定的断点(或所有定义的断点)。enable [breakpoints] once list…
临时启用指定的断点。GDB 在停止您的程序后立即禁用这些断点。enable [breakpoints] delete list…
使指定的断点启用一次,然后删除。一旦您的程序停止,GDB 就会删除这些断点。等效于用tbreak
设置的断点。
breakpoints
同 disable
中的描述。
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 次到达之前不会中断)。
print -- p
求表达式 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
用于指定显示格式。对于格式 i
或 s
,或者包括单位大小或单位数量,将表达式 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 | sudo apt-get install nasm micro-inetd |
安装 peda:
1 | git clone https://github.com/longld/peda.git ~/peda |
如果系统为 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 | assemble $pc |
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 contextcontext_register
-- Display register information of current execution contextcontext_stack
- - Display stack of current execution contextcontext 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 patternpattern_create
-- Generate a cyclic patternpattern_env
-- Set environment variable with a cyclic patterpattern_offset
-- Search for offset of a value in cyclic patternpattern_patch
-- Write a cyclic pattern to memorypattern_search
-- Search a cyclic pattern in registers and memorypattern 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 searchsession
-- 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
5gdb-peda$ python
> status = peda.get_status()
> while status == "BREAKPOINT":
> peda.execute("continue")
> end
外部脚本
1 | # myscript.py |
1 | gdb-peda$ source myscript.py |
0x30 pwntools
0x31 安装
安装binutils:
1
2
3
4
5git 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安装capstone:
1
2
3
4git clone https://github.com/aquynh/capstone
cd capstone
make
sudo make install安装pwntools:
1
2sudo apt-get install libssl-dev
sudo pip install pwntools
如果你在使用 Arch Linux,则可以通过 AUR 直接安装,这个包目前是由我维护的,如果有什么问题,欢迎与我交流:
1 | $ yaourt -S python2-pwntools |
但是由于 Arch 没有 PPA 源,如果想要支持更多的体系结构(如 arm, aarch64 等),只能手动编译安装相应的 binutils,使用下面的脚本,注意将变量 V
和 ARCH
换成你需要的。binutils源码
1 |
|
测试安装是否成功:
1 | from pwn import * |
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()
:接收数据直到 EOFrecvline(keepends=True)
:接收一行,可选择是否保留行尾的\n
recvrepeat(timeout=default)
:接收数据直到 EOF 或 timeoutrecvuntil(delims, timeout=default)
:接收数据直到 delims 出现send(data)
:发送数据sendline(data)
:发送一行,默认在行尾加\n
close()
:关闭管道
下面是一个例子,先使用 listen 开启一个本地的监听端口,然后使用 remote 开启一个套接字管道与之交互:
1 | >>> from pwn import * |
下面是一个与进程交互的例子:
1 | >>> p = process('/bin/sh') |
shellcraft
使用 shellcraft 模块可以生成对应架构和 shellcode 代码,直接使用链式调用的方法就可以得到,首先指定体系结构,再指定操作系统:
1 | >>> print shellcraft.i386.nop().strip('\n') |
asm
该模块用于汇编和反汇编代码。
体系结构,端序和字长需要在 asm()
和 disasm()
中设置,但为了避免重复,运行时变量最好使用 pwnlib.context
来设置。
汇编:(pwnlib.asm.asm
)
1 | >>> asm('nop') |
请注意,这里我们生成了 i386 和 arm 两种不同体系结构的 nop
,当你使用不同与本机平台的汇编时,需要安装该平台的 binutils,方法在上面已经介绍过了。
反汇编:(pwnlib.asm.disasm
)
1 | >>> print disasm('\xb8\x01\x00\x00\x00') |
构建具有指定二进制数据的 ELF 文件:(pwnlib.asm.make_elf
)
1 | >>> context.clear(arch='amd64') |
这里我们生成了 amd64,即 64 位 /bin/sh
的 shellcode,配合上 asm 函数,即可通过 make_elf
得到 ELF 文件。
另一个函数 pwnlib.asm.make_elf_from_assembly
允许你构建具有指定汇编代码的 ELF 文件:
1 | >>> asm_sh = shellcraft.amd64.linux.sh() |
与上一个函数不同的是,make_elf_from_assembly
直接从汇编生成 ELF 文件,并且保留了所有的符号,例如标签和局部变量等。
elf
该模块用于 ELF 二进制文件的操作,包括符号查找、虚拟内存、文件偏移,以及修改和保存二进制文件等功能。(pwnlib.elf.elf.ELF
)
1 | >>> e = ELF('/bin/cat') |
上面的代码分别获得了 ELF 文件装载的基地址、函数地址、GOT 表地址和 PLT 表地址。
我们常常用它打开一个 libc.so,从而得到 system 函数的位置,这在 CTF 中是非常有用的:
1 | >>> e = ELF('/usr/lib/libc.so.6') |
我们甚至可以修改 ELF 文件的代码:
1 | >>> e = ELF('/bin/cat') |
下面是一些常用函数:
asm(address, assembly)
:汇编指定指令并插入到 ELF 的指定地址处,需要使用 ELF.save() 保存bss(offset)
:返回.bss
段加上offset
后的地址checksec()
:打印出文件使用的安全保护disable_nx()
:关闭 NXdisasm(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 | >>> core = Corefile('/tmp/core-a.out-30555-1507796886') |
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 | # attach to pid 1234 |
memleak
1 | pwnlib.memleak |
该模块用于内存泄露的利用。可用作装饰器。它会将泄露的内存缓存起来,在漏洞利用过程中可能会用到。
util
1 | pwnlib.util.packing`, `pwnlib.util.cyclic |
util 其实是一些模块的集合,包含了一些实用的小工具。这里主要介绍两个,packing 和 cyclic。
packing 模块用于将整数打包和解包,它简化了标准库中的 struct.pack
和 struct.unpack
函数,同时增加了对任意宽度整数的支持。
使用 p32
, p64
, u32
, u64
函数分别对 32 位和 64 位整数打包和解包,也可以使用 pack()
自己定义长度,另外添加参数 endian
和 signed
设置端序和是否带符号。
1 | >>> p32(0xdeadbeef) |
cyclic 模块在缓冲区溢出中很有用,它帮助生成模式字符串,然后查找偏移,以确定返回地址。
1 | >>> cyclic(20) |
0x34 Pwntools 在 CTF 中的运用
可以在下面的仓库中找到大量使用 pwntools 的 write-up: pwntools-write-ups
0x40 OllyDbg
0x41 OllyDbg调试器基本操作
① 加载程序
- 加载程序:OllyDbg可以用两种方式加载目标程序进行调试,一种是通过CreateProcess创建进程;另外一种是利用
DebugActiveProcess
函数将调试器捆绑到一个正在运行的进程上。 2.利用CreateProcess创建进程:单击菜单“File/Open”或者按快捷键F3打开目标文件,这样会调用CreateProcess
创建一个用以调试的进进程。OllyDbg将接收目标进程发送的调试事件,并对相应的对调试事件进行处理。 - OllyDbg除了直接加载目标程序,也支持带参数的程序,方法是:在打开对话框中的“Arguments”栏中输入参数。如下图所示:
- 将OllyDbg附加到一个正在运行的进程:OllyDbg一个实用的功能是可以调试正在运行的程序,这个功能称为”附加(Attach)“。其原理是利用
DebugActiveProcess
函数将调试器捆绑到一个正在运行的进程上,如果执行成功,则类似利用CreateProcess
创建新进程。 - 单击菜单”File/Attach“打开附加对话框。选择正在运行的目标进程,单击Attach按钮即可附加到目标进程。附加后,目标程序会暂停在Ntdll.dll中的DbgBreakPoint处,按下F9或者shift+F9就可以让程序继续运行。接着对目标程序进行调试分析。
- 如果进程是隐藏的,OllyDbg有一个
-p
启动参数,只要得到进程的pid就可以附加了。可以利用IceSword等工具获得隐藏进程的PID,然后在控制台下用 -p 参数附加即可。注意,pid的值是十进制 - 单步跟踪:调试器一个最基本的功能就是动态跟踪,OllyDbg在菜单“Debug”里有控制运行的命令,各个菜单项有对应的快捷键。
OllyDbg功能键 | 功能 |
---|---|
F7 | 单步步进,遇到Call跟进 |
F8 | 单步步过,遇到CALL跳过,不跟进 |
Ctrl+F9 | 直到出现RET指令时中断 |
Alt+F9 | 若进入系统领空,此命令可以回到应用程序领空。 |
F9 | 运行程序 |
F8键在调试中用的很频繁,可以一句句地单步执行汇编指令,遇到CALL指令不会跟进,而是路过。而F7的差别就主要在于,遇到CALL、LOOP等指令是会跟进去。
如果是重复多次调用F7或者F8时,OllyDbg提供了“Ctrl+F7”和“Ctrl+F8”快捷键,直到按下Esc键、F12键或者遇到断点时停止。
当位于某个CALL中,想回到调用这个CALL的地方时,可以按下“Ctrl+F9”快捷键,执行“执行到返回(Execute till return)”功能。OllyDbg就会停在遇到的第一个返回命令(RET、RETF或者IRET。可以设置为停在刚执行完RET等的位置).
而如果跟进系统DLL提供的API函数中,此时想返回到应用程序领空里,可以按快捷键“Alt+F9”执行“Execute till user code”(执行到用户代码)命令。
② 设置断点
- 设置断点:断点是调试器的一个重要功能,可以让程序中断在需要的地方,从而方便分析。最常用的断点是INT3断点,其原理是OllyDbg将断点地址处代码修改为INT3 指令。设置断点的快捷键是F2,再按一次F2取消断点;也可以用鼠标双击“Hex dump”列来设置断点。
- 调试分析:字符串通常利用Window文件框输入。为了检查输入字符,程序通常采用下面的函数将文本框中的内容读取出来。
字符串读取函数 | 32位(ANSI版本) | 32位(Unicode版本) |
---|---|---|
GetDlgItemText | GetDlgItemTextA | GetDlgItemTextW |
GetWindowText | GetWindowTextA | GetWindowTextW |
一般情况下事先不知道程序具体会调用什么函数来取字符串,只好多试几次(经验比较重要)
还可以利用输入表设置断点。在OllyDbg中,按下"Ctrl+N"这个快捷键打开应用程序输入表,会发现应用程序调用的其他模块的导出函数,找到相应的函数后,通过Enter键即可切换到相应的代码,接下来按F2设置断点。
另一种方法:使用“Ctrl+G“键打开跟随表达式的窗口,输入GetDlgItemTextA字符串,单击OK按钮,会来到GetDlgItemTextA函数入口处,在0x77D6AC1E处下断点.
在阅读代码时:
- 要搞清楚各个API函数的定义(查看相关API手册)。
- API函数基本采用的是**_stdcall调用约定,即函数入口参数按从右到左的顺序入栈,由被调用者清理栈中参数,返回值放在eax中**。因此,对相关的API函数要分析其前的push指令,这些指令将参数放进堆栈以供API调用。整个过程要关注堆栈的变化。
- C代码中的子程序采用的是C调用约定,参数入口按照从右到左的顺序入栈,由调用者清理栈总参数。
③ 找关键比较
- 找到关键比较位置:程序在进行验证时,一般会在关键位置进行比较,如果验证通过,则程序会跳转到正确的流程;如果程序验证不通过,则会跳转到错误的流程执行,在进行破解时一般只要找到比较的关键位置,将代码修改后然后保存到文件中即可完成对程序的暴力破解。
- 目前修改的是内存中的数据,为了使修改一直有效,就必须将这个变化写进磁盘文件中。
- 保存方法:用鼠标选中修改过的代码,单击鼠标右键,执行“Copy to excutable/Selection”命令,将打开文件编辑窗口,单击鼠标右键,执行命令“Save File ”即可将修改保存到文件中。
- 像这种通过修改文件然后屏蔽程序某些功能或改变程序流程,使程序保护方法失效的方法称为patch(补丁)或“爆破”