using the MCP23008 gpio expander with chip Pro and perl

I’ve mentioned the next thing co.  chip family of single board computers before.  It was billed as the world’s first $9 computer.  I first heard of it on NPR one morning and couldn’t get to a computer to pre-order one fast enough.  A full-blown debian distribution, wifi, i2c, SPI, 8-gpio pins, composite video, UART, USB and more about the size of a credit card.  It seemed too good to be true.  I guess it was, because they did not last long.  I bought 15 of them, but never received the last five before they tanked.  They also came out with the chip Pro,  an even smaller, somewhat stripped down version of the chip aimed at being a platform for mass-produced products for $16.  I really liked the optional ufl wifi antenna port.  With an external high-gain antenna, it is really great at sniffing wifi networks.   It is totally awesome, and I wish I had 100 of them.

chip pro
chip Pro

Sadly, I burned up all my gpio pins on a project.  I didn’t want to scrap my chip pro, however, and decided to use the MCP23008 gpio I/O expander to get my gpio back up and working.

I wrote my own easy to use perl based driver to interface with the MCP23008.  There are some great tools on CPAN that implement I2C for stuff like this, but I went an even simpler route.  My driver (at a mere 40 lines of code)  uses system calls (via backticks)  to read and write to registers on the gpio expander.  For example, to read the gpio register, the script will just make a system call like so:


sudo i2cget -y 1 0X20 0x09

This tells the chip Pro to talk to the i2c device on bus 1, at address 0x20, and read the value in register 0x09 (gpio register).   Here is the driver in it’s entirety:

#!/usr/bin/perl -w
use strict;

=pod
utilities for MCP23008 gpio extender
execute any scripts that use this as sudo
=cut

#global variables
my $address;
my $gpio_w = 0;

sub gpio_enable{
	#function takes the i2c address of the MCP23008
	#it also  sets the IO direction
	#register.  Must be a value from 0-255
	#1 is input, 0 is output

	$address = $_[0];
	my $ioreg_val = $_[1];

	#write io reg
	`i2cset -y 1 $address 0x00 $ioreg_val`;
}#end gpio_enable

sub gpio_read{
	#function uses i2cget to
	#read gpio register 0x09
	my $reading = `i2cget -y 1 $address 0x09`;
	return $reading;
}#end gpio_read

sub gpio_write{
	#takes int from 0-255 to write to the
	#gpio register
	$gpio_w = $_[0];
	`i2cset -y 1 $address 0x09 $gpio_w`;
}#end gpio_write

1;

So simple, I’m embarrassed, but it works great.  To test, I wrote a simple script that visually counts to 255 in binary with LED output.

perl mcp23008 driver
i2c mcp23008 binary counter with chip Pro and perl

Here is the script:

#!/usr/bin/perl
use strict;
use Time::HiRes qw(usleep);
require 'gpio_MCP23008.pl';

#init the 23008 IO register as output
gpio_enable(0x20, 0);

for(my $i=0;$i<256;$i++){
usleep(50000);
gpio_write($i);
}#end for

sleep(5);
gpio_write(0);

 

getting started with snort

 snort IPS
snort IPS

Wireshark is great.  Personally, I prefer tcpdump from the command line with my own scripts to extract specific results I am looking for.  If you want to become dangerous with tcpdump, you should check out Daniel Miessler’s tutorial on the subject and start experimenting.  That being said, my ideas of how to see what is actually going on in networks that I manage are constrained to my limited knowledge base, experience and imagination, and I am sure it is not terribly hard to outsmart me:

  • is my LAMP server getting hit with  SQL injection attacks?
  • is there some (unknown to me) vulnerability to a package on my system that I am not aware of yet?
  • is a device on my network compromised by some type of malware?
  • is the NSA all up in my biz?
  • am I getting port-scanned?  If so, by whom? What are they looking for?
  • is someone trying really hard with nmap scripts or other penetration tools to hack me?
  • some kinds of attacks I can’t even fathom?

I’m pretty sure at this point, the answer to all of these questions is YES!  Well, I want to know what the heck is going on, and even with decent tcpdump skills, I am not sure how to tell if, for instance, I am being hit with an OS detection scan or something like that.  Snort is the perfect tool for problems like this.

Snort sniffer mode

Snort’s sniffer mode is pretty cool.

sudo snort -i enp1s0 -dev

This command will basically show every frame on the wire whether IPv4, IPv6, et. al,  with the hex and ascii output to the console.  Here is the output from this command:

snort sniffer mode
snort sniffer mode

If I just want to see frames moving on my LAN in this manner, snort is definitely not my tool of choice; tcpdump is.  However, in sniffer mode, I can capture and log network traffic in binary  (which is incredibly fast) and analyze it later.  But, I want to be alerted about intrusions…..

Snort Network Intrusion Detection Mode

This is what I think makes snort special.  I installed snort as an NIDS on a few machines in my lab by following this awesome turorial. I intend to attack my snort machines and post the results soon.

battery discharge analyzer

pid3A Battery loadtest analyzer
pid3A Battery loadtest analyzer

This is the first of probably several posts about a battery discharge analyzer I have been perfecting for a little over a year now. I work in the telecommunications field where uninterrupted power is a must. To achieve this, there are large, high-capacity backup battery plants that keep equipment up and running in the event of a commercial power outage. Generally, but not always, these are -48 volt DC lead-acid batteries  anywhere from 50 amp-hour to over 2000 amp-hour depending on the application and power requirements of the facility they are backing up. It is important to know how long a battery plant will be able to supply power before a facility goes down. To calculate this, you take into factor the combined current draw of all the equipment, and the amp-hour rating of the batteries. So, if your equipment loads the batteries at 15 amps, and you have 200-AH batteries, you should be able to power the site for 13.3 hours if your batteries are at 100% capacity.

hours of backup power = amp hours / site load

