html
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Hex to PCAP Converter</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
padding-top: 0;
min-height: 100vh;
background: rgb(251, 250, 250);
display: flex;
justify-content: center;
align-items: flex-start;
}
.container {
width: 80%;
margin: 5px 0;
min-height: 100vh;
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
textarea {
width: 80%;
min-height: 250px;
margin: 10px 0;
font-family: monospace;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #0056b3;
}
.output-container {
margin-top: 20px;
}
.title {
font-weight: bold;
margin-bottom: 5px;
color: #333;
}
.checkbox-container {
margin: 10px 0;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 8px;
width: 400px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.modal input[type="text"] {
width: 100%;
padding: 8px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
}
.modal-buttons {
display: flex;
justify-content: space-between;
margin-top: 15px;
}
.modal-buttons button {
width: 170px;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
margin: 70px 1rem 1rem;
}
.button-container {
flex-direction: column;
}
.modal-content {
width: 95%;
padding: 1.5rem;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Hex to PCAP Converter</h1>
<div class="checkbox-container">
<input type="checkbox" id="addHeaders" checked>
<label for="addHeaders">Add MAC/IP/UDP Headers (GTP-C Port: 2123)</label>
</div>
<div class="input-container">
<div class="title">Input Hex Stream:</div>
<textarea id="inputHex" placeholder="Enter hex stream (space or newline separated)"></textarea>
<div class="button-container">
<button onclick="convertHex()">Convert to Wireshark Format</button>
<button onclick="showExportDialog()">Export PCAP</button>
</div>
</div>
<div class="output-container">
<div class="title">Wireshark Compatible Format:</div>
<textarea id="outputHex" readonly></textarea>
</div>
</div>
<!-- Export Dialog -->
<div id="exportDialog" class="modal">
<div class="modal-content">
<h2>Export PCAP</h2>
<div>
<label for="filename">FileName:</label>
<input type="text" id="filename" value="packet.pcap">
</div>
<div class="modal-buttons">
<button onclick="closeExportDialog()">Cancel</button>
<button onclick="confirmExport()">Export</button>
</div>
</div>
</div>
<script>
const MAC_IP_HEADER_LENGTH = 34; // MAC + IP header length (不含UDP头)
const UDP_HEADER_LENGTH = 8; // UDP header length
function getHeaderTemplate() {
return [
'FA', '16', '3E', '3C', 'F5', '10', 'FA', '16',
'3E', '9F', 'B1', 'B4', '08', '00', '45', '00',
'00', '00', // IP总长度 (占位)
'15', '81', '00', '00', '80', '11',
'00', '00', // IP校验和 (占位)
'65', '26', '03', '6F', '65', '26',
'03', 'D7', '08', '4B', '08', '4B',
'00', '00', // UDP长度 (占位)
'00', '00' // UDP校验和 (占位,可选)
];
}
function calculateIPChecksum(header) {
let sum = 0;
for (let i = 14; i < 34; i += 2) {
const value = (parseInt(header[i], 16) << 8) + parseInt(header[i + 1], 16);
sum += value;
}
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (~sum & 0xFFFF).toString(16).padStart(4, '0');
}
function processHexInput(input) {
const cleaned = input.replace(/[\s\n\r]+/g, '').toUpperCase();
const bytes = [];
for (let i = 0; i < cleaned.length; i += 2) {
bytes.push(cleaned.substr(i, 2));
}
return bytes;
}
function updateHeaders(headers, dataLength) {
// 更新IP总长度 (IP头20字节 + UDP头8字节 + 数据长度)
const ipTotalLength = (20 + 8 + dataLength).toString(16).padStart(4, '0');
headers[16] = ipTotalLength.slice(0, 2);
headers[17] = ipTotalLength.slice(2, 4);
// 更新UDP长度 (UDP头8字节 + 数据长度)
const udpLength = (8 + dataLength).toString(16).padStart(4, '0');
headers[38] = udpLength.slice(0, 2);
headers[39] = udpLength.slice(2, 4);
// 更新IP校验和
const ipChecksum = calculateIPChecksum(headers);
headers[24] = ipChecksum.slice(0, 2);
headers[25] = ipChecksum.slice(2, 4);
return headers;
}
function convertHex() {
let input = document.getElementById('inputHex').value;
let bytes = processHexInput(input);
if (document.getElementById('addHeaders').checked) {
let headers = getHeaderTemplate();
headers = updateHeaders(headers, bytes.length);
bytes = [...headers, ...bytes];
}
let output = '';
for (let i = 0; i < bytes.length; i += 16) {
const offset = (i).toString(16).padStart(4, '0');
output += offset + ' ';
const lineBytes = bytes.slice(i, i + 16);
output += lineBytes.join(' ');
if (lineBytes.length < 16) {
output += ' '.repeat(16 - lineBytes.length);
}
output += '\n';
}
document.getElementById('outputHex').value = output;
}
function createPcapHeader() {
const buffer = new ArrayBuffer(24);
const view = new DataView(buffer);
view.setUint32(0, 0xa1b2c3d4, false);
view.setUint16(4, 2, false);
view.setUint16(6, 4, false);
view.setInt32(8, 0, false);
view.setUint32(12, 0, false);
view.setUint32(16, 65535, false);
view.setUint32(20, 1, false);
return new Uint8Array(buffer);
}
function createPacketHeader(length) {
const buffer = new ArrayBuffer(16);
const view = new DataView(buffer);
const now = Math.floor(Date.now() / 1000);
view.setUint32(0, now, false);
view.setUint32(4, 0, false);
view.setUint32(8, length, false);
view.setUint32(12, length, false);
return new Uint8Array(buffer);
}
function showExportDialog() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const defaultFilename = `packet-${year}-${month}-${day}-${hours}-${minutes}.pcap`;
document.getElementById('filename').value = defaultFilename;
document.getElementById('exportDialog').style.display = 'block';
}
function closeExportDialog() {
document.getElementById('exportDialog').style.display = 'none';
}
function confirmExport() {
let filename = document.getElementById('filename').value.trim();
if (!filename.endsWith('.pcap')) {
filename += '.pcap';
}
exportPcap(filename);
closeExportDialog();
}
function exportPcap(filename) {
let input = document.getElementById('inputHex').value;
let hexBytes = processHexInput(input);
if (document.getElementById('addHeaders').checked) {
let headers = getHeaderTemplate();
headers = updateHeaders(headers, hexBytes.length);
hexBytes = [...headers, ...hexBytes];
}
const packetData = new Uint8Array(hexBytes.map(hex => parseInt(hex, 16)));
const pcapHeader = createPcapHeader();
const packetHeader = createPacketHeader(packetData.length);
const finalBuffer = new Uint8Array([
...pcapHeader,
...packetHeader,
...packetData
]);
const blob = new Blob([finalBuffer], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
window.onload = function() {
document.getElementById('inputHex').value = '';
}
</script>
</body>
</html>