Description

    제작자가 입력한 내용이 어떤 내용이었는지 맞춰주세요!
    헤당 문자열은 DH{..}이며, printable ascii입니다

    Decompiled-src

    main

    __int64 __fastcall main(int a1, char **a2, char **a3)
    {
      int fd; // [rsp+Ch] [rbp-514h]
      __int64 v5[32]; // [rsp+10h] [rbp-510h] BYREF
      char buf[1032]; // [rsp+110h] [rbp-410h] BYREF
      unsigned __int64 v7; // [rsp+518h] [rbp-8h]
    
      v7 = __readfsqword(0x28u);
      memset(v5, 0, sizeof(v5));
      puts("generate your flag!");
      printf("> ");
      __isoc99_scanf("%255s", v5);
      sub_13F1(v5, buf, 256LL);
      fd = open("./out.txt", 1);
      write(fd, buf, 0x100uLL);
      close(fd);
      return 0LL;
    }

    “generate your flag!” 문구를 출력하고, scanf 함수를 통해 255바이트만큼 v5로 입력받는다.
    sub_13F1 함수에 의해 v5를 넘겨주면서 buf를 다시 가져온다.

    O_WRONLY인 쓰기 모드로 out.txt 파일에다가 buf 내용을 쓴다.

    sub_13F1

    __int64 __fastcall sub_13F1(__int64 a1, __int64 a2, int a3)
    {
      char v5[256]; // [rsp+20h] [rbp-120h] BYREF
      __int64 v6[4]; // [rsp+120h] [rbp-20h] BYREF
    
      v6[3] = __readfsqword(0x28u);
      v6[0] = 0LL;
      v6[1] = 0LL;
      AES_set_encrypt_key(&byte_4010, 128LL, v5);
      AES_cbc_encrypt(a1, a2, a3, v5, v6, 1LL);
      return 0LL;
    }
    int AES_set_encrypt_key(const unsigned char * userKey, const int bits, AES_KEY * key);

    AES_set_encrypt_key
    바이트 배열 형태의 암호화 키(userKey)의 포인터와 사용할 키의 bits수를 입력 받아
    AES_KEY의 포인터 형태로 AES의 암호화 키를 출력하는 함수이다.

    성공할 경우, 리턴값은 0
    실패할 경우, 음수 값이 나온다.

    따라서 암호화 키는 byte_4010이고, v5를 통해서 AES의 암호화 키를 출력하고 있다.

    void AES_cbc_encrypt(const unsigned char * in, unsigned char * out, size_t length, const AES_KEY * key, unsigned char * ivec, const int enc);

    AES_cbc_encrypt

    주어진 키인 key와 초기화 벡터인 ivec을 사용하여
    주어진 길이의 데이터인 in을 CBC 모드로 암호화하거나 복호화할 수 있는 함수이다.

    enc는 암호화 모드를 나타내는 플래그이며,
    0이면 복호화 모드, 1이면 암호화 모드이다.

    length는 암호화할 데이터의 길이를 의미한다.

    따라서 AES-CBC-128 모드로 main에서 입력받았던 v5를 암호화시킨다고 보면 보면 된다.
    여기서 length는 %255s를 통해서 v5를 입력받았기에 256이다.

    AES 키로 사용되는 byte_4010를 역참조해보면,
    sub_13F1 함수 이외에 sub_1392, sub_12C9 함수에서 참조하는것을 확인할 수 있다.

    이러한 함수들은 프로그램 초기에 호출되는데,

    sub_12C9

    void sub_12C9()
    {
      unsigned int v0; // eax
      int v1; // ebx
      int v2; // [rsp+4h] [rbp-1Ch]
      int i; // [rsp+8h] [rbp-18h]
      int j; // [rsp+Ch] [rbp-14h]
    
      v0 = time(0LL);
      srand(v0);
      v2 = 1;
      for ( i = 0; i <= 4095; ++i )
      {
        v1 = ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
        v2 *= v1 * rand();
      }
      for ( j = 0; j <= 14; ++j )
        byte_4010[j + 1] += byte_4010[j] + v2;
    }

    ptrace 함수를 통해 디버깅을 탐지하고 있었다.
    만약 디버깅을 하고 있을 경우, ptrace에서 -1을 반환하지만,
    디버깅을 하고 있지 않으면 0을 반환한다.

    따라서 디버깅을 하고 있지 않으면, v2는 0이 된다.

    sub_1392

    int sub_1392()
    {
      int v0; // eax
      int result; // eax
      int i; // [rsp+8h] [rbp-8h]
      char v3; // [rsp+Ch] [rbp-4h]
    
      v0 = rand();
      srand(v0);
      result = rand();
      v3 = result;
      for ( i = 0; i <= 15; ++i )
      {
        result = i;
        byte_4010[i] ^= v3;
      }
      return result;
    }

    이번에는 srand 함수를 통해 랜덤 시드를 생성하여
    rand에 의해 반환된 v3 값과 XOR하는 것을 확인할 수 있다.

    v3는 char형 타입이기에 0~255 범위가 되겠다.

    Solution

    Before

    After

    sub_1392 함수에서 XOR할 rand 반환값이 아닌, 0x41 값으로 opcode를 패치시켰다.

    hook_prob.c

    //gcc -shared -fPIC -o hook_prob.so hook_prob.c -ldl -lssl -lcrypto
    //LD_PRELOAD=./hook_prob.so ./prob
    
    #define _GNU_SOURCE
    
    #include <dlfcn.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <openssl/aes.h>  
    
    //https://gist.github.com/ccbrown/9722406
    void DumpHex(const void* data, size_t size) {
    	char ascii[17];
    	size_t i, j;
    	ascii[16] = '\0';
    	for (i = 0; i < size; ++i) {
    		printf("%02X ", ((unsigned char*)data)[i]);
    		if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') {
    			ascii[i % 16] = ((unsigned char*)data)[i];
    		} else {
    			ascii[i % 16] = '.';
    		}
    		if ((i+1) % 8 == 0 || i+1 == size) {
    			printf(" ");
    			if ((i+1) % 16 == 0) {
    				printf("|  %s \n", ascii);
    			} else if (i+1 == size) {
    				ascii[(i+1) % 16] = '\0';
    				if ((i+1) % 16 <= 8) {
    					printf(" ");
    				}
    				for (j = (i+1) % 16; j < 16; ++j) {
    					printf("   ");
    				}
    				printf("|  %s \n", ascii);
    			}
    		}
    	}
    }
    
    typedef int (*orig_AES_set_encrypt_key)(const unsigned char *userKey, const int bits, AES_KEY *key);
    int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key) {
        orig_AES_set_encrypt_key original_AES_set_encrypt_key;
        original_AES_set_encrypt_key = (orig_AES_set_encrypt_key) dlsym(RTLD_NEXT, "AES_set_encrypt_key");
    
        printf("Hooked AES_set_encrypt_key\n");
        printf("userKey:\n");
        DumpHex(userKey, 16);
    
        int ret = original_AES_set_encrypt_key(userKey, bits, key);
        
        printf("AES_set_encrypt_key ret: %d\n", ret);
    	printf("==========================\n");
        return ret;
    }
    
    typedef void (*orig_AES_cbc_encrypt)(const unsigned char* in, unsigned char* out, size_t length, const AES_KEY* key, unsigned char* ivec, const int enc);
    
    void AES_cbc_encrypt(const unsigned char* in, unsigned char* out, size_t length, const AES_KEY* key, unsigned char* ivec, const int enc) {
        orig_AES_cbc_encrypt original_AES_cbc_encrypt;
        original_AES_cbc_encrypt = (orig_AES_cbc_encrypt) dlsym(RTLD_NEXT, "AES_cbc_encrypt");
    
        printf("Hooked AES_cbc_encrypt\n");
        printf("ivec:\n");
        DumpHex(ivec, 16);
    
        original_AES_cbc_encrypt(in, out, length, key, ivec, enc);
    
    	printf("==========================\n");
    }

    간단히 후킹 라이브러리를 만들어서 AES 암호화에 사용될 KEY와 IV를 가져올 수 있다.

    최종적으로 sub_1392 함수에서 0x41과 XOR된 키 값은 아래와 같다.

    unsigned char key[16] = {0x00, 0x28, 0xC3, 0x91, 0x34, 0xB0, 0xD3, 0x92, 0xA7, 0xF4, 0x7C, 0xA8, 0x52, 0x42, 0xFB, 0xD5};

    iv 값은 아래와 같다.

    unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

    solve.c

    0x41과 XOR하여 키 값을 다시 복원시키고,
    sub_1392 함수에서 XOR할 rand 반환값이 0~255 사이이기 때문에
    브루트포싱해서 DH{ 플래그를 찾아내면 된다.

    //gcc -o solve solve.c -ldl -lssl -lcrypto
    
    #include <dlfcn.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdint.h>
    #include <openssl/aes.h> 
    #include <stdlib.h>
    
    unsigned char key[16] = {0x00, 0x28, 0xC3, 0x91, 0x34, 0xB0, 0xD3, 0x92, 0xA7, 0xF4, 0x7C, 0xA8, 0x52, 0x42, 0xFB, 0xD5};
    
    unsigned char xored_key[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    
    unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
    
    char* readFile(const char *filename) {
        FILE *file = fopen(filename, "r");
    
        fseek(file, 0, SEEK_END);
        long fileSize = ftell(file);
        rewind(file);
    
        char *content = (char*)malloc(fileSize + 1);
        fread(content, 1, fileSize, file);
        content[fileSize] = '\0';
    
        fclose(file);
    
        return content;
    }
    
    int main(void) {
        AES_KEY aes_key;
    
        for (int i = 0; i < 16; i++) { 
            key[i] ^= 0x41;
        }
    
        char *p_in = readFile("./out_flag.txt");
    
        char p_out[256];
        for(int i = 0; i < 256; i++) {
            bzero(iv, 16);  
    
            for(int j = 0; j < 16; j++) {
                xored_key[j] = key[j] ^ i;
            }
            AES_set_decrypt_key(xored_key, 128, &aes_key);
    
            AES_cbc_encrypt(p_in, p_out, 256, &aes_key, iv, AES_DECRYPT);
            if(p_out[0] == 'D' && p_out[1] == 'H' && p_out[2] == '{')
                printf("FLAG? %s\n", p_out);
        }
        return 0;
    }

    FLAG

    DH{4e979cd79e571e07d362d3240921c7b9e643082e31029adadfbc4af89b7d0b7f}

    답글 남기기

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