200AH / 15A site load = 13.3 hours (assuming 100% battery capacity)

But what if the batteries are not at 100% capacity?  What if they are in poor condition and have much less than 100% capacity?  How do you know?  The answer is capacity testing with a load bank.  IEEE450 describes load testing recommendations for vented lead-acid battery plants and IEEE1188 for valve-regulated lead-acid (VRLA) battery systems.  While this load test analyzer will work with any type of battery composition,  I have focused on lead-acid batteries.

characteristic discharge curve of a lead-acid cell
characteristic discharge curve of a lead-acid cell

The picture above shows a characteristic discharge curve of a lead-acid cell under a slight load.  It dips into a trough,  rebounds, and discharges somewhat logarithmically until it totally drops off.  This graph was generated by my load test analyzer, which I refer to as pid3A (pi, as in raspberry pi, data acquisition and analysis).  It takes thousands of samples,   plots the results on dynamically generated graphs, creates user-defined reports,  stores results for download, and displays test results via it’s internal web server.

Right now, I am on my second prototype.  It can sample up to 24 individual cells during a load test.  It can function as a stand-alone analyzer that travels with a separate load bank, or an online all the time battery monitoring system.

IEEE450 IEEE1188 load tester invention
pid3a battery load test analyzer in online monitoring system configuration

The samples are taken by three MCP3208 12-bit analog to digital converters (ADC).  Each chip has eight inputs, and is connected to a dedicated voltage reference.  I wrote a bit-banged driver for the ADC chip in perl using my own gpio interface.

MCP3208 Analog to digital converter
MCP3208 Analog to digital converter
MCP3208 Connections to raspberry pi
MCP3208 Connections to raspberry pi

The maximum input voltage the ADC’s can sample is 5VDC, so I had to make an adjustable voltage divider network to tune each cell input into the device.

Voltage divider network
Voltage divider network

The sampling rate is user defined.  During the discharge test, results are written to a CSV file which is used later to process the results, and generate the graphs and reports.

To keep the file small, it contains the unix epoch timestamp, sample number, the cell# being sampled,  the binary OP code from the ADC reading, and the voltage relative the negative terminal of the battery plant (calculated from the ADC opcode and some test parameters).

I used the DBD::CSV database driver from cpan to mine data and produce results from the readings contained in the csv file.  I also developed a handy desktop application for testing  that can generate graphs for my device, or any other general purpose csv database.

CSV database analyzer
CSV database analyzer

Below is a graph produced by pid3A of a 350Ah 24-cell VRLA load test that failed about half way through.  You can clearly see that a few cells began failing quickly, and nearly reversed polarity.

VLRA load test results
VLRA load test results

Like I said, this is merely an introduction to the pid3A loadtest analyzer with more to follow.

Here is an incomplete whitepaper I did for the first prototype some time ago.  It shows how far pid3A has come

perl-based GPIO control for pine64

The pine64, like many single board computers, has a file system interface for its gpio pins.  This means that reading and writing logic levels on a particular gpio pin is a matter of reading and writing to the particular file for that pin.  Interacting with files in this manner is something that perl excels at.  That is one reason why I mostly forego using ready made tools like wiringpi and make my own gpio interface functions.  This also help me to understand the system more completely, and customize according to the way that I think, instead of using another’s canned tools.

I have written a number of perl scripts for the raspberry pi that control the gpio system interface and bit-banged drivers for several popular integrated circuits such as the MCP3004, MCP3008, MCP3208 (all analog to digital converters) , MAX7219, 74HC595, and others.  The pine64 has a (mostly) compatible 40-pin gpio header.  I only had to change one line of my gpio driver to make it work with the pine64.  Here is the perl gpio function file in it’s entirety:

 

#!/usr/bin/perl -w
use strict;

=pod
gpio utilities for pine64
enable, disable, read, write
=cut

#global vars
#array for gpio pin numbers
my @line_nums = (227,226,362,71,233,76,64,65,66,229,230,69,73,80,32,33,72,77,78,79,67,231,68,70,74,75);
my ($iox, $status);

sub gpio_enable{
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	#print "IO line: $iox\n";

	my $gpiosz = @line_nums;	
	#print "gpiosz: $gpiosz\n";

	my $direction = $_[1];
	#print "direction: $direction\n";

	#err chk
	if($ind < 0 || $ind > $gpiosz){
		print "INVALID GPIO RANGE\n";
		exit;
	}#end if invalid index
	if($direction ne 'in' && $direction ne 'out'){
		print "INVALID DIRECTION\n";
		exit;
	}#end if invalid direction
	
	#write gpio pin val to export file
	open(EF, ">", "/sys/class/gpio/export") or die $!;
	print EF $iox;
	close(EF);

	#set direction of gpio pin
	open(GF, ">", "/sys/class/gpio/gpio$iox/direction") or die $!;
	print GF $direction;
	close(GF);

}#end gpio_enable

sub gpio_read{
	#gpio number as arg, returns direction in/out
	my $ind = $_[0];
	$iox = $line_nums[$ind];
	
	my $value = ''; 

	#reads state of gpio pin
	open(GS, "/sys/class/gpio/gpio$iox/value") or die $!;
	while(){ $value = $_; };
	#print "$iox val: $value";
	close(GS);

	if($value == 0 || $value == 1){
		return $value;
	}#end unless
	else{ 
		print "ERROR: Undefined value on GPIO $iox\n";
		exit;
	}#end else
}#end gpio_read

sub gpio_write{
	my $ind = $_[0];
	$iox = $line_nums[$ind];

	my $value = $_[1];

	#write value to gpio pin
	open(GV, ">", "/sys/class/gpio/gpio$iox/value") or die $!;
	print GV $value;
	close(GV);
}#end gpio_write

1;

