The Linux concept of ‘Everything is a file’ is a very helpful one.
This post share details of an attacker can read the network statistics even if the commands like ‘netstat’ and ‘lsof’ are not available.
/proc is a virtual file system that is created each time a system boots and is dissolved during shutdown. The directory /proc contains (among other things) one subdirectory for each process running on the system, which is named after the process ID (PID).
One such directory is /proc/<PID>/net This directory provides a comprehensive look at various networking parameters and statistics. Each directory and virtual file within this directory describes aspects of the system’s network configuration.
For example: /proc/<PID>/net/tcp — Contains detailed TCP socket information.
A sample file looks like
DECODING THE VALUES
The values are divided in different columns like local_address, rem_address [remote address], st [status code], etc
Values in each column is stored in Hexadeciaml notation in ‘Little Endian’ format
This means
0100007F should be read as 7F 00 00 01 which gets decoded to 127.0.0.1
and the value of port number 8124 gets decoded to 33060
This is also shown in below image
The below script created in Ruby decodes the fields and print in a tabular format. All it needs is process ID as the parameter. The script also displays the command line used to execute the process and the Apparmor status of process
The output from the script is given below
The code
#!/bin/env ruby
=begin
A DECODER FOR /PROC/<PID>/NET/TCP or UDP FILE
DISPLAYS NETWORK STATS AND INFORMATION OF PROCESS
COMMAND LINE AND APPARMOR STATUS OF PROCESS
REGARDS: ALPHA THREAT IT SOLUTIONS
USAGE : ruby proc_net.rb <PROCESS ID>
process ID can be obtained via ps -A command
=end
local_ip=[]; local_port=[]; remote_address=[]; remote_port=[]; state=[]
final_local=["\nLocal_IP : PORT\t\t\t\b REMOTE_IP : REMOTE_PORT\t\tSTATE\n"]
filename="/proc/#{ARGV[0]}/net/tcp"
begin
a=File.readlines(filename).each do |x|
if x.match?(/[A-Z 0-9]{8}:[A-Z -9]{4}/)
y = x.split(":")
local_ip << y[1].sub!(" ",'')
local_port << y[2].split(" ")[0]
remote_address << y[2].split(" ")[1]
remote_port << y[3].split(" ")[0]
if y[4].split(" ")[1] == '00'
state << "ERROR_STATUS"
elsif y[4].split(" ")[1] == '01'
state << "TCP_ESTABLISHED"
elsif y[4].split(" ")[1] == '02'
state << "TCP_SYN_SENT"
elsif y[4].split(" ")[1] == '03'
state << "TCP_SYN_RECV"
elsif y[4].split(" ")[1] == '04'
state << "TCP_FIN_WAIT1"
elsif y[4].split(" ")[1] == '05'
state << "TCP_FIN_WAIT2"
elsif y[4].split(" ")[1] == '06'
state << "TCP_TIME_WAIT"
elsif y[4].split(" ")[1] == '07'
state << "TCP_CLOSE"
elsif y[4].split(" ")[1] == '00'
state << "TCP_CLOSE_WAIT"
elsif y[4].split(" ")[1] == '09'
state << "TCP_LAST_ACK"
elsif y[4].split(" ")[1] == '0A'
state << "TCP_LISTEN"
elsif y[4].split(" ")[1] == '0B'
state << "TCP_CLOSING"
else
state << "UNKNOWN"
#if end
end
end
rescue Exception
next
#exception end
end
#do end
end
def print_ip(x)
return x.scan(/../).reverse.map{|x| x.to_i(16)}.join('.')
end
def print_port(y)
return y.to_i(16)
end
for length in 0..local_ip.count-1
#sets spacing for printing columns
size=print_ip(local_ip[length]).to_s + ' : ' + print_port(local_port[length]).to_s
size=size.length.to_i
fs=23-size
size=print_ip(remote_address[length]).to_s + ' : ' + print_port(remote_port[length]).to_s
size=size.length.to_i
ns=23-size
#create a final array with all data
final_local << print_ip(local_ip[length]).to_s + ' : ' + print_port(local_port[length]).to_s + " "*fs + "\t\t" + print_ip(remote_address[length]).to_s + ' : ' + print_port(remote_port[length]).to_s + " "*ns + "\t\t" + state[length].to_s
end
puts final_local
puts "\nProcess Command Line: " + File.read("/proc/#{ARGV[0]}/cmdline") + "\n\n" if File.exists?("/proc/#{ARGV[0]}/cmdline")
puts "AppArmour status: " + File.read("/proc/#{ARGV[0]}/attr/apparmor/current") + "\n" if File.exists?("/proc/#{ARGV[0]}/attr/apparmor/current")


