Reputation: 13
Goal
Things that are happening -
Thing where I am stuck -
This is my complete code for capturing and logging my packets.
package com.example.vpn
import android.net.VpnService
import android.os.ParcelFileDescriptor
import android.util.Log
import java.io.FileDescriptor
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketTimeoutException
import java.util.concurrent.SynchronousQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
class VpnNetworkException(message: String, cause: Throwable? = null) : Exception(message, cause)
class MyVpnService : VpnService(){
private var TAG = "SS"
private var vpnInterface: ParcelFileDescriptor? = null
override fun onCreate() {
super.onCreate()
startVpn()
}
private fun startVpn() {
val builder = Builder()
val ip = getLocalIpAddress()
Log.d("SS", "DeviceIP $ip")
builder.addAddress("192.168.30.221", 24)
builder.addDnsServer("8.8.8.8")
builder.addRoute("0.0.0.0",0 )
builder.setBlocking(true)
vpnInterface = builder.setSession("vpn").establish()
vpnInterface?.let {
Thread {
capturePackets(it.fileDescriptor)
}.start()
}
}
private fun capturePackets(fd: FileDescriptor) {
val input = FileInputStream(fd)
val buffer = ByteArray(32767)
val dnsSocket = DatagramSocket()
val executor = ThreadPoolExecutor(0, 32, 60L, TimeUnit.SECONDS, SynchronousQueue<Runnable>())
protect(dnsSocket)
while (true) {
val length = input.read(buffer)
if (length == 0) {
continue
}
val packetData = buffer.copyOf(length)
executor.execute {
Log.d("SS","Packet: ${packetData.toHexString()}")
parsePacket(packetData)
}
}
}
private fun ByteArray.toHexString(): String {
return joinToString(separator = " ") { byte -> "%02x".format(byte) }
}
private fun parsePacket(packetData: ByteArray) {
// Parse the IP Header (assuming IPv4)
val ipVersion = (packetData[0].toInt() shr 4) and 0xF
if (ipVersion == 4) {
Log.d("SS","IPv4 Packet Detected ${packetData.toHexString()}")
// Extract the source and destination IP addresses (IPv4)
val sourceIp = packetData.slice(12..15).toByteArray().toIpString()
val destinationIp = packetData.slice(16..19).toByteArray().toIpString()
Log.d("SS","Source IP: $sourceIp")
Log.d("SS","Destination IP: $destinationIp")
// Extract the Protocol (TCP/UDP/ICMP)
when (packetData[9].toInt()) {
6 -> { // TCP Protocol
Log.d("SS","TCP Packet Detected")
parseTcpHeader(packetData)
}
17 -> { // UDP Protocol
Log.d("SS","UDP Packet Detected")
parseUdpHeader(packetData, destinationIp)
}
else -> Log.d("SS","Other Protocol: ${packetData[9].toInt()}")
}
} else {
Log.d("SS","Non-IPv4 Packet")
}
}
// Parse TCP Header
private fun parseTcpHeader(packetData: ByteArray) {
// IP header length is in the lower 4 bits of the first byte (multiply by 4 to get byte size)
val ipHeaderLength = (packetData[0].toInt() and 0x0F) * 4
// TCP header starts right after the IP header
val tcpHeaderStart = ipHeaderLength
val sourcePort = (packetData[tcpHeaderStart].toInt() shl 8) or packetData[tcpHeaderStart + 1].toInt()
val destinationPort = (packetData[tcpHeaderStart + 2].toInt() shl 8) or packetData[tcpHeaderStart + 3].toInt()
Log.d("SS", "Source Port: $sourcePort")
Log.d("SS", "Destination Port: $destinationPort")
}
// Parse UDP Header
private fun parseUdpHeader(packetData: ByteArray, destinationIP: String) {
Log.w(TAG, "=================================== REQUEST ======================================")
// IP header length is in the lower 4 bits of the first byte (multiply by 4 to get byte size)
val ipHeaderLength = (packetData[0].toInt() and 0x0F) * 4
Log.d("SS", "IP header length $ipHeaderLength")
if (packetData.size <= ipHeaderLength){
return
}
if (packetData.size <ipHeaderLength + 8){
return
}
// UDP header starts right after the IP header
val udpHeader = packetData.copyOfRange(ipHeaderLength, ipHeaderLength+8)
val udpPayload = packetData.copyOfRange(ipHeaderLength+8, packetData.size)
Log.d("SS", "UDP HEADER, ${udpHeader.toHexString()}")
Log.d("SS", "UDP PAYLOAD, ${udpPayload.toHexString()}")
val sourcePort = ((udpHeader[0].toInt() and 0xFF) shl 8) or (udpHeader[1].toInt() and 0xFF)
val destinationPort = ((udpHeader[2].toInt() and 0xFF) shl 8) or (udpHeader[3].toInt() and 0xFF)
Log.d("SS","Source Port: $sourcePort")
Log.d("SS","Destination Port: $destinationPort")
val txID = ((udpPayload[0].toInt() and 0xFF) shl 8) or (udpPayload[1].toInt() and 0xFF)
val flags = ((udpPayload[2].toInt() and 0xFF) shl 8) or (udpPayload[3].toInt() and 0xFF)
val questionCount = ((udpPayload[4].toInt() and 0xFF) shl 8) or (udpPayload[5].toInt() and 0xFF)
val answerRRCount = ((udpPayload[6].toInt() and 0xFF) shl 8) or (udpPayload[7].toInt() and 0xFF)
val authorityRRCount = ((udpPayload[8].toInt() and 0xFF) shl 8) or (udpPayload[9].toInt() and 0xFF)
Log.d("SS", "transaction id: ${txID.toInt() and 0xFF}")
Log.d("SS", "flags: ${flags.toInt() and 0xFF}")
Log.d("SS", "question count: ${questionCount.toInt() and 0xFF}")
Log.d("SS", "answerRRCount: ${answerRRCount.toInt() and 0xFF}")
Log.d("SS", "authorityRRCount: ${authorityRRCount.toInt() and 0xFF}")
val domainParts = mutableListOf<String>()
var index = 12
while (true){
if (index >= udpPayload.size) {
Log.e("SS", "Index out of bounds while parsing domain. Current index: $index, payload size: ${udpPayload.size}")
break
}
val length = udpPayload[index].toInt() and 0xFF // Get the length byte as an unsigned int
if (length == 0) { // Termination byte indicating the end of the domain name
break
}
if (index + 1 + length > udpPayload.size) {
Log.e("SS", "Label length exceeds UDP payload size. index: $index, length: $length, payload size: ${udpPayload.size}")
break
}
// Extract the domain label and add it to the list
val label = udpPayload.copyOfRange(index + 1, index + 1 + length)
domainParts.add(String(label, Charsets.US_ASCII)) // Convert byte array to string
index += length + 1 // Move index forward
}
val domainName = domainParts.joinToString(".")
Log.d("SS", "Querying for domain $domainName")
Log.w(TAG, "=================================== ******* ======================================")
// Sending packets to destination
sendUDPPackets(packetData, udpPayload, destinationIP, destinationPort)
}
// Extension function to convert bytes to an IPv4 string
private fun ByteArray.toIpString(): String {
return joinToString(separator = ".") { byte -> (byte.toInt() and 0xFF).toString() }
}
// get the local IP of the device
private fun getLocalIpAddress(): String {
try {
val interfaces = NetworkInterface.getNetworkInterfaces()
for (networkInterface in interfaces) {
val addresses = networkInterface.inetAddresses
for (address in addresses) {
// Exclude loopback addresses
if (!address.isLoopbackAddress && address is InetAddress) {
val ipAddress = address.hostAddress
// Check if the IP is IPv4 (ignore IPv6 addresses)
if (ipAddress.indexOf(':') < 0) {
return ipAddress
}
}
}
}
} catch (e: Exception) {
Log.e("SS", "Error getting IP address: ${e.message}")
}
return "192.168.30.1" // Return null if no IP address found
}
// send UDP packet to the destination
private fun sendUDPPackets(packet:ByteArray, udppayload: ByteArray, destinationIp:String, destinationPort:Int) {
val socket = DatagramSocket()
protect(socket)
try {
val dnsServer: InetAddress = InetAddress.getByName("8.8.8.8")
val dnsPacket = DatagramPacket(udppayload, udppayload.size, dnsServer, 53)
socket.send(dnsPacket)
Log.d("SS", "Packet sent to $destinationIp:$destinationPort")
// Set a timeout for receiving replies
socket.soTimeout = 10000 // Timeout after 2 seconds
// Attempt to receive a reply
val datagramData = ByteArray(1024)
val replyPacket = DatagramPacket(datagramData, datagramData.size)
socket.receive(replyPacket)
val response = datagramData.copyOf(replyPacket.length)
val hexResponse = response.joinToString(" ") { String.format("%02X", it) }
Log.i(TAG, "Received DNS response in hex: $hexResponse")
injectPacket(packet, response)
} catch (e: SocketTimeoutException) {
Log.e("SS", "No reply received within the timeout period.")
} catch (e: Exception) {
Log.e("SS", "Error sending UDP packet: ${e.message}")
} finally {
socket.close()
}
}
private fun injectPacket(packet: ByteArray, dnsResponse: ByteArray) {
val ipOutPacket = packet.copyOf()
// swap ipv4
//12,13,14,15
//16,17,18,19
var temp = ipOutPacket[12]
ipOutPacket[12] = ipOutPacket[16]
ipOutPacket[16] = temp
temp = ipOutPacket[13]
ipOutPacket[13] = ipOutPacket[17]
ipOutPacket[17] = temp
temp = ipOutPacket[14]
ipOutPacket[14] = ipOutPacket[18]
ipOutPacket[18] = temp
temp = ipOutPacket[15]
ipOutPacket[15] = ipOutPacket[19]
ipOutPacket[19] = temp
// swaping ports
// 20,21
// 22,23
val ipHeaderLength = (packet[0].toInt() and 0x0F) * 4
//
temp = ipOutPacket[ipHeaderLength]
ipOutPacket[ipHeaderLength] = ipOutPacket[ipHeaderLength+2]
ipOutPacket[ipHeaderLength+2] = temp
temp = ipOutPacket[ipHeaderLength+1]
ipOutPacket[ipHeaderLength+1] = ipOutPacket[ipHeaderLength+3]
ipOutPacket[ipHeaderLength+3] = temp
val newArray = ipOutPacket.sliceArray(0 until ipHeaderLength+8)
val outArray = newArray + dnsResponse
val totalBytes = outArray.size
Log.d("SS","New byte length $totalBytes")
outArray[3] = (totalBytes and 0xFF).toByte() // Lower byte
outArray[2] = (totalBytes shr 8).toByte() // Upper byte
val hexResponse = outArray.joinToString(" ") { String.format("%02X", it) }
Log.i(TAG, "ipOutPacket response in hex: $hexResponse")
parseDnsResponse(outArray)
try {
val output = FileOutputStream(vpnInterface!!.fileDescriptor)
output.write(outArray)
output.flush()
} catch (e: Exception) {
Log.e("VPN", "Error injecting packet: ${e.message}")
}
}
private fun parseDnsResponse(packetData:ByteArray) {
Log.w(TAG, "=================================== RESPONSE ======================================")
val ipHeaderLength = (packetData[0].toInt() and 0x0F) * 4
if (packetData.size <= ipHeaderLength){
return
}
if (packetData.size <ipHeaderLength + 8){
return
}
// UDP header starts right after the IP header
val udpHeader = packetData.copyOfRange(ipHeaderLength, ipHeaderLength+8)
val udpPayload = packetData.copyOfRange(ipHeaderLength+8, packetData.size)
Log.d("SS", "UDP HEADER, ${udpHeader.toHexString()}")
Log.d("SS", "UDP PAYLOAD, ${udpPayload.toHexString()}")
val sourcePort = ((udpHeader[0].toInt() and 0xFF) shl 8) or (udpHeader[1].toInt() and 0xFF)
val destinationPort = ((udpHeader[2].toInt() and 0xFF) shl 8) or (udpHeader[3].toInt() and 0xFF)
Log.d("SS","Source Port: $sourcePort")
Log.d("SS","Destination Port: $destinationPort")
val txID = ((udpPayload[0].toInt() and 0xFF) shl 8) or (udpPayload[1].toInt() and 0xFF)
val flags = ((udpPayload[2].toInt() and 0xFF) shl 8) or (udpPayload[3].toInt() and 0xFF)
val questionCount = ((udpPayload[4].toInt() and 0xFF) shl 8) or (udpPayload[5].toInt() and 0xFF)
val answerRRCount = ((udpPayload[6].toInt() and 0xFF) shl 8) or (udpPayload[7].toInt() and 0xFF)
val authorityRRCount = ((udpPayload[8].toInt() and 0xFF) shl 8) or (udpPayload[9].toInt() and 0xFF)
Log.d("SS", "transaction id: ${txID.toInt() and 0xFF}")
Log.d("SS", "flags: ${flags.toInt() and 0xFF}")
Log.d("SS", "question count: ${questionCount.toInt() and 0xFF}")
Log.d("SS", "answerRRCount: ${answerRRCount.toInt() and 0xFF}")
Log.d("SS", "authorityRRCount: ${authorityRRCount.toInt() and 0xFF}")
val domainParts = mutableListOf<String>()
var index = 12
while (true){
if (index >= udpPayload.size) {
Log.e("SS", "Index out of bounds while parsing domain. Current index: $index, payload size: ${udpPayload.size}")
break
}
val length = udpPayload[index].toInt() and 0xFF // Get the length byte as an unsigned int
if (length == 0) { // Termination byte indicating the end of the domain name
break
}
if (index + 1 + length > udpPayload.size) {
Log.e("SS", "Label length exceeds UDP payload size. index: $index, length: $length, payload size: ${udpPayload.size}")
break
}
// Extract the domain label and add it to the list
val label = udpPayload.copyOfRange(index + 1, index + 1 + length)
domainParts.add(String(label, Charsets.US_ASCII)) // Convert byte array to string
index += length + 1 // Move index forward
}
val domainName = domainParts.joinToString(".")
Log.d("SS", "Querying for domain $domainName")
index += 5 // skip QTYPE (2 bytes) and QCLASS (2 bytes)
// Parse the answer section
for (i in 0 until answerRRCount) {
// Answer Name (can be a pointer)
val namePointer = ((udpPayload[index].toInt() and 0xFF) shl 8) or (udpPayload[index + 1].toInt() and 0xFF)
index += 2 // Move past the pointer
// Type (A, CNAME, etc.)
val type = ((udpPayload[index].toInt() and 0xFF) shl 8) or (udpPayload[index + 1].toInt() and 0xFF)
index += 2
// Class (Internet = 1)
val classType = ((udpPayload[index].toInt() and 0xFF) shl 8) or (udpPayload[index + 1].toInt() and 0xFF)
index += 2
// TTL (Time to live)
val ttl = ((udpPayload[index].toInt() and 0xFF) shl 24) or
((udpPayload[index + 1].toInt() and 0xFF) shl 16) or
((udpPayload[index + 2].toInt() and 0xFF) shl 8) or
(udpPayload[index + 3].toInt() and 0xFF)
index += 4
// Data length
val dataLength = ((udpPayload[index].toInt() and 0xFF) shl 8) or (udpPayload[index + 1].toInt() and 0xFF)
index += 2
// Data (IP address or CNAME)
val data: String = when (type) {
1 -> { // A record (IPv4 address)
val ip = "${udpPayload[index].toInt() and 0xFF}.${udpPayload[index + 1].toInt() and 0xFF}.${udpPayload[index + 2].toInt() and 0xFF}.${udpPayload[index + 3].toInt() and 0xFF}"
index += 4 // Move past the IP address
ip
}
5 -> { // CNAME record
val cnameParts = mutableListOf<String>()
var cnameIndex = index
while (udpPayload[cnameIndex].toInt() != 0) {
val labelLength = udpPayload[cnameIndex].toInt() and 0xFF
val cnameLabel = udpPayload.copyOfRange(cnameIndex + 1, cnameIndex + 1 + labelLength)
cnameParts.add(String(cnameLabel, Charsets.US_ASCII))
cnameIndex += labelLength + 1
}
index = cnameIndex + 1 // Move past the CNAME
cnameParts.joinToString(".")
}
else -> {
"Unknown type"
}
}
Log.d("SS", "Answer #$i - Name: $namePointer, Type: $type, Class: $classType, TTL: $ttl, Data: $data")
}
Log.w(TAG, "=================================== ***** ======================================")
}
override fun onDestroy() {
super.onDestroy()
vpnInterface?.close()
}
}
Logs:
W =================================== REQUEST ======================================
2024-09-28 19:31:57.244 5366-5550 SS com.example.vpn D IP header length 20
2024-09-28 19:31:57.245 5366-5550 SS com.example.vpn D UDP HEADER, 76 c5 00 35 00 28 07 75
2024-09-28 19:31:57.248 5366-5550 SS com.example.vpn D UDP PAYLOAD, 00 f5 01 00 00 01 00 00 00 00 00 00 03 77 77 77 06 72 65 64 64 69 74 03 63 6f 6d 00 00 01 00 01
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D Source Port: 30405
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D Destination Port: 53
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D transaction id: 245
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D flags: 0
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D question count: 1
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D answerRRCount: 0
2024-09-28 19:31:57.249 5366-5550 SS com.example.vpn D authorityRRCount: 0
2024-09-28 19:31:57.250 5366-5550 SS com.example.vpn D Querying for domain www.reddit.com
2024-09-28 19:31:57.250 5366-5550 SS com.example.vpn W =================================== ******* ======================================
=================================== RESPONSE ======================================
ipOutPacket response in hex: 45 00 00 9F 4A 30 40 00 40 11 00 EC 08 08 08 08 C0 A8 1E DD 00 35 76 C5 00 28 07 75 00 F5 81 80 00 01 00 05 00 00 00 00 03 77 77 77 06 72 65 64 64 69 74 03 63 6F 6D 00 00 01 00 01 C0 0C 00 05 00 01 00 00 13 05 00 17 06 72 65 64 64 69 74 03 6D 61 70 06 66 61 73 74 6C 79 03 6E 65 74 00 C0 2C 00 01 00 01 00 00 00 18 00 04 97 65 01 8C C0 2C 00 01 00 01 00 00 00 18 00 04 97 65 C1 8C C0 2C 00 01 00 01 00 00 00 18 00 04 97 65 41 8C C0 2C 00 01 00 01 00 00 00 18 00 04 97 65 81 8C
2024-09-28 19:31:57.341 5366-5551 SS com.example.vpn D UDP HEADER, 00 35 d5 5b 00 2e 10 c2
2024-09-28 19:31:57.341 5366-5489 SS com.example.vpn I Received DNS response in hex: E4 23 81 80 00 01 00 05 00 00 00 00 07 70 72 65 76 69 65 77 04 72 65 64 64 02 69 74 00 00 01 00 01 C0 0C 00 05 00 01 00 00 00 23 00 21 09 64 75 61 6C 73 74 61 63 6B 06 72 65 64 64 69 74 03 6D 61 70 06 66 61 73 74 6C 79 03 6E 65 74 00 C0 2D 00 01 00 01 00 00 00 17 00 04 97 65 41 8C C0 2D 00 01 00 01 00 00 00 17 00 04 97 65 C1 8C C0 2D 00 01 00 01 00 00 00 17 00 04 97 65 81 8C C0 2D 00 01 00 01 00 00 00 17 00 04 97 65 01 8C
2024-09-28 19:31:57.341 5366-5489 SS com.example.vpn D New byte length 170
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D UDP PAYLOAD, 00 f5 81 80 00 01 00 05 00 00 00 00 03 77 77 77 06 72 65 64 64 69 74 03 63 6f 6d 00 00 01 00 01 c0 0c 00 05 00 01 00 00 13 05 00 17 06 72 65 64 64 69 74 03 6d 61 70 06 66 61 73 74 6c 79 03 6e 65 74 00 c0 2c 00 01 00 01 00 00 00 18 00 04 97 65 01 8c c0 2c 00 01 00 01 00 00 00 18 00 04 97 65 c1 8c c0 2c 00 01 00 01 00 00 00 18 00 04 97 65 41 8c c0 2c 00 01 00 01 00 00 00 18 00 04 97 65 81 8c
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D Source Port: 53
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D Destination Port: 30405
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D transaction id: 245
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D flags: 128
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D question count: 1
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D answerRRCount: 5
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D authorityRRCount: 0
2024-09-28 19:31:57.345 5366-5550 SS com.example.vpn D Querying for domain www.reddit.com
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn D Answer #0 - Name: 49164, Type: 5, Class: 1, TTL: 4869, Data: reddit.map.fastly.net
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn D Answer #1 - Name: 49196, Type: 1, Class: 1, TTL: 24, Data: 151.101.1.140
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn D Answer #2 - Name: 49196, Type: 1, Class: 1, TTL: 24, Data: 151.101.193.140
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn D Answer #3 - Name: 49196, Type: 1, Class: 1, TTL: 24, Data: 151.101.65.140
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn D Answer #4 - Name: 49196, Type: 1, Class: 1, TTL: 24, Data: 151.101.129.140
2024-09-28 19:31:57.346 5366-5550 SS com.example.vpn W =================================== ***** ======================================
- As there are very limited resource on this any help will be appreciated.
- Have been stuck in this issue for a week now. 😢
Upvotes: 0
Views: 79