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}