Very simple (under 80 lines) and very effective.  This has been tested ad infinitum to work as expected.  This gpio function file is also the main building block for my bit-banged drivers.    To demonstrate,  here is an example using a MAX7219 64-LED display driver.  I wrote a custom bit-banged driver using this gpio script.

pine64 perl max7219 driver
pine64 MAX7219 perl based driver

When I write a driver like this, I get the data sheet from the manufacturer, and write functions to set all of the configuration registers and control the IO functions.  I use these 8-position, 7-segment displays frequently in my home brew projects, so I started there with my new pine64.  The driver I wrote for this particular display has some visual effects,  sets the intensity, and has some easy to use functions that can read text files word by word.  I can also handle cascaded displays and a few more novel features that I have not seen anywhere else.  Here is the display driver in its current entirety:

 

#!/usr/bin/perl 
use strict;
use Time::HiRes qw (usleep);
require "/home/debian/gpio/gpio_pine64.pl";

=pod
routines to control a MAX7219 8-digit LED
display driver.  
for now:
CLK: gpio 0
DS : gpio 1
LOAD:gpio 2
data is shifted into regs on rising edge of CLK, 
output displayed on rising edge of LOAD

NOTES:
This version supports cascading several 7219's.  I have
noticed that even with programs that assume only one
7219, that if there is another cascaded, the exact same
output is mirrored on subsequent displays. 

I assume that turn_on, set_intenisty, set_scanlimit, etc
all get shifted into the next chip anyway, however, 
these subroutines take the number of cascaded 7219s as
an argument and shift in and latch the necessary
controls to use the display.  
=cut

#global vars for gpio init function
#init should be called from script
#init7219 (0,1,2);
my ($clk, $ds, $load);

#SUBROUTINE PROTOTYPES
=pod

=cut

############### GLOBAL VARS #####################
#shutdown register 
my @sdreg = (0,0,0,0,1,1,0,0);
my @turn_on = (0,0,0,0,0,0,0,1);
my @turn_off = (0,0,0,0,0,0,0,0);

#set scan limit register 
my @slimreg = (0,0,0,0,1,0,1,1);
#last 3 LSBs scan all 8 digits
my @slregall = (0,0,0,0,0,1,1,1);

#display test register
my @disptstreg = (0,0,0,0,1,1,1,1);

#intensity register
my @intreg = (0,0,0,0,1,0,1,0);

#digit addrs
#first 4 bits D15-D12
#don't care bits, then addr
my @_d0 = (0,1,0,1,0,0,0,1);
my @_d1 = (0,1,0,1,0,0,1,0);
my @_d2 = (0,1,0,1,0,0,1,1);
my @_d3 = (0,1,0,1,0,1,0,0);
my @_d4 = (0,1,0,1,0,1,0,1);
my @_d5 = (0,1,0,1,0,1,1,0);
my @_d6 = (0,1,0,1,0,1,1,1);
my @_d7 = (0,1,0,1,1,0,0,0);

#0-9
my @_1 = (0,0,1,1,0,0,0,0);
my @_2 = (0,1,1,0,1,1,0,1);
my @_3 = (0,1,1,1,1,0,0,1);
my @_4 = (0,0,1,1,0,0,1,1);
my @_5 = (0,1,0,1,1,0,1,1);
my @_6 = (0,1,0,1,1,1,1,1);
my @_7 = (0,1,1,1,0,0,0,0);
my @_8 = (0,1,1,1,1,1,1,1);
my @_9 = (0,1,1,1,0,0,1,1);
my @_0 = (0,1,1,1,1,1,1,0);
my @_all = (1,1,1,1,1,1,1,1);

#alpha chars
my @_a = (0,1,1,1,0,1,1,1);
my @_b = (0,0,0,1,1,1,1,1);
my @_c = (0,1,0,0,1,1,1,0);
my @_d = (0,0,1,1,1,1,0,1);
my @_e = (0,1,0,0,1,1,1,1);
my @_f = (0,1,0,0,0,1,1,1);
my @_g = (0,1,1,1,1,0,1,1);
my @_h = (0,0,1,1,0,1,1,1);
my @_i = (0,0,0,0,0,1,0,0);
my @_j = (0,0,1,1,1,1,0,0);
my @_k = (0,0,1,1,0,1,1,0);
my @_l = (0,0,0,0,1,1,1,0);
my @_m = (1,0,0,1,0,1,0,0);
my @_n = (0,0,0,1,0,1,0,1);
my @_o = (0,0,0,1,1,1,0,1);
my @_p = (0,1,1,0,0,1,1,1);
my @_q = (0,1,1,1,0,0,1,1);
my @_r = (0,0,0,0,0,1,0,1);
my @_s = (0,1,0,1,1,0,1,1);
my @_t = (0,0,0,0,1,1,1,1);
my @_u = (0,0,0,1,1,1,0,0);
my @_v = (0,0,0,1,1,1,0,0);
my @_w = (0,0,0,1,0,1,0,0);
my @_x = (0,0,0,1,1,1,0,1);
my @_y = (0,0,1,1,1,0,1,1);
my @_z = (0,1,1,0,1,1,0,1);

#special chars
my @_dp = (1,0,0,0,0,0,0,0);
my @_sp = (0,0,0,0,0,0,0,0);
my @_dash = (0,0,0,0,0,0,0,1);
my @_comma = (1,0,0,0,0,0,0,0);
my @_period = (1,0,0,0,0,0,0,0);
my @_question = (1,1,1,0,0,0,1,0);
my @_exclaimation = (1,0,1,1,0,0,0,0);

#individual segments
my @_seg_a = (0,1,0,0,0,0,0,0);
my @_seg_b = (0,0,1,0,0,0,0,0);
my @_seg_c = (0,0,0,1,0,0,0,0);
my @_seg_d = (0,0,0,0,1,0,0,0);
my @_seg_e = (0,0,0,0,0,1,0,0);
my @_seg_f = (0,0,0,0,0,0,1,0);
my @_seg_g = (0,0,0,0,0,0,0,1);

