-
[SCTF 2021] secure_enoughWargame/study 2022. 1. 26. 22:29
1. Description
"I made secure communication algorithm. No one can see my message."라는 문구와 함께 out.pcap 파일, my_client 바이너리 파일이 주어진다. out.pcap 파일은 서버와 통신하면서 오고 가는 패킷 정보를 보여주며, my_client 파일은 서버와 소켓 통신을 하는 클라이언트의 바이너리 파일인 듯하다. 해당 문제는 reversing, binary 태그가 달려있었다.
2. Analysis
my_client 파일을 ida로 열어서 분석을 진행하면서 out.pcap 파일을 번갈아 보는 방식으로 분석을 진행했다. 먼저 my_client 파일의 main 함수는 아래와 같다.
my_client - main 함수 인자 검사를 한 후, 몇 가지 함수를 호출하고 프로그램이 종료된다.
1) connect_server
connect_server함수는 다음과 같다.
my_client - connect_server 함수 코드를 분석해보면 서버의 주소와 소켓으로 연결을 하는 부분이며, 7001 포트로 tcp 접속을 시도하는 것을 알 수 있다. 접속에 성공하면 소켓 디스크립터(이하 fd)를 반환한다. out.pcap 파일에서도 서버에 접속을 시도하는 것을 확인할 수 있다.
out.pcap - 서버 연결 시도 2) sub_1784
sub_1784 함수는 다음과 같이 구현되어 있다.
sub_1784 함수 내부적으로 send, recv 함수를 호출한다. 이는 ida 코드를 분석한 후 임의로 이름을 붙인 것이다. send와 recv 함수는 다음과 같이 구현되어 있다.
(1) send
send 함수 함수의 앞 부분에서 set_hash 함수를 호출하는데 set_hash 함수는 다음과 같다.
set_hash 함수 srand 함수를 이용해 현재 시간을 시드로 하는 랜덤 한 값을 생성하여 md5 해시 값을 생성하고, 인자로 들어온 주소에 이 값을 저장한다. 이 과정을 한번 더 반복해서 결과적으로는 파라미터의 주소에 32 byte의 md5 해시값 두 개가 저장된다. 다시 send 함수로 돌아와 보자.
send 함수 set_hash 함수를 연달아 두 번 호출해서 hash1, hash2 주소(데이터 영역)에 64 byte의 해시 값을 생성한 후, 버퍼에 hash1 값을 복사하고, hash2는 rsa_encrypt 함수를 호출해서 ciphertext 변수에 rsa로 암호화된 값을 저장한다. 26 line에서 write 함수를 통해 서버로 데이터를 서버에 전송하게 되는데, 그림으로 전송되는 데이터를 도식화하면 아래와 같다.
전송되는 데이터 앞의 1은 패킷의 유효성?을 체크하는 용도로 집어넣은 듯 하다. 이 부분은 out.pcap 파일에서도 확인할 수 있다.
out.pcap 파일 또한 현재까지 데이터 영역에 생성된 값들은 다음과 같은 형태를 나타낸다.
hash1 & hash2 (2) recv
recv 함수는 다음과 같다.
recv 함수 서버로 부터 259 byte의 데이터를 읽어 들인 후, rsa_decrypt 함수를 호출해서 plain변수(데이터 영역)에 그 결과를 저장한다. 해당 부분을 pcap 파일에서 확인하면 다음과 같다.
out.pcap 파일 또한 지금까지 데이터 영역에 생성된 값들은 다음과 같다.
hash1 & hash2 & hash3 recv 함수가 호출된 후, recv 함수를 호출하는 sub_1784 함수의 끝부분에 save_hash 함수가 호출되는데 save_hash 함수는 다음과 같다.
save_hash 함수 save_hash 함수는 sub_1647 함수를 연달아 4번 호출하는데 sub_1647 함수는 다음과 같다.
sub_1647 함수 파라미터 + hash1 + hash2 + plain 값에 대해 MD5 해시를 생성하는 것을 알 수 있다. 디컴파일 뷰에서는 나타나지 않는데, save_hash 함수의 어셈블리 코드를 살펴보면 다음과 같이 데이터 영역에 MD5 해시의 결괏값을 저장하는 것을 확인할 수 있다.
MD5 결과 저장 이 부분에 대한 코드 실행이 끝나면, 데이터 영역에 다음과 같은 값들이 저장되게 된다.
데이터 영역에 저장된 값들 3) sub_1968
main에서 세번째로 호출되는 함수인 sub_1968은 다음과 같다.
sub_1968 함수 aes_encrypt 함수를 이용해 "Give Me Key"라는 메시지를 암호화한 후, write 함수로 서버에 데이터를 전송한다. 이때 aes_encrypt 함수는 다음과 같다.
aes_encrypt 함수 코드를 살펴보면 key 값으로 0x2030A0 영역에 있는 값을, iv 값으로 0x2030c0 주소에 있는 값을 사용하며, AES 256 암호화를 사용하는 것을 알 수 있다. 즉 아래 그림에서 0x2030A0에 있는 32byte가 AES key가 되고, 0x2030C0에 있는 32byte가 AES IV가 된다.
AES key, IV out.pcap 파일에서도 서버에 암호화된 값을 전송하는 것을 확인할 수 있다.
4) sub_1A28
main 함수에서 마지막으로 호출되는 sub_1A28은 다음과 같다.
sub_1A28 함수 서버로부터 64byte를 읽어 들인 후, aes_decrypt함수를 호출해 복호환 후 puts로 내용물을 출력한다. out.pacp 파일에서 서버로부터 받은 데이터를 확인할 수 있다. 그렇다면 AES key 값을 구해서 데이터를 복호화해준다면 flag를 획득할 수 있을 것이다.
3. Solving
사용되는 AES의 key와 IV는 아래 그림의 색칠한 부분이다.
AES key, IV 여기서 hash1 값은 out.pcap 파일에서 서버에 전송되는 패킷의 데이터를 확인하면 쉽게 찾을 수 있고, plain의 경우 서버로부터 읽어 들인 데이터를 rsa 공개키로 복호화하면 구할 수 있다. 문제는 hash2 값이다. hash2 값은 rsa 공개키로 암호화한 후 서버로 전송되기 때문에 pcap 파일에도 암호화된 hash2만 존재한다. rsa의 private key를 모르는 상황에서 어떻게 hash2 값을 구할 수 있을까? 답은 rand 함수에 있다.
hash1, hash2 값을 생성하는 set_hash 함수는 함수의 시작 부분에서 time(0)을 시드로 해서 랜덤 한 값을 생성한다. 하지만 set_hash 함수는 연이어 호출되기 때문에, hash1, hash2 값이 똑같게 설정될 확률이 굉장히 높다.
연달아 호출되는 set_hash 함수 이로부터 hash2 값을 hash1과 똑같이 설정하게 되면 AES key와 IV를 구하는데 필요한 모든 요소를 계산할 수 있어 key와 iv를 구할 수 있다. 아래 코드는 획득한 정보를 바탕으로 서버로부터 받은 데이터를 복호화해서 출력해주는 코드이다.
//g++ -o solve solve.cpp -lcrypto #include <cstdio> #include <cstring> #include <openssl/md5.h> #include <openssl/evp.h> #include <openssl/bio.h> #include <openssl/pem.h> #include <openssl/rsa.h> unsigned char hash1[32]; unsigned char hash2[32]; unsigned char plain[32]; unsigned char aes_key[32]; unsigned char aes_iv[32]; unsigned char flag[64]; void hex_dump(unsigned char *buf, int len) { for(int i=0; i<len; i++) printf("%02x", buf[i]); puts(""); } void rsa_decrypt(unsigned char* ct, int len, unsigned char* pt) { BIO *bio = BIO_new_mem_buf("-----BEGIN PUBLIC KEY-----\n" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA214EFGCpMbQhB4uRo7P9\n" "FAajAfvz7ianshjD44IvZeZHeEYTfa1zONbjYGK2lw/0v+xZ/Em4M9sPOSGlsPcr\n" "vG3O9/XKM0+he05Lh8nedtMnpOQgxFhwJNbdKR3SYzsH8+JziLHAmKQmlmH8FBiE\n" "reGsshAhICrz8GGDCjDg7Aam4wKj0HY6hfj8zUYjAf2MxoozWIYFmjSXI2xwp6Kq\n" "Uqhac9W0nnQkToe+vtBjlcPowRV9WViNIB2msE6afe+YqKVSYNizbEXSbmocsA+A\n" "job4i1u8LAtdd4zF5gmGuKCJITiMMglakHzwosXXfbejIaJlpfC6sx4xIu6nkx6Y\n" "lQIDAQAB\n" "-----END PUBLIC KEY-----", -1); RSA *rsa = PEM_read_bio_RSA_PUBKEY(bio, &rsa, NULL, NULL); puts("decrypting..."); RSA_public_decrypt(len, ct, pt, rsa, RSA_PKCS1_PADDING); } void aes_decrypt(unsigned char* ct, int len, unsigned char* pt) { int outl, tmpl; EVP_CIPHER_CTX *ctx; ctx = EVP_CIPHER_CTX_new(); EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), 0, aes_key, aes_iv); EVP_DecryptUpdate(ctx, pt, &outl, ct, len); EVP_DecryptFinal_ex(ctx, pt + outl, &tmpl); EVP_CIPHER_CTX_free(ctx); } void aes_key_gen(void) { MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, "A", 1); MD5_Update(&ctx, hash1, 32); MD5_Update(&ctx, hash2, 32); MD5_Update(&ctx, plain, 32); MD5_Final(aes_key, &ctx); MD5_Init(&ctx); MD5_Update(&ctx, "BB", 2); MD5_Update(&ctx, hash1, 32); MD5_Update(&ctx, hash2, 32); MD5_Update(&ctx, plain, 32); MD5_Final(aes_key + 16, &ctx); } void aes_iv_gen(void) { MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, "CCC", 3); MD5_Update(&ctx, hash1, 32); MD5_Update(&ctx, hash2, 32); MD5_Update(&ctx, plain, 32); MD5_Final(aes_iv, &ctx); MD5_Init(&ctx); MD5_Update(&ctx, "DDDD", 4); MD5_Update(&ctx, hash1, 32); MD5_Update(&ctx, hash2, 32); MD5_Update(&ctx, plain, 32); MD5_Final(aes_iv + 16, &ctx); } int main() { memcpy(hash1, "\xa3\xe6\xf4\x84\xd7\x86\x5a\xb1\x05\x6e\x15\x83\x3c\x74\x8b\xed\xb6\x89\xc0\x61\x3f\xa1\x14\x6a\x35\xf1\x29\x79\x7a\x77\x05\x14", 32); memcpy(hash2, "\xa3\xe6\xf4\x84\xd7\x86\x5a\xb1\x05\x6e\x15\x83\x3c\x74\x8b\xed\xb6\x89\xc0\x61\x3f\xa1\x14\x6a\x35\xf1\x29\x79\x7a\x77\x05\x14", 32); unsigned char ciphertext[256]; memcpy(ciphertext, "\x0f\x4b\x82\xb9\xd7\x71\xa2\x62\x5d\xe1\x33\x92\x69" \ "\xea\xd8\x59\x93\x08\xa5\x11\x9f\x3c\x8a\x3e\xb2\xe2\x66\xf0\x42" \ "\x10\xc2\xac\x7e\x56\x57\x07\x2e\xcd\x5f\xb7\x77\xa9\x9a\x8d\x57" \ "\xd9\x4e\x39\xfa\x70\x01\xdd\x92\x6a\xc4\x2e\x4e\x9c\x94\x4c\xd0" \ "\x86\x86\x86\x05\xd5\x9d\xb7\x18\xca\xf0\x73\x8f\x99\x83\x57\x51" \ "\x19\xe4\xae\x63\xf8\x4c\x7a\x27\x4e\xba\x7b\x39\xb9\xdc\x19\xa7" \ "\x49\xa9\xbc\xa7\xbe\xad\x0a\xa7\x5e\xa8\xf2\xc3\x4a\x48\xdd\xa8" \ "\xa4\x81\x2e\x93\x32\x49\xe9\x45\xf6\x68\x58\x78\x59\x47\xd9\x51" \ "\x68\x15\x4b\x18\xe4\x4f\x0f\xfa\x4f\x3c\x0a\x33\x6e\xe2\xfc\x72" \ "\xf6\xb0\xaa\x1d\xee\xba\x5c\xd4\x64\x6e\x68\xae\x59\x19\x23\xdc" \ "\x28\x94\x59\x78\x62\xa7\x53\xc3\xf8\x64\x09\xcc\x19\xb8\xb5\x07" \ "\x0d\xe0\x8f\xda\xb3\x40\x61\x8e\x6f\xb9\x37\x0d\x95\xbf\x07\x67" \ "\x0d\x76\xcd\xf3\x20\xd5\xbd\x3b\xf1\x0c\x26\xec\x89\xf4\x79\x56" \ "\xa4\xe6\xf8\x50\xf7\x51\xd7\x48\x0c\x82\xcb\x25\xf7\xa4\x8b\xa1" \ "\x67\xd2\x07\xd7\xa3\x83\x6c\x7d\xee\x67\x9a\x7a\xc1\xe0\x04\xe0" \ "\x39\x95\x98\x99\x4e\x75\x42\xd6\x3e\x65\xeb\x24\xb4\x11\x58\xc6" \ "\x67\x28\x72", 256); rsa_decrypt(ciphertext, 256, plain); aes_key_gen(); aes_iv_gen(); printf("aes key : "); hex_dump(aes_key, 32); printf("aes iv : "); hex_dump(aes_iv, 32); unsigned char flag_enc[64]; memcpy(flag_enc, "\xdc\x01\x4f\x22\x66\xd9\x36\x8d\xbd\x6f\xb5\xd3\xfa\x1d" \ "\x67\x5c\xc2\x17\x2a\xe7\x03\x87\x2a\xfb\xad\xc9\x4d\xc8\xcb\xc8" \ "\xaf\xcd\xa7\xc1\x17\x72\x53\xfe\x51\x11\x40\x41\xad\x01\x03\xbb" \ "\xb8\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ "\x00\x00", 64); aes_decrypt(flag_enc, 64, flag); puts((char *)flag+1); return 0; }
4. Comment
해당 문제를 처음 접했을 때, wireshark를 한 번도 다뤄본 적이 없어서 어떻게 패킷을 보는지도 몰랐기에 바이너리 분석만 하다가 넘긴 문제다. 최근에 네트워크 관련된 수업을 듣기도 하고 소켓 통신 관련된 코드를 작성할 일도 있어서 다시 문제를 붙잡고 풀어보았다. 개인적으로 시간은 많이 소요되었지만 문제의 핵심 취약점보다도 다양한 것을 배울 수 있는 문제였다고 생각한다. 풀이 코드를 작성하면서 ida의 디컴파일 뷰를 봤을 때 openssl이 사용된 것 같아서 한번 사용해볼 겸 이리저리 찾아가면서 환경 세팅하고 코드를 작성했는데 (1) 윈도우에서보다는 리눅스에서 사용하는 것이 훨씬 편하다. (2) openssl 문서는 굉장히 불친절하다 라는 생각이 들었다.
반응형'Wargame > study' 카테고리의 다른 글
[dreamhack.io] rev-login (0) 2022.03.02 [HackCTF] BabyMIPS (0) 2022.02.22 [dreamhack.io] Textbook-CBC (0) 2022.01.13 [reversing.kr] PEPassword (0) 2022.01.04 [SCTF 2021] memory (0) 2021.12.30