콘텐츠로 건너뛰기

ptrace_block

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}

답글 남기기