#@all_digits is an arra of array references
#representing each digit used to operate
#on each digit 
my @all_digits = (\@_d0,\@_d1,\@_d2,\@_d3,\@_d4,\@_d5,\@_d6,\@_d7);

#@all_segs is an array of array references
#representing each segment of a 7-seg LED array
my @all_segs = (\@_seg_a,\@_seg_b,\@_seg_c,\@_seg_d,\@_seg_e,\@_seg_f,\@_seg_g);

#Hash that maps alphanumeric chars to array ref
my %alphanums = (
	'A' => \@_a,
	'B' => \@_b,
	'C' => \@_c,
	'D' => \@_d,
	'E' => \@_e,
	'F' => \@_f,
	'G' => \@_g,
	'H' => \@_h,
	'I' => \@_i,
	'J' => \@_j,
	'K' => \@_k,
	'L' => \@_l,
	'M' => \@_m,
	'N' => \@_n,
	'O' => \@_o,
	'P' => \@_p,
	'Q' => \@_q,
	'R' => \@_r,
	'S' => \@_s,
	'T' => \@_t,
	'U' => \@_u,
	'V' => \@_v,
	'W' => \@_w,
	'X' => \@_x,
	'Y' => \@_y,
	'Z' => \@_z, 
	'0', => \@_0, 
	'1', => \@_1, 
	'2', => \@_2, 
	'3', => \@_3, 
	'4', => \@_4, 
	'5', => \@_5, 
	'6', => \@_6, 
	'7', => \@_7, 
	'8', => \@_8, 
	'9', => \@_9, 
	'.' => \@_period, 
	'-' => \@_dash, 
	',' => \@_comma, 
	'?' => \@_question, 
	'!' => \@_exclaimation
);#end %alphanums declaration

############### SUBROUTINES #####################
sub shift_in{
=pod
data is shifted in with 16 bit packets: 8-bit segment,
4-bit digit address, 4-bit dont care bits
=cut
	#array ref seg data
	my $leds = $_[0];
	#array ref seg addr
	my $addr = $_[1];
	#number cascaded 7219s
	my $ncas = $_[2];
	#delay in milliseconds
	my $delay = $_[3];
	#latch flag
	my $lf = $_[4];

	#high flag for data gpio line
	my $hf = 0;

	#my $ncp = $ncas * 32;	#min number 32 for 16 clock pulses
	my $ncp = 32;
	##print "ncp: $ncp\n"; 

	#main clock loop
	my $i=0;
	#$state toggles from 1 to 0 needed for clock pulse
	my $state = 0;					
	#ensures first pulse goes from low to high
	my $seed = 3;					

	while($i<$ncp){#correct num clock pulses $state = $seed%2; $seed++; #load data in last 8 clock pulses #make DS high before clock pulse; only on even num #so index array is whole number #MSB read first, then addr, last data if(($i%2) eq 0){ #address D8-D11, and don't care bits D12-D15 if($addr->[$i/2] eq 1 && $i<=14){ gpio_write($ds,1); $hf = 1;#set high flag }#end addr high bit #7-seg data, D0-D7 if($leds->[($i-16)/2] eq 1 && $i > 14){#array ref, light this led up
				#test; set q1 high
				##print "inside data loop, i: $i\tindex: " .(($i-16)/2) . "\n";
				gpio_write($ds, 1);
				$hf = 1;#set high flag
			}#end if build D0-D7

		}#end if even $i 

		#TEST
		#print "i:\t$i\nD:\t$hf\n\n";
	
		#toggle clock pulse
		gpio_write($clk, $state);
		usleep($delay);#sleep .001 sec

		#lower data if high flag set
		if($hf eq 1){
			gpio_write($ds,0);
			$hf = 0;#reset high flag
		}#end if

		$i++;
	}#end while

	#latch output lines
	if($lf eq 1){	
		load();
	}#end latch flag

	gpio_write($clk,0);#set clock pulse low

}#end shift_in

sub load{

=pod
toggles LOAD pin on 7219 to send to output pins.  When the pin goes from low to high, sets output. 
=cut

	#enable XIO-p2
	gpio_enable($load, 'out');

	#ensure it is low
	gpio_write($load, 0);#low

	#go from low to high
	gpio_write($load, 1);#high
	#print "LATCH HIGH\n";
	usleep(500);#pause 0.0005 second

	#reset to low
	gpio_write($load, 0);#low
	#print "LATCH LOW\n";
	
}#end load

sub init7219{
=pod
initializes gpio lines to low
=cut
	$clk = $_[0];
	$ds = $_[1];
	$load = $_[2];

	gpio_enable($clk, 'out');
	gpio_write($clk, 0);
	gpio_enable($ds, 'out');
	gpio_write($ds, 0);
	gpio_enable($load, 'out');
	gpio_write($load, 0);
}#end init

sub print_sentence{
	#currently for a single 7219 array
	my $sentence = $_[0];
	#set to all uppercase letters
	$sentence = uc $sentence;

	#delay between words in microseconds
	my $delay = $_[1];

	#all off flag, set if reading, unset if
	#you want the text to remain on the array
	#if empty, clears the text
	my $cleartxt_flag = $_[2];

	my @words = split / /, $sentence;
	my $numwords = @words;
=pod
	foreach my $word (@words){
		print $word."\n";
	}#end foreach
=cut
	
	#print "sentence: $sentence\nNumwords: $numwords\n";
	
	#loop through words
	for(my $i=0;$i<$numwords;$i++){
		#split word into an array of chars
		my @letters = split //, $words[$i];

		#number of chars in word
		my $numalphanums = @letters;
		#print "num letters: $numalphanums\n";
			
		#first letter
		my $ln = 0;
		
		#reverse @all_digits to display words
		#left to right
		my @rev_segs = reverse @all_digits;

		foreach my $digit (@rev_segs){
		
			#limited to 8 digits for now
			shift_in($alphanums{$letters[$ln]}, $digit, 1, 250, 1 );
			#print "letter: $letters[$ln]\n";
			$ln++;#go to next letter
		}#end letters inner for loop
		
		usleep($delay);
		unless($cleartxt_flag == 1){
			all_off();
		}#end if
	}#end for numwords
}#end print_sentence

