Description

    드림이는 2030년에 열리는 드림 콘서트의 티켓을 등록하고 싶지만, 티켓 등록 프로그램은 아직 시간이 되지 않았다고 합니다. 드림이의 빠른 티켓 등록을 위해 프로그램을 분석하고 티켓 등록 키를 알아내주세요!

    올바른 티켓 등록 키에 DH{}를 감싸서 인증해주세요.

    Files

    ubuntu@WSL2:~/CTF/dreamhack.io$ tree ./Times
    ./Times
    └── times
    
    0 directories, 1 file
    
    ubuntu@WSL2:~/CTF/dreamhack.io$ file ./Times/times
    ./Times/times: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=51e6516fbaf2154323ed5780c55beac8684f543e, for GNU/Linux 3.2.0, stripped

    64비트 리눅스용 실행 파일 하나.

    실행

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times
    Not yet !!! Please wait more time.

    실행하려고하면, 위와 같은 문구를 띄우면 더이상 진행되지 않는다.

    분석

    이번에는 후킹 라이브러리를 만들어 문제를 풀어나가보려고 한다.

    __int64 sub_17D0()
    {
      int v0; // edx
      __int64 result; // rax
    
      if ( time(0LL) <= 1909094399 )
      {
        puts("Not yet !!! Please wait more time.");
        exit(0);
      }
      v0 = 1234 * (ptrace(PTRACE_TRACEME, 29808LL, 24946LL, 25955LL) + 1);
      result = v0 ^ (unsigned int)(unsigned __int16)word_4048;
      word_4048 ^= v0;
      return result;
    }

    우선 실행되지 않는 이유는 위와 같이 time 함수의 리턴값을 특정값과 비교하고 있었기 때문이다.

    추가로 ptrace 함수로 디버깅을 탐지한다.

    #define _GNU_SOURCE
    #include <dlfcn.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <time.h>
    
    typedef time_t (*orig_time_t)(time_t *);
    static orig_time_t orig_time = NULL;
    
    time_t time(time_t *tloc) {
        if (!orig_time) {
            orig_time = (orig_time_t)dlsym(RTLD_NEXT, "time");
        }
    
        time_t ret = orig_time(tloc);
        printf("[+] Hooked time func! %ld -> 1909094400\n", ret);
        return 1909094400;
    }

    gcc -shared -fPIC -o hook_times.so hook_times.c -ldl

    따라서 times 함수를 1909094399를 초과한 값으로 후킹하는 라이브러리 코드를 작성하고 빌드한 다음,

    patchelf –add-needed ./hook_times.so ./times

    LD_PRELOAD 환경 변수없이 후킹시키는 라이브러리가 로드되도록 patchelf 명령어로 바이너리를 패치시켰다.

    만약에 로드되지 않도록, 다시 제거하려면 –remove-needed 옵션을 사용하면 된다.

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ldd times
            linux-vdso.so.1 (0x00007ffff7fc1000)
            ./hook_times.so (0x00007ffff7fb0000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7d7d000)
            /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fc3000)

    ldd 명령어로 확인해보면,
    정상적으로 hook_times.so 라이브러리가 추가된 것을 확인할 수 있다.

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times
    [+] Hooked time func! 1696075526 -> 1909094400
    ./times [registration code]
    
    ubuntu@WSL2:~/CTF/dreamhack.io/Times ./times AAAAAAAAAAAAAAAAAAAAAAAAAAA
    [+] Hooked time func! 1696075579 -> 1909094400
    Welcome to registration center
    [+] Hooked time func! 1696075579 -> 1909094400
    [+] Hooked time func! 1696075579 -> 1909094400
    Registration failed..

    time 함수 후킹이 잘되는 것을 볼 수 있다.

    main

    __int64 __fastcall main(int a1, char **a2, char **a3)
    {
      int v4; // ebx
      int v5; // ebx
      unsigned int *v6; // rbx
      int v7; // [rsp+1Ch] [rbp-54h] BYREF
      int i; // [rsp+20h] [rbp-50h]
      int j; // [rsp+24h] [rbp-4Ch]
      int k; // [rsp+28h] [rbp-48h]
      int m; // [rsp+2Ch] [rbp-44h]
      int v12; // [rsp+30h] [rbp-40h]
      unsigned int seed; // [rsp+34h] [rbp-3Ch]
      char *s; // [rsp+38h] [rbp-38h]
      __int64 v15[6]; // [rsp+40h] [rbp-30h] BYREF
    
      v15[3] = __readfsqword(0x28u);
      if ( a1 == 2 )
      {
        s = strdup(a2[1]);
        v12 = strlen(s);
        puts("Welcome to registration center");
        seed = time(0LL);
        srand(seed);
        v4 = rand();
        v7 = v4 + rand();
        v15[0] = 0LL;
        v15[1] = 0LL;
        sub_563958AED3AD(&v7, 4uLL, (__int64)v15);
        for ( i = 0; i < v12; ++i )
        {
          s[i] ^= *((_BYTE *)v15 + ((4 * (_BYTE)i) & 15));
          s[i] ^= *((_BYTE *)v15 + ((4 * (_BYTE)i + 1) & 0xF));
          s[i] ^= *((_BYTE *)v15 + ((4 * (_BYTE)i + 2) & 0xF));
          s[i] ^= *((_BYTE *)v15 + ((4 * (_BYTE)i + 3) & 0xF));
        }
        for ( j = 0; j < v12 / 2; ++j )
          *(_WORD *)&s[2 * j] ^= word_563958AF0048;
        seed = time(0LL);
        srand(seed);
        v5 = rand();
        v7 = v5 + rand();
        memset(v15, 0, 0x10uLL);
        sub_563958AED3AD(&v7, 4uLL, (__int64)v15);
        for ( k = 0; k < v12; ++k )
        {
          s[k] ^= *((_BYTE *)v15 + ((4 * (_BYTE)k) & 0xF))
          s[k] ^= *((_BYTE *)v15 + ((4 * (_BYTE)k + 1) & 0xF));
          s[k] ^= *((_BYTE *)v15 + ((4 * (_BYTE)k + 2) & 0xF));
          s[k] ^= *((_BYTE *)v15 + ((4 * (_BYTE)k + 3) & 0xF));
        }
        for ( m = 0; m < v12 / 4; ++m )
        {
          v6 = (unsigned int *)&s[4 * m];
          *v6 = sub_563958AED74A(*v6);
        }
        if ( !memcmp(s, byte_563958AF0020, 41uLL) )
          puts("Registration done !");
        else
          puts("Registration failed..");
        return 0LL;
      }
      else
      {
        printf("%s [registration code]\n", *a2);
        return 1LL;
      }
    }

    간단하게 설명하자면,

    argc가 2인지 확인하고,
    파라미터인 우리가 입력한 40바이트 값에 여러 XOR 연산을 거친다음,
    memcmp로 41바이트 크기만큼 비교하고 있었다.

    여기서 40이 아닌 41 바이트 크기인 이유는 마지막에 NULL을 포함시키기 위해서이다.

    sub_17D0 함수에서 기존의 word_563958AF0048 값인 0x4D2에다가 다시 0x4D2를 XOR 하니까 0,
    즉 NULL이 되는 것이다.

    사용자의 입력에 따라 어떤 값이 바뀔까?

    #define _GNU_SOURCE
    #include <dlfcn.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <time.h>
    
    typedef int (*memcmp_func_t)(const void*, const void*, size_t);
    typedef time_t (*orig_time_t)(time_t *);
    
    static memcmp_func_t orig_memcmp = NULL;
    static orig_time_t orig_time = NULL;
    
    int memcmp(const void* ptr1, const void* ptr2, size_t num) {
        if (!orig_memcmp) {
            orig_memcmp = (memcmp_func_t)dlsym(RTLD_NEXT, "memcmp");
        }
    
        printf("[+] Hooked memcmp called with ptr1=%p, ptr2=%p, num=%zu\n", ptr1, ptr2, num);
    
        printf("ptr1: ");
        for(int i = 0; i < 40; i++){
            if((i) % 8 == 0) {
                printf("\n");
            }
            printf("0x%x ", *(uint8_t*)(ptr1+i));
        }
    
        printf("\n");
    
        printf("ptr2: ");
        for(int i = 0; i < 40; i++){
            if((i) % 8 == 0) {
                printf("\n");
            }
            printf("0x%x ", *(uint8_t*)(ptr2+i));
        }
        printf("\n");
    
        
        int ret = orig_memcmp(ptr1, ptr2, num);
    
        return ret;
    }
    
    time_t time(time_t *tloc) {
        if (!orig_time) {
            orig_time = (orig_time_t)dlsym(RTLD_NEXT, "time");
        }
    
        time_t ret = orig_time(tloc);
        printf("[+] Hooked time func! %ld -> 1909094400\n", ret);
        return 1909094400;
    }

    memcmp 함수를 후킹해서 값을 살펴보도록 하였다.

    우선 40바이트를 A로 전부 채웠을 때, 나온 결과는 다음과 같았다.

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times_orig AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    [+] Hooked time func! 1696076648 -> 1909094400
    Welcome to registration center
    [+] Hooked time func! 1696076648 -> 1909094400
    [+] Hooked time func! 1696076648 -> 1909094400
    [+] Hooked memcmp called with ptr1=0x55555555a6b0, ptr2=0x555555558020, num=41
    ptr1:
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    ptr2:
    0x66 0xc 0x4c 0x86 0xa6 0x2c 0x1c 0x9c
    0x1c 0x66 0x1c 0x2c 0x9c 0x6c 0xa6 0xcc
    0xa6 0x6c 0x6c 0xac 0xa6 0xa6 0x86 0x4c
    0x2c 0x46 0xec 0x8c 0xec 0x46 0x8c 0x9c
    0x4c 0xec 0xc6 0x66 0x4c 0x46 0x86 0x4c
    Registration failed..

    ptr1 값이 전부 0x82로 채워졌다.

    마지막 바이트를 B로 바꾸면..?

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times_orig AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
    [+] Hooked time func! 1696076870 -> 1909094400
    Welcome to registration center
    [+] Hooked time func! 1696076870 -> 1909094400
    [+] Hooked time func! 1696076870 -> 1909094400
    [+] Hooked memcmp called with ptr1=0x55555555a6b0, ptr2=0x555555558020, num=41
    ptr1:
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x42 0x82 0x82 0x82
    ptr2:
    0x66 0xc 0x4c 0x86 0xa6 0x2c 0x1c 0x9c
    0x1c 0x66 0x1c 0x2c 0x9c 0x6c 0xa6 0xcc
    0xa6 0x6c 0x6c 0xac 0xa6 0xa6 0x86 0x4c
    0x2c 0x46 0xec 0x8c 0xec 0x46 0x8c 0x9c
    0x4c 0xec 0xc6 0x66 0x4c 0x46 0x86 0x4c
    Registration failed..

    36번째 자리인 0x82가 0x42로 바뀐 것을 알 수 있다.

    마지막 바이트인 AA부분을 BC로 바꾸면?

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times_orig AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABC
    [+] Hooked time func! 1696077186 -> 1909094400
    Welcome to registration center
    [+] Hooked time func! 1696077186 -> 1909094400
    [+] Hooked time func! 1696077186 -> 1909094400
    [+] Hooked memcmp called with ptr1=0x55555555a6b0, ptr2=0x555555558020, num=41
    ptr1:
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0x82 0x82 0x82 0x82
    0x82 0x82 0x82 0x82 0xc2 0x42 0x82 0x82
    ptr2:
    0x66 0xc 0x4c 0x86 0xa6 0x2c 0x1c 0x9c
    0x1c 0x66 0x1c 0x2c 0x9c 0x6c 0xa6 0xcc
    0xa6 0x6c 0x6c 0xac 0xa6 0xa6 0x86 0x4c
    0x2c 0x46 0xec 0x8c 0xec 0x46 0x8c 0x9c
    0x4c 0xec 0xc6 0x66 0x4c 0x46 0x86 0x4c
    Registration failed..

    이번에는 36번째 자리가 0xc2, 37번째 자리가 0x42로 바뀐 것을 알 수 있다.

    이렇게 0x41(A)는 0x82, 0x42(B)는 0x42, 0x43(C)는 0xC2라는
    서로 대칭을 이룬다는 점을 발견할 수 있다.

    a = 0x86
    b = 0x46
    c = 0xc6
    d = 0x26
    e = 0xa6
    f = 0x66
    g = 0xe6
    h = 0x16 
    i = 0x96 
    j = 0x56 
    k = 0xd6 
    l = 0x36 
    m = 0xb6
    ...
    0 = 0x0c
    1 = 0x8c 
    2 = 0x4c
    3 = 0xcc
    4 = 0x2c
    5 = 0xac
    6 = 0x6c
    7 = 0xec
    8 = 0x1c 
    9 = 0x9c 

    FLAG

    Byte order을 고려해서 FLAG를 얻을 수 있다.

    DH{a20f984e48f83e69566e2aee17b491b7fc722ab2}

    ubuntu@WSL2:~/CTF/dreamhack.io/Times$ ./times a20f984e48f83e69566e2aee17b491b7fc722ab2
    [+] Hooked time func! 1696077344 -> 1909094400
    Welcome to registration center
    [+] Hooked time func! 1696077344 -> 1909094400
    [+] Hooked time func! 1696077344 -> 1909094400
    [+] Hooked memcmp called with ptr1=0x55555555a6b0, ptr2=0x555555558020, num=41
    ptr1:
    0x66 0xc 0x4c 0x86 0xa6 0x2c 0x1c 0x9c
    0x1c 0x66 0x1c 0x2c 0x9c 0x6c 0xa6 0xcc
    0xa6 0x6c 0x6c 0xac 0xa6 0xa6 0x86 0x4c
    0x2c 0x46 0xec 0x8c 0xec 0x46 0x8c 0x9c
    0x4c 0xec 0xc6 0x66 0x4c 0x46 0x86 0x4c
    ptr2:
    0x66 0xc 0x4c 0x86 0xa6 0x2c 0x1c 0x9c
    0x1c 0x66 0x1c 0x2c 0x9c 0x6c 0xa6 0xcc
    0xa6 0x6c 0x6c 0xac 0xa6 0xa6 0x86 0x4c
    0x2c 0x46 0xec 0x8c 0xec 0x46 0x8c 0x9c
    0x4c 0xec 0xc6 0x66 0x4c 0x46 0x86 0x4c
    Registration done !

    답글 남기기

    이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다