sub print_interleaved{
	#takes separate strings for each
	#line of displays. The strings are
	#assumed to fit into the 8-digit
	#line of an led array for a 7219
	my $str1 = $_[0];
	my $str2 = $_[1];

	#upper case
	$str1 = uc $str1;
	$str2 = uc $str2;

	#convert strings to array of chars
	my @s1 = split //, $str1;
	my @s2 = split //, $str2;

	#pad string arrays with spaces
	#if less than 8 chars
	my $s1_len = @s1;
	my $s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	if($s1_len < 8){
		my $nsp = 8-$s1_len;
		for(my $n=$s1_len;$n<8;$n++){
			$s1[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	if($s2_len < 8){
		my $nsp = 8-$s2_len;
		for(my $n=$s2_len;$n<8;$n++){
			$s2[$n] = " ";
		}#end for pad w/ spaces
	}#end pad str1 with spaces

	$s1_len = @s1;
	$s2_len = @s2;
	#print "s1len: $s1_len\ts2len: $s2_len\n";

	#reverse @all_digits to display words
	#left to right
	my @rev_segs = reverse @all_digits;

	#init letter number to 0
	my $ln = 0;

	foreach my $digit (@rev_segs){
		shift_in($alphanums{$s2[$ln]}, $digit, 1, 250, 0 );
		shift_in($alphanums{$s1[$ln]}, $digit, 1, 250, 0 );
		load();
		$ln++;
	}#end for
}#end print_interleaved

sub turn_on{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "tu ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_on, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				#print "tu load\n";
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to normal operation
		shift_in(\@turn_on, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_on

sub turn_off{
	my $ncas = $_[0];
	if(defined($ncas)){
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@turn_off, \@sdreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set shutdown register to off
		shift_in(\@turn_off, \@sdreg, 1, 250, 1);
	}#end else
}#end turn_off

sub set_scanlimit{
	my $ncas = $_[0];
	if(defined($ncas)){
		#print "sl ncas defined\n";
		for(my $ni=0;$ni<$ncas;$ni++){
			shift_in(\@slregall, \@slimreg, $ncas, 250, 0);
			if($ni ==($ncas-1)){
				load();
				#print "sl load\n";
			}#end if
		}#end for
	}#end if multiple 7219's
	else{#just one
		#set scan limit register
		shift_in(\@slregall, \@slimreg, 1, 500, 1);
		#print "sl 1 chip\n";
	}#end else
}#end set_scanlimit

sub set_intensity{
	#takes string as arg: min, dim, mid, bright, max
	my $intensity = $_[0];
	
	#default to max
	my @intregdata = (0,0,0,0,1,1,1,1);
	if($intensity eq 'min'){
		@intregdata = (0,0,0,0,0,0,0,0);
	}#end if
	if($intensity eq 'dim'){
		@intregdata = (0,0,0,0,0,0,1,1);
	}#end if
	if($intensity eq 'mid'){
		@intregdata = (0,0,0,0,0,1,1,1);
	}#end if
	if($intensity eq 'bright'){
		@intregdata = (0,0,0,0,1,0,1,1);
	}#end if
	if($intensity eq 'max'){
		@intregdata = (0,0,0,0,1,1,1,1);
	}#end if

	#print "Intensity: $intensity\n";
	shift_in(\@intregdata, \@intreg, 1, 250, 1);
}#end set_intensity

sub all_off{
	#clear display
	#call after turned on, and
	#scan reg set to all digits
	my $ncas = $_[0];
	if(defined($ncas)){
		foreach my $digit(@all_digits){
			for(my $ni=0;$ni<$ncas;$ni++){
				shift_in(\@turn_off, $digit, $ncas, 100, 0);
				if($ni ==($ncas-1)){
					load();
				}#end if
			}#end for
		}#end outer for
	}#end if multiple 7219's
	else{#just one
		#@all_digits is an array of array references
		#representing each digit
		foreach my $digit (@all_digits){
			shift_in(\@turn_off, $digit, 1, 100, 1);
		}#end foreach
	}#end else
}#end all_off

sub disp_teston{
	#turn on display test, 
	#all digits and dp
	shift_in(\@turn_on, \@disptstreg, 1, 200, 1);
}#end disp_test

sub disp_testoff{
	#turn off display test, 
	#all digits and dp
	shift_in(\@turn_off, \@disptstreg, 1, 200, 1);
}#end disp_test

#################### EFFECTS######################
sub clockwise_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	
	while($k<$i){
		for(my $n=0;$n<6;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@all_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end clockwise_circles

sub countercw_circles{
	#number of iterations
	my $i = $_[0];
	my $k = 0;
	my @revall_segs = reverse @all_segs;
	while($k<$i){
		for(my $n=1;$n<7;$n++){
			#outer for, each segment
			for(my $x=0; $x<8; $x++){
				#inner for, each digit
				shift_in(@revall_segs[$n], @all_digits[$x], 1, 100, 1);
			}#end inner for
		}#end outer for

		$k++;

	}#end while
	all_off();
}#end countercw_circles

sub bullets_lrtop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rltop {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_a, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlmid {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_g, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_lrbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){ for(my $i=8;$i>=0;$i--){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop

sub bullets_rlbot {
	#number of iterations
	my $ni = $_[0];
	for(my $n=0;$n<$ni;$n++){
		for(my $i=0;$i<=8;$i++){
			shift_in(\@_seg_d, @all_digits[$i], 1, 100, 1);
			all_off();
		}#end inner for
	}#end outer for	
	all_off();
}#end bullets_lrtop
1;

Here is an example of how simple it is to use one of these LED displays using this driver.

 

#!/usr/bin/perl
use strict;
require 'MAX7219.pl';        #required driver for LED array

init7219(0,1,2);             #set the GPIO lines on the pine64 for clock, data in, and latch
turn_on(1);                  #turn on only 1 MAX7219 chip
set_scanlimit(1);            #tell it to use all 8 digits for 1 LED array
set_intensity('max');        #maximum intensity

print_sentence("pine64 rules the world", 500000);

ethical uses of nmap: scanning your home network

I routinely monitor the devices on my home network for many reasons:

  • did the kids really turn off their phones / tablets at night when I told them to?
  • has someone hacked my wifi?
  • are my routers / switches up and running?
  • are my home servers up?
  • what IP address did the device I just plugged in get from the my ISP’s router?

While there are many tools at my disposal for any of these issues, nmap is usually where I start.  While one could easily use nmap for nefarious purposes,  I focus on penetration testing networks / devices that belong to me like my home network, VPS’s that I lease, and my networking lab.  My ISP actually forbids port scanning in my terms of service, so I steer clear of doing any port scanning from home

I just plugged in my new pine64 running debian into my home network, and wanted to know the IP address so I could ssh into it.  To discover this, I did a ‘before’ scan of my network to see what was online.

nmap -T5 192.168.0.2-51

This tells nmap to scan the specified address range, and to make it quick.  Here are the results.

Starting Nmap 7.01 ( https://nmap.org ) at 2018-11-21 22:20 CST
Warning: 192.168.0.13 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.8 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.10 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.9 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.12 giving up on port because retransmission cap hit (2).
Warning: 192.168.0.2 giving up on port because retransmission cap hit (2).
Nmap scan report for 192.168.0.2
Host is up (0.0037s latency).
Not shown: 733 closed ports, 266 filtered ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.5
Host is up (0.00031s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

Nmap scan report for 192.168.0.7
Host is up (0.00018s latency).
Not shown: 991 closed ports
PORT STATE SERVICE
21/tcp open ftp
25/tcp open smtp
80/tcp open http
110/tcp open pop3
139/tcp open netbios-ssn
445/tcp open microsoft-ds
995/tcp open pop3s
3128/tcp open squid-http
3306/tcp open mysql

Nmap scan report for 192.168.0.8
Host is up (0.0039s latency).
Not shown: 694 closed ports, 304 filtered ports
PORT STATE SERVICE
49153/tcp open unknown
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.9
Host is up (0.018s latency).
Not shown: 646 closed ports, 351 filtered ports
PORT STATE SERVICE
49152/tcp open unknown
49154/tcp open unknown
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.10
Host is up (0.00027s latency).
Not shown: 790 closed ports, 203 filtered ports
PORT STATE SERVICE
21/tcp open ftp
23/tcp open telnet
80/tcp open http
515/tcp open printer
631/tcp open ipp
7443/tcp open oracleas-https
9100/tcp open jetdirect

Nmap scan report for 192.168.0.11
Host is up (0.011s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

Nmap scan report for 192.168.0.12
Host is up (0.0020s latency).
Not shown: 671 closed ports, 328 filtered ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap scan report for 192.168.0.13
Host is up (0.0028s latency).
Not shown: 569 filtered ports, 430 closed ports
PORT STATE SERVICE
62078/tcp open iphone-sync

Nmap done: 50 IP addresses (9 hosts up) scanned in 32.16 seconds

Here I can see that my LAMP server is up, a few iPhones, my printer, and a few PC’s.  Now, I turn on the pine64 and rescan.

BOOYizzAH!  A new device at 192.168.0.16 with port 22 ssh open.

Nmap scan report for 192.168.0.16
Host is up (0.00032s latency).
Not shown: 999 closed ports
PORT STATE SERVICE
22/tcp open ssh

using namp for OS detection

The OS detection feature is pretty awesome.  Here, I scan  a cisco 2940 ethernet switch.


nmap -vv -T5 -A 192.168.0.200

Below is a portion of the output of this command.  It correctly guesses a cisco device (easily ascertained by the OUI section of the MAC address), specifically a 2900 series switch running IOS 12.x.

PORT STATE SERVICE REASON VERSION
23/tcp open telnet syn-ack ttl 255 Cisco router telnetd
80/tcp open http syn-ack ttl 255 Cisco IOS http config
| http-auth:
| HTTP/1.0 401 Unauthorized
|_ Basic realm=level 15 access
| http-methods:
|_ Supported Methods: GET POST
|_http-title: Authorization Required
MAC Address: 00:14:XX:XX:XX:XX (Cisco Systems)
Device type: switch
Running: Cisco IOS 12.X
OS CPE: cpe:/o:cisco:ios:12.1
OS details: Cisco 2900-series or 3700-series switch (IOS 12.1)
TCP/IP fingerprint:
OS:SCAN(V=7.01%E=4%D=11/21%OT=23%CT=1%CU=41341%PV=Y%DS=1%DC=D%G=N%M=00141C%
OS:TM=5BF63311%P=x86_64-pc-linux-gnu)SEQ(SP=106%GCD=1%ISR=10B%TI=Z%CI=I%TS=
OS:U)OPS(O1=M5B4%O2=M578%O3=M280%O4=M218%O5=M218%O6=M109)WIN(W1=1020%W2=102
OS:0%W3=1020%W4=1020%W5=1020%W6=1020)ECN(R=Y%DF=N%T=FF%W=1020%O=M5B4%CC=N%Q
OS:=)T1(R=Y%DF=N%T=FF%S=O%A=S+%F=AS%RD=0%Q=)T2(R=Y%DF=N%T=FF%W=0%S=A%A=S%F=
OS:AR%O=%RD=0%Q=)T3(R=Y%DF=N%T=FF%W=1020%S=O%A=S+%F=AS%O=M5B4%RD=0%Q=)T4(R=
OS:Y%DF=N%T=FF%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=N%T=FF%W=0%S=A%A=S+%F=A
OS:R%O=%RD=0%Q=)T6(R=Y%DF=N%T=FF%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=N%T=F
OS:F%W=0%S=A%A=S%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=FF%IPL=38%UN=0%RIPL=G%RID=G%
OS:RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=S%T=FF%CD=S)

Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=262 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: IOS; Devices: router, switch; CPE: cpe:/o:cisco:ios

decoding ripv2 packets with Packetor

I regularly read Daniel Miessler’s blog and his unsupervised learning newsletter.  He recently mentioned a website called Packetor which is a free online packet analyzer.

I captured some ripv2 packets on my lab network using tcpdump, and filtered for destination address 224.0.0.9, which is the multicast IP address for ripv2 routers.  Ripv2 routers blast their entire routing table to this multicast address periodically (usually every 30 seconds).  I captured the packets like so:

sudo tcpdump -i eno1 dst 224.0.0.9 -# -XX | tee ripd.txt

This command captures ripv2 packets, numbers them, displays them in hex and ascii, and pipes the output to a text file.  Here is some of the tcpdump output.

 3 21:31:38.536458 IP 192.168.0.5 > rip2-routers.mcast.net: igmp v2 report rip2-routers.mcast.net
0x0000: 0100 5e00 0009 e411 5b40 6fa8 0800 46c0 ..^.....[@o...F.
0x0010: 0020 0000 4000 0102 4361 c0a8 0005 e000 ....@...Ca......
0x0020: 0009 9404 0000 1600 09f6 e000 0009 ..............
4 21:31:44.808348 IP 192.168.0.5 > rip2-routers.mcast.net: igmp v2 report rip2-routers.mcast.net
0x0000: 0100 5e00 0009 e411 5b40 6fa8 0800 46c0 ..^.....[@o...F.
0x0010: 0020 0000 4000 0102 4361 c0a8 0005 e000 ....@...Ca......
0x0020: 0009 9404 0000 1600 09f6 e000 0009 ..............
5 21:31:55.606022 IP 192.168.0.201.route > rip2-routers.mcast.net.route: RIPv2, Response, length: 64
0x0000: 0100 5e00 0009 0014 1c61 b1b4 0800 45c0 ..^......a....E.
0x0010: 005c 0000 0000 0211 1657 c0a8 00c9 e000 .\.......W......
0x0020: 0009 0208 0208 0048 ea3d 0202 0000 0002 .......H.=......
0x0030: 0000 0a0a 0a32 ffff ffff 0000 0000 0000 .....2..........
0x0040: 0001 0002 0000 0a14 1400 ffff ff00 0000 ................
0x0050: 0000 0000 0001 0002 0000 0a32 3206 ffff ...........22...
0x0060: ffff 0000 0000 0000 0002 ..........
6 21:32:21.206294 IP 192.168.0.201.route > rip2-routers.mcast.net.route: RIPv2, Response, length: 64
0x0000: 0100 5e00 0009 0014 1c61 b1b4 0800 45c0 ..^......a....E.
0x0010: 005c 0000 0000 0211 1657 c0a8 00c9 e000 .\.......W......
0x0020: 0009 0208 0208 0048 ea3d 0202 0000 0002 .......H.=......
0x0030: 0000 0a0a 0a32 ffff ffff 0000 0000 0000 .....2..........
0x0040: 0001 0002 0000 0a14 1400 ffff ff00 0000 ................
0x0050: 0000 0000 0001 0002 0000 0a32 3206 ffff ...........22...
0x0060: ffff 0000 0000 0000 0002 ..........

This is great and all, but I wanted to see like some human-readable routing table information or something. That is where packetor came in.

I pasted the hex output of a frame from the router at 192.168.0.201 and got much more detailed information about the routing table ->

I’m impressed.   Definitely going into ctrl+d.

capturing and decoding APRS packets with software defined radio

I have a commercial FCC license (general radio operators license) as a requirement for my  current position.  I also have a general class amateur  radio license, KD5UUU, but I’ve never had any equipment.  That is, until I bought a $20 rtl-sdr radio dongle.

It’s pretty amazing what you can do with it.  Recently, I’ve been  capturing and decoding automatic packet reporting system APRS packets.  AX.25 frames are broadcast on 144.39MHz that generally are used to track vehicles by sending their gps coordinates.

There are several high-quality graphical SDR applications out there, my favorite being CubicSDR.

cubicsdr aprs monitoring
CubicSDR monitoring 144.39MHz for APRS packets using RTL-SDR on a VHF Sinclair antenna mounted at 210′

CubicSDR allows for recording live traffic.  I set it up to record a wav in mono at 44.1KHz, and to only record if a signal is breaking the squelch threshold that I set.

Here is a recording of the packets I captured.   Pretty slow day.  Recording for around 10 minutes only captured 8 discernible frames.  I received transmissions from Jackson, TN all the way to Harding University in Searcy, AR (I was located in Wynne, AR).

So what’s in there?  You need to install direwolf to see.  Once you have that installed, go to the directory where the audio is saved and use atest.


atest recording.wav

Here are the decoded frames from the recording above.

You can see that most frames contain gps info.  APRS can also be used for short text messages.  Frame #8 contains one such example ‘Wht RAM 4X4 pickup’.

You can see live and historical  APRS traffic at arps.fi on a map.  It’s pretty cool to see amateur operators travel across the screen.

An example of an amateur operator’s progress along a stretch of I-40 near Memphis, TN from https://aprs.fi

pine64 unboxing

I love tinkering with single board computers.  I was an early adopter of the CHIP  (until they tanked) and later the raspberry pi family of SBC’s.

chip single board computer
the now defunct CHIP

I was looking for a cheaper alternative to the pi model 3 ($35) yet more powerful than the pi zero w.  There were several alternatives such as the orangePi, but after reading the forums, it looked like there were some serious overheating problems and poor support from the community.  I came across the pine64 and just had to get one.

I haven’t done much with it yet; just burned debian + mate desktop that a member of the community made available, but so far I am impressed.  I got the base Allwinner quad-core 64-bit ,512MB  memory model for just $15!

pine64 processor info

Somewhat larger than the pi 3, it has 10/100 ethernet, HDMI, 2x USB ports, and lots of GPIO pins.   I can’t wait to get started.

pine64 vs raspberry pi 3b
pine64 vs raspberry pi 3b

 

perl regex script for IP traffic analysis

Perl is my language of choice for just about everything: shell scripting, windows applications using Tk, Iot, raspberry pi GPIO, and sometimes even web via CGI (an anachronism, I know).  It’s just so easy to do something really powerful with little effort.

I have been using Wireshark since it was ethereal, back in the early 2000’s when I was caught up deep into the Cisco certification racket.  Lately, I prefer the granular control of tcpdump from the command line.  One of Perl’s many great strengths is of course it’s regular expression capabilities.  One night, just for fun, I wanted to analyze my TCP/IP traffic as I was casually browsing some of my usual sites.  I ran the following command and had the output piped to a text file:


sudo tcpdump -i eth2 -n -# dst 192.168.0.3 | tee dump.txt

Pretty simple.  It tells tcpdump to monitor my ethernet interface, not to resolve hostnames, number each packet, record only packets destined for my PC, and write the results in dump.txt.   You immediately see results on the screen:

tcpdump perl
tcpdump results

I wrote a very simple script off the top of my head to scan the results file, dump.txt, for IPv4 addresses, and then tell me the number of packets received from that address, the name of the organization that it originated from, and the country of origin.

The Perl regex that I came up with for finding an IP address in a text file is embarrassing simple, but very effective.

while($_ =~ /(\d+\.\d+\.\d+\.\d+)/g){
...
}#end while

Does it test for invalid addresses? No.  But after using this for some time,  it has never failed to find every IP address in the output of a tcpdump capture.   My script looks at each IP address and runs a whois command for each unique address.  I then use more regexes to find the organization and countries of origin.  below is a sample output.

This particular capture was very brief, and only contained 7 unique addresses.  This script, however, can work for hundreds or thousands of IP addresses.

I also used this script to parse my auth.log file on one of my internet-facing home servers.  I stupidly had my home server with port 22 open for ssh.  I was constantly  being hit with attempts to login to my server with well known usernames and passwords literally all day long.  (I changed the ssh port later and nearly all of this stopped!)  Most of these login attempts were from foreign countries, no doubt running a script.  I was first alerted to this problem by running

netstat -t

and seeing a lot of tcp connections to strange addresses that I was sure were unsolicited, and that I did not initiate.
Here is the script in it’s entirety.

#!/usr/bin/perl -w
use strict;
$| = 1;

=pod
parses tcpdump file for ip addresses

example for creating file:
sudo tcpdump -i eth0 > dump.txt

=cut

open FH, "dump.txt" or die $!;

#array for IP addresses
my @ips;
my @uips;            #unique IP address array
my $ipex_f = 0; #flag to test t/f ip exists in array

my $ln = 1;
while(){

while($_ =~ /(\d+\.\d+\.\d+\.\d+)/g){
push @ips, $1;
#print "$1\ton line $ln\n";

#reset each line
$ipex_f = 0;
my $dexist = 0;

#see if IP in array, if not push on unique IP array
foreach my $ip (@ips){
if($ip eq $1){
$dexist++;
}#end if
}#end foreach

if($dexist == 1){
push @uips, $1;
}#end if
}#end if

$ln++;
}#end while

my $n_ip = @uips;
print "$n_ip IP addresses found......\n-----------------------------------------\n";
foreach my $addr (@uips){
print $addr."\n";
}#end foreach

my $n=1;
foreach my $ipa (@uips){
print "\n$n--------------------------------------------\n";
my $n_occur = 0;

foreach my $n (@ips){
if($n eq $ipa){
$n_occur++;
}#end if
}#foreach

print "$ipa\t$n_occur \n";
my $whois = `whois $ipa > whois.txt`;
open WT, "whois.txt" or die $!;
while(){
if($_ =~ /Organization/i){ print $_; }
if($_ =~ /Country/i){ print $_; }
}#end while
close WT;

$n++;
}#end foreach

close FH;

Simple, but effective.  You could use this on ANY text document that contains IP addresses.

cisco 2801 & 2621XM load testing with iperf

I just wanted to see what would happen to the CPU of a few Cisco routers if I flooded them with traffic using iperf.  Specifically, I wondered if it was possible to ‘weaponize’ iperf by setting up multiple clients sending tons of traffic across a network to an iperf sink.

I set up an iperf server on a 10.3 FreeBSD machine like so:


iperf -s

freeBSD iperf server

As you can see, the client at 192.168.0.3 sent 444MB in 60 seconds.  What was really interesting to me was the CPU load on the Cisco 2621XM router that is connected to the 10.30.30.0 network……… nearly 90%!!!

From what I understand, the Cisco 2621XM router has an MPC860 processor capable of 88MIPS at 66MHz.  I would posit that is is possible to disable a router such as this with an iperf attack coming from multiple devices.  I mean, only one iperf client nearly maxed it out.

Now for the more powerful Cisco 2801.  CPU utilization hovers near 50%.

Even with multiple clients sending traffic to the iperf server, the CPU utilization never increases.  I assume this is due to the CEF (cisco express forwarding) functionality of the router, but I am not for sure.