Tuesday, February 22, 2011

Episode #135: His name is my name too

Tim takes it easy this week

This week Cory Williams writes in:

I would like to have a script that will find files with the same file name prefix but different extension. For instance, let's say within \Windows\System32 there are two files, FileName1.EXE and FileName1.DLL. One given is that it will always be an .EXE and a .DLL in combination. However, I will not know the prefix names. Is there a script that can find such files?

One of our basic rules of this site is no scripts. We have pushed the line a few times, but if we do it again the universe as we now it may implode. And yes, this blog is that important to the universe. Now back to the very important task at hand.

This task is quite simple in PowerShell. File and Directory objects both contain a BaseName property that contains the name minus the extension.

PS C:\> ls myfile.txt | select name,basename | ft -a

Name BaseName
---- --------
myfile.txt myfile


We can use this property with the Group-Object cmdlet to find files with matching basenames. The results can be piped into the Where-Object cmdlet (alias ?) to filter for groups with more than one object.

PS C:\Windows\system32> ls | group basename | ? { $_.Count -gt 1 }

Count Name Group
----- ---- -----
2 Boot {Boot, boot.sdi}
2 config {config, config.nt}
2 Dism {Dism, Dism.exe}
2 DRVSTORE {DRVSTORE, drvstore.dll}
2 ias {ias, ias.dll}
2 migwiz {migwiz, migwiz.lnk}
2 Msdtc {Msdtc, msdtc.exe}
...


If you don't want to include directories in the mix then we can filter them out before the grouping.

PS C:\Windows\system32> ls | ? { -not $_.PSIsContainer } |
group basename | ? { $_.Count -gt 1 }


Count Name Group
----- ---- -----
2 activeds {activeds.dll, activeds.tlb}
2 certmgr {certmgr.dll, certmgr.msc}
3 cliconfg {cliconfg.dll, cliconfg.exe, cliconfg.rll}
2 DELS3ci {DELS3ci.dll, DELS3ci.exe}
2 DELS3L3 {DELS3L3.DLL, DELS3L3.SMT}
2 diskcopy {diskcopy.com, diskcopy.dll}
2 dssec {dssec.dat, dssec.dll}
...


Pretty simple. Now let's see what Hal's got in store for us.

Hal takes it where he can get it:

Of course Tim's solution makes me immediately think of the Unix basename command, and ultimately how frustrating it is. Frustrating because, unlike most Unix commands, basename won't read input from stdin. You can only call basename on individual strings fed in on the command line. And even then you can only specify a single file name extension to use when reducing the string-- e.g. "basename hosts.deny .deny".

But instead of moaning about what we don't have, let's look at what we do have. sed will allow us to read in a list of file names on stdin and chop off everything after the last dot:

$ ls | sed 's/\.[^.]*//'
a2ps
a2ps-site
acpi
adjtime
alchemist
aliases
aliases
...

Now all we have to do is list the prefixes that are duplicated. A little bit of tweaking to our sed expression, some uniq action, and output redirection gets us where we need to be:

$ ls -d $(ls | sed 's/\.[^.]*/.*/' | uniq -d)
ant.conf cron.weekly issue.rpmnew prelink.conf
ant.d csh.cshrc logrotate.conf prelink.conf.d
auto.master csh.login logrotate.d rc.d
auto.misc dnsmasq.conf modprobe.conf rc.local
auto.net dnsmasq.d modprobe.d rc.news
auto.smb dovecot.conf ntp.conf rc.sysinit
cron.d dovecot.conf-dist ntp.conf-dist xinetd.conf
cron.daily dovecot.conf.rpmnew ntp.conf.rpmnew xinetd.d
cron.deny hosts.allow php.d
cron.hourly hosts.deny php.ini
cron.monthly issue.net prelink.cache

In the pipeline inside the "$(...)" construct I'm using sed to convert the output of ls from "<filename>.<ext>" to "<filename>.*". The "uniq -d" command gives me only the base <filename>.* patterns that are listed more than once. Then we use these wildcards as command-line arguments in the outer ls command, and we get the list of matching files.

You'll notice that I used "ls -d ..." here, because some of the returned values are going to match directories. If you want to filter out directories as Tim does above, then things get a lot more complicated:

$ ls -d $(find . -maxdepth 1 ! -type d | sed 's/.\/\(.*\.\)[^.]*/\1*/' | sort | uniq -d)
auto.master dovecot.conf-dist ld.so.conf prelink.conf
auto.misc dovecot.conf.rpmnew ld.so.conf.d prelink.conf.d
auto.net hosts.allow ld.so.conf.rpmnew rc.d
auto.smb hosts.deny ntp.conf rc.local
csh.cshrc issue.net ntp.conf-dist rc.news
csh.login issue.rpmnew ntp.conf.rpmnew rc.sysinit
dovecot.conf ld.so.cache prelink.cache

The find command locates the non-directory objects in the current directory. But the output of the find command gives us "./<filename>.<ext>". So we have a more complicated sed expression to convert this to "<filename>.*". Also, the output of the file command need not be in sorted order like the output of ls, so we have to "sort" before we feed the whole mess into "uniq -d".

The problem now is that our output still contains some directories. You'll notice, for example, that "rc.*" matches not only rc.local, rc.news, and rc.sysinit, but also rc.d, which is a directory. So we actually have to post-process the output and get rid of directories:

$ for file in $(ls -d $(find . -maxdepth 1 ! -type d | sed 's/.\/\(.*\.\)[^.]*/\1*/' | 
sort | uniq -d)); do
[ -d $file ] || echo $file;
done

...
prelink.cache
prelink.conf
rc.local
rc.news
rc.sysinit

Man, that's pretty fugly! But I just don't see a clean way to do this. Let me know if you can think of a better one.

When all you have is a Haemer...

It was nice to hear from "friend of the blog" Jeff Haemer again. He sent us this alternative approach to our problem:

$ for f in *.*; do echo ${f%.*}.*; done | uniq -d
...
prelink.cache prelink.conf prelink.conf.d
rc.d rc.local rc.news rc.sysinit
xinetd.conf xinetd.d

Jeff's basically bypassing external tools here and using the shell built-ins to do almost everything. The outer loop matches any file with a dot in the name, but inside the loop is where the magic happens.

The construct "${var%pattern}" strips off anything matching "pattern" from the right-hand side of "var". In this case, Jeff is getting rid of the final dot and any extension from the file name. So, for example, "xinetd.conf" and "xinetd.x" are both reduced to "xinetd".

Then Jeff simply does "echo <result>.*"-- e.g., "echo xinetd.*", continuing with the previous example. In the case of files that have the same prefix, that will yield multiple lines of duplicate output, which Jeff post-processes the loop for with "uniq -d".

The only difficulty is that Jeff's solution matches directories as well as files. You'll have to do some post-processing to filter out the directories if that's important to you. However, you'll need to be careful with lines like our "xinetd" example. Once you get rid of "xinetd.d", you'll be left with just "xinetd.conf", which will also need to be filtered out because there are no other files in the directory with this prefix.

Anyway, thanks for writing in again, Jeff! Always good to hear from you.

Tuesday, February 15, 2011

Episode #134: Never Out of Sorts

Hal takes it easy

Just a quick one this week, since I'm currently hopping around the world. I was recently working a case where we had extracted a bunch of date-stamped messages from unallocated space, and we wanted to output them in reverse-chronological order. Unfortunately, the date stamps were in "MM/DD/YYYY" format, which is not really conducive for easy sorting (before you start hating on the US date format standard, let me point out that DD/MM/YYYY isn't any easier to deal with for this sort of thing).

Now many of you are probably aware that the "sort" command allows you to specify the field(s) to sort on with "-k". But did you know that you were allowed to use multiple "-k" options?

$ sort -nr -t/ -k3,3 -k1,2 messages
01/05/2011 12 keyboards drumming
01/04/2011 11 admins smiling
01/03/2011 10 systems thrashing
01/02/2011 9 networks crashing
01/01/2011 8 hosts a-pinging
12/31/2010 7 Windows versions
12/30/2010 6 (billion) Linux distros
12/29/2010 5 Windows loops!
12/28/2010 4 authors coding
12/27/2010 3 shells hacked
12/26/2010 2 types of hosts
12/25/2010 Plus command line hist-or-y!

The "-t" option specifies my field delimiter-- the "/" between the pieces of the date. Then I sort first on the 3rd field ("-k3,3", aka the year) and break ties with fields 1 and 2 ("-k1,2", the month and day, respectively). "-nr" gives me a reversed numeric sort, so I get my messages in reverse chronological order like I wanted.

Oh dear. I hope this one won't be too difficult for Tim. Do you think I should dare him to come up with a CMD.EXE version?

Tim is easy

This isn't too bad for PowerShell. All we need to do is convert the date to an object, sort, and output.

PS C:\> Get-Content messages.txt |
Select-Object @{Name="Date";Expression={Get-Date($_.substring(0,10))}},@{Name="Line";Expression={$_}} |
Sort-Object date -Descending | Select-Object Line


Line
----
01/05/2011 12 keyboards drumming
01/04/2011 11 admins smiling
01/03/2011 10 systems thrashing
01/02/2011 9 networks crashing
01/01/2011 8 hosts a-pinging
12/31/2010 7 Windows versions
12/30/2010 6 (billion) Linux distros
12/29/2010 5 Windows loops!
12/28/2010 4 authors coding
12/27/2010 3 shells hacked
12/26/2010 2 types of hosts
12/25/2010 Plus command line hist-or-y!


We start by reading the file messages.txt. The output is piped into, and objectified by, Select-Object. The first 10 characters are used to create a Date Object via Get-Date. We also create an object (Line) represting the full line. The results are then sorted based on our newly created Date Object. Finally, the Line object is output to show our results.

If we really wanted to PowerShell this command, we would convert the entire output to an object. In addition, we can shorten the command by using aliases and shortened parameter names.

PS C:\> gc messages.txt | select @{n="Date";e={Get-Date($_.substring(0,10))}},
@{n="Text";e={$_.substring(11)}} | sort date -desc


Date Text
---- ----
1/5/2011 12:00:00 AM 12 keyboards drumming
1/4/2011 12:00:00 AM 11 admins smiling
1/3/2011 12:00:00 AM 10 systems thrashing
1/2/2011 12:00:00 AM 9 networks crashing
1/1/2011 12:00:00 AM 8 hosts a-pinging
12/31/2010 12:00:00 AM 7 Windows versions
12/30/2010 12:00:00 AM 6 (billion) Linux distros
12/29/2010 12:00:00 AM 5 Windows loops!
12/28/2010 12:00:00 AM 4 authors coding
12/27/2010 12:00:00 AM 3 shells hacked
12/26/2010 12:00:00 AM 2 types of hosts
12/25/2010 12:00:00 AM Plus command line hist-or-y!


CMD.EXE

So Hal decides to take it easy this week, but he wants me to put in the extra shift and do CMD.EXE as well! And he knows I can't back down from a dare, so here its.

Hal's file contains tabs and we need to use the tab character as one of the delimiters in our for loop. But when you hit the tab key at the command prompt it tries to use tab completion. We need to turn this feature off, so we have to start a new shell and tell it to F Off (literally).

C:\> cmd /F:off


Not only can we insult our shell, but we also turn off tab completion so we can use the tab character in our commands. Without this command it isn't possible to type the following command. Obviously, substitute <tab> with a press of the tab key.

C:\> (for /F "tokens=1-3,* delims=/<tab>" %a in (messages.txt) do
@echo %c/%a/%b<tab>%d) | sort /R

2011/01/05 12 keyboards drumming
2011/01/04 11 admins smiling
2011/01/03 10 systems thrashing
2011/01/02 9 networks crashing
2011/01/01 8 hosts a-pinging
2010/12/31 7 Windows versions
2010/12/30 6 (billion) Linux distros
2010/12/29 5 Windows loops!
2010/12/28 4 authors coding
2010/12/27 3 shells hacked
2010/12/26 2 types of hosts
2010/12/25 Plus command line hist-or-y!


The For loop parses our text file using the forward slash and tab as delimiters. We then rewrite the date into a sortable format. You might say that is cheating, but I say that is the right way to write dates. The first token, %a, represents the month, %b the day, %c the year, and %d is the remainder of the line. We then rewrite the line by putting the year first. The entire For loop and output is wrapped in parenthesis before being piped to sort. Parenthesis must be used or each line will be sent, one at a time, to the sort command. If the sort command recieves only one line at a time it [effectivly] has nothing to sort.

That gives us the "fixed" output, but we can put the date back to "normal":

C:\> for /F "usebackq tokens=1-3,* delims=/<tab><space>" %m in
(`^(for /F "tokens=1-3,* delims=/<tab>" %a in ^(messages.txt^) do
@echo %c/%a/%b<tab>%d^) ^| sort /R`) do
@echo %n/%o/%m<tab>%p

01/05/2011 12 keyboards drumming
01/04/2011 11 admins smiling
01/03/2011 10 systems thrashing
01/02/2011 9 networks crashing
01/01/2011 8 hosts a-pinging
12/31/2010 7 Windows versions
12/30/2010 6 (billion) Linux distros
12/29/2010 5 Windows loops!
12/28/2010 4 authors coding
12/27/2010 3 shells hacked
12/26/2010 2 types of hosts
12/25/2010 Plus command line hist-or-y!


There you go Hal, you got your CMD.EXE. And don't forget to /F:off.

Edit: Ed Skoudis just cheered that last statement

Tuesday, February 8, 2011

Episode #133: Name's the Same?

Hal's on mailroom duty

This week's challenge comes to us courtesy of Brian Finn:

We are building a new external DNS server for our network, and I was trying to figure out a way to make sure I had the new one giving out the same answers as the old one. I was going to throw a bunch of host names at both servers and make sure they handed out the same IP addresses. I figured out how to do it with dig on Linux:

dig -f hostnames.txt +noall +answer @dnsserver1 > output1
dig -f hostnames.txt +noall +answer @dnsserver2 > output2
diff output1 output2

Is there a one-liner that can do this? Can it even be done in Windows?

I'll leave the Windows answer to my esteemed colleague, but the one-liner answer should be familiar to regular readers of the blog:

diff <(dig -f hostnames.txt +noall +answer @dnsserver1) \
<(dig -f hostnames.txt +noall +answer @dnsserver2)

The solution is to use bash's "<(...)" syntax to substitute the command output in place of the file name arguments in the diff command line.

I did want to compliment Brian on his excellent dig fu here. Normally I use the "host" command when doing DNS queries from the command-line because the output is so much easier to deal with. But in this case because we want to query an entire list of hostnames, the "-f" option to dig comes in really handy. To simplify the dig output, Brian first sets the "+noall" option which would normally suppress all output from the command. But he follows that up with "+answer", which means to only output the answers returned by the remote name server. So you get nice clean output like this:

$ dig -f hostnames.txt +noall +answer @127.0.0.1
www.deer-run.com. 3600 IN CNAME newwinkle.deer-run.com.
newwinkle.deer-run.com. 3600 IN A 67.18.149.10
test.deer-run.com. 3600 IN A 192.168.168.168
$ dig -f hostnames.txt +noall +answer @67.18.149.10
www.deer-run.com. 14400 IN CNAME newwinkle.deer-run.com.
newwinkle.deer-run.com. 14400 IN A 67.18.149.10
test.deer-run.com. 14400 IN CNAME newwinkle.deer-run.com.
newwinkle.deer-run.com. 14400 IN A 67.18.149.10

You can see from the above output that I've set up a test case using a fake DNS server on my local box and the master server for my domain. The records for www.deer-run.com are the same, but test.deer-run.com differs between the two servers. So what's going to happen when I try my one-liner here?

$ diff <(dig -f hostnames.txt +noall +answer @127.0.0.1) \
<(dig -f hostnames.txt +noall +answer @67.18.149.10)

1,3c1,4
< www.deer-run.com. 3600 IN CNAME newwinkle.deer-run.com.
< newwinkle.deer-run.com. 3600 IN A 67.18.149.10
< test.deer-run.com. 3600 IN A 192.168.168.168
---
> www.deer-run.com. 14400 IN CNAME newwinkle.deer-run.com.
> newwinkle.deer-run.com. 14400 IN A 67.18.149.10
> test.deer-run.com. 14400 IN CNAME newwinkle.deer-run.com.
> newwinkle.deer-run.com. 14400 IN A 67.18.149.10

Why is diff reporting that all of the output is different between the two commands? Because if you look carefully at the second column of output you'll see that the time-to-live values returned by the two servers are different. That's because I set the default TTL value differently in the two DNS installations.

So the question is, is this a significant difference that deserves to be reported on? Some folks might think that it was, and so the output above is correct. However, if you don't care about differing TTL values, then you'll probably want to do something like this:

$ diff <(dig -f hostnames.txt +noall +answer @127.0.0.1 | awk '{print $1,$4,$5}') \
<(dig -f hostnames.txt +noall +answer @67.18.149.10 | awk '{print $1,$4,$5}')

3c3,4
< test.deer-run.com. A 192.168.168.168
---
> test.deer-run.com. CNAME newwinkle.deer-run.com.
> newwinkle.deer-run.com. A 67.18.149.10

Kind of long for a "one-liner", but it gets the job done.

Well Brian basically did my work for me this week. Let's see how Tim's handling things on his solo mission.

Tim celebrates a Packers win!

I'm a big Packers fan and I'm excited they won on Sunday. Congratulations to the Pack and Go Pack Go! But now that football is over, we'll get back to some Windows fu.

Unfortunately, there isn't a nice PowerShell cmdlet to get the results from DNS. We have to use the old nslookup command, which doesn't give us nice output objects. In addition, nslookup sends some of its messages to stderr, so we'll strip that from the output by sending it to the $null garbage can. Also, we'll set the timeout value to make sure we don't miss any lookups and mess up our output. Here is the command and its output.

PS C:\> nslookup -timeout=20 www.commandlinekungfu.com 8.8.8.8 2>$null
Server: google-public-dns-a.google.com
Address: 8.8.8.8

Name: www.commandlinekungfu.com
Address: 209.20.73.195


Our problem is the results show the name and IP address of name server we are querying. Since we don't have a built-in cmdlet to just give us answers, we will have to strip the first three lines from the output. We can do just that by using the range operator to only access the lines of output we want.

PS C:\> (nslookup -timeout=20 www.commandlinekungfu.com 8.8.8.8 2>$null )[3..99]
Name: www.commandlinekungfu.com
Address: 209.20.73.195


This command returns the 3rd through 99th line of output. Of course we don't have 99 lines of output, but it makes sure that we get all the output. The output of each command can be fed into the Compare-Object cmdlet to see if the DNS servers are returning different results.

PS C:\> Compare-Object `
(nslookup -timeout=20 www.commandlinekungfu.com 8.8.8.8 2>$null )[3..99]
(nslookup -timeout=20 www.commandlinekungfu.com 8.8.4.4 2>$null )[3..99]


<No Output>


No output means everything matches. Great!

What does it look like if the output is different?

PS C:\> Compare-Object `
(nslookup -timeout=20 www.commandlinekungfu.com 8.8.8.8 2>$null )[3..99]
(nslookup -timeout=20 www.commandlinekungfu.com 127.0.0.1 2>$null )[3..99]


InputObject SideIndicator
----------- ------------
Address: 209.20.73.195 <=
Address: 111.22.33.222 =>


That works for manually checking each hostname, but what if we have a text file containing all the DNS names we want to check? Our first problem is the output above does not show the name we are looking up so we would end up with a list of IP addresses without the context of the hostname. We have to change the output so the name and address is on one line.

PS C:\> [string]::Join(" ", (nslookup -timeout=20 www.commandlinekungfu.com 8.8.8.8 2>$null )[3..99])
Name: www.commandlinekungfu.com Address: 209.20.73.195


This command removes each line break and replaces it with a space. We then read the file and and check each hostname inside the ForEach-Object cmdlet (alias %).

PS C:\> Get-Content hostnames.txt | % { Compare-Object
[string]::Join(" ", (nslookup -timeout=20 $_ 8.8.8.8 2>$null )[3..99])
[string]::Join(" ", (nslookup -timeout=20 $_ 127.0.0.1 2>$null )[3..99])
}


InputObject SideIndicator
----------- ------------
Name: www.commandlinekungfu.com Address: 209.20.73.195 <=
Name: www.commandlinekungfu.com Address: 111.22.33.222 =>


While definitely not as simple and elegant as Hal's solution, it does work.

By the way, there are a number of plug-ins that emmulate dig and give us pretty objects, my favorite is over here.

Monday, January 31, 2011

Episode #132: Enigma

Tim goes stealth-mode:

We are always looking for new topic ideas from you readers, but this week we only received one email and trying to use it was difficult.

Hey there,

I've been searching for kung fu teachers, and I was excited to find you.

It's too hard to find trustworthy, quality service providers, and <dumb site> is changing that. We're growing really fast, we need more kung fu teachers, and I think you're a perfect fit!

Posting on <dumb site> is a great way to advertise yourself and it's completely free for service providers like you.

All you need to do to post your information is visit: <dumb site's URL>

Thanks!

~Heather


Apparently Heather didn't know that we don't do physical kung fu. We do try to kick bit booty, but we leave any physical effort to people in another line of work, like accountants. Oh, and the site name was redacted because I don't want to give them any business.

Since that didn't give us any great ideas, I came up with one on my own using some really secure encryption.

One method of encryption involves the use of On-time Pads. When used correctly it is impossible to crack. "If the key is truly random, as large as or greater than the plaintext, never reused in whole or part, and kept secret, the ciphertext will be impossible to decrypt or break without knowing the key." That is different from taking billions of years to crack AES, this is uncrackable!

Let's use this technique to send a message to Hal:
I secretly teach Kung Fu

Hal and I meet up, and exchange one-time pads. I generated the pad by flipping a coin, where 1 is head and 0 is tails. The 1s and 0s were converted to bytes to create our encryption key.

PS C:\> $key = [byte[]](0x70,0xB3,0xDE,0xC0,0xDE,0xDF,0xAC,0xE1,0x7B,
0xA5,0x41,0x51,0x33,0x7E,0xD5,0xC1,0x11,0xDF,0x00,0x15,0xDE,0xAD,0xD0,0x0D)


With this clear text...

PS C:\> $cleartext = "I secretly teach Kung Fu"


...we do a bit of encrypting

PS C:\> 0..($cleartext.length -1) | % {
$ciphertext += ($key[$_] -bxor [int]$cleartext[$_]).ToString("x2") }

PS C:\> $ciphertext
3993ada5bdadc99517dc6125561fb6a93194757bb98d9678


This command counts from 0 to 24, the length of the clear text minus 1 (remember, base 0). The current pipeline object ($_) represents the counter and is used as the index in the array to grab the respective bytes from the key and clear text. The -bxor operator does a binary XOR. The result is then converted to a two character Hex string (x2), then is appended it to our cipher text. I would then send Hal this message:



Dear Hal,

Don't tell Heather, but 3993ada5bdadc99517dc6125561fb6a93194757bb98d9678


The cool thing is that the same process can be used to decrypt.

PS C:\> $cipherbytes = [byte[]](0x39,0x93,0xad,0xa5,0xbd,0xad,0xc9,0x95,0x17,
0xdc,0x61,0x25,0x56,0x1f,0xb6,0xa9,0x31,0x94,0x75,0x7b,0xb9,0x8d,0x96,0x78)

PS C:\> 0..($cipherbytes.length -1) | % {
$cipherbytes += ($key[$_] -bxor $cipherbytes[$_]).ToString("x2") }

PS C:\> $cipherbytes
I secretly teach Kung Fu


We can even use PowerShell to convert a hex string to bytes:

PS C:\> "3993ada5bdadc99517dc6125561fb6a93194757bb98d9678" | 
Select-String ".." -AllMatches | % { $_.Matches } | % { [byte]("0x" + $_.Value) }



This command will match on each two characters (..), take each match object, take each byte of the match and convert it to a byte.

That isn't really pretty, but we'll see how Hal will send the message, and if Bash is leet.

Hal goes into suck mode

I've got to be honest. This is a challenge where bash just isn't very conducive to solving the problem. But since a "real" scripting language like Perl or Python is disallowed by the rules of our blog, I'll just have to muddle through somehow.

Before we get to the suckage, let's start by assigning the bytes of our one-time pad to an array, just like Tim did:

$ otp=(0x70 0xB3 0xDE 0xC0 0xDE 0xDF 0xAC 0xE1 0x7B 0xA5 0x41 0x51 
0x33 0x7E 0xD5 0xC1 0x11 0xDF 0x00 0x15 0xDE 0xAD 0xD0 0x0D)

The syntax we're using here "var=(val1 val2 ...)" is a convenient way for assigning a list of values to the elements of an array. We'll use this again in just a moment.

The hard part is when we want to start XOR-ing these bytes with the bytes in our string. The bash XOR operator ("^") wants both operators to be a numeric type. If I tried to do something like "I ^ 0x70", bash treats the "I" as an invalid number and therefore zero-- that's not what we want at all! So what I need to do is somehow convert the ASCII bytes in our input string into their numeric equivalents.

Well guess what? There's no bash built-in function for doing this! So you're left with doing a nasty hack like this:

$ echo -n I secretly teach Kung Fu | xxd -p
49207365637265746c79207465616368204b756e67204675

As you can see, I'm using the hexdumping program xxd as my ASCII-to-hex converter. The "-p" option means "plain mode": just output the hex bytes and nothing else. Of course, the problem here is that the bytes are all run together. But a little sed will fix that:

$ echo -n I secretly teach Kung Fu | xxd -p | sed 's/\(..\)/0x\1 /g'
0x49 0x20 0x73 0x65 0x63 0x72 0x65 0x74 0x6c 0x79 0x20 0x74 0x65 0x61 0x63...

In my sed expression, I'm snapping up two characters at a time and outputting a "0x", then the two characters, then a space.

With a little command substitution, I can now assign these hex bytes to another array:

$ bytes=( $(echo -n I secretly teach Kung Fu | xxd -p | sed 's/\(..\)/0x\1 /g') )

I'm really just doing an array assigment here-- "bytes=( ... )"-- similar to the one we used to set up our one-time pad. But in this case, the values I'm assigning are the output of our shell pipeline.

Now that I've got two arrays of bytes, I can XOR them together with a for loop:

$ for ((i=0; $i < ${#bytes[*]}; i++)); do 
printf "%x" $((${bytes[$i]} ^ ${otp[$i]}));
done

3993ada5bdadc99517dc6125561fb6a93194757bb98d9678$

The expression "${#bytes[*]}" evaluates to the number of elements in the array. Inside the loop, I'm using printf to output the results of my XOR operation as hex digits ("%x"). The only downside, as you can see, is that there's no final trailing newline at the end of the output-- the "$" you see there is the shell prompt for the next line! This is maybe not such a big deal, because most likely I'd want to do something like this:

$ secret=$( for ... )

That would assign the output of the loop to a variable, $secret.

But now what about going the other way and converting the contents of $secret back into the original ASCII? This is actually pretty similar to the steps above with a couple of small mods:

$ bytes=( $(echo $secret | sed 's/\(..\)/0x\1 /g') )
$ for ((i=0; $i < ${#bytes[*]}; i++)); do
printf "%x" $((${bytes[$i]} ^ ${otp[$i]}));
done | xxd -r -p

I secretly teach Kung Fu$

Here I'm resetting the value of our bytes array to be the individual bytes from $secret, converted by sed to "0xNN" format. Then I run through the same for loop to XOR these bytes with our one-time pad. However this time the output goes into "xxd -r -p", which "reverts" the resulting string of bytes into their corresponding ASCII values.

All I can say is, thank the shell gods for xxd!

Tuesday, January 25, 2011

Episode #131: Subject of Attachment

Because Hal sed So:

We got an interesting challenge in the mailbag this week from Ryan Tuthill:

Given the example logs:

562C938C3.A9069|attachment = Interview Results.xls
B1CE33BED.A58D0|subj="Who?" | subj="Who?"
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
19B4D27D2.A8CE7|subj="FW: Two New Bud Light Commercial's" | attachment = Bud_Lite_Pick_Up_Line.mpg


What I am curious to discover is a way to print only lines that contain subj and attachment, not just one or the other. From there, I would like to print the lines with the same subj and attachment titles.


Looking at the problem, I thought to myself, "Hmmm, irregular pattern matching. Seems like a job for sed." Then I started wondering if I could solve the problem entirely with sed. We haven't spent much time on the blog talking about sed, but you must understand that it's a fully capable programming language in its own right. For proof of that assertion, I refer you to sedtris-- yep, that's a Tetris game written entirely in sed.

Anyway, it turns out there is a sed-only solution to Ryan's problem. Assuming we've got our sample input in a file called "input", simply do this:

$ sed 'h; s/.*subj="\(.*\)".*attachment = \1$/\1/; t hit; d; :hit g' input
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc

I know the syntax is terse and mysterious here, so let's take this slowly. The first thing you need to understand is that sed has two different data spaces you can work with. The one you normally use is the "pattern space", which is the line you just read in. When you do an operation like "s/.../.../", you're operating on the pattern space. However, sed also has a "hold space" where you can store stuff for later. And there are operators that let you copy or append data into the hold space from the pattern space and vice-versa.

With that in mind, let's walk through our sed "program". The first command we give sed is "h", which means "copy the pattern space into the hold space, overwriting the previous value in the hold space". We're using this to store the original version of the line we just read in, so that we can print it out later if it matches our criteria.

Once we've saved a copy of the original line, we can start mangling the copy in the pattern space. The next sed operation we perform is a substitution. The regular expression on the lefthand side matches 'subj="<string>"' anywhere on the line, followed by 'attachment = <string>' at the end of the line. sed is one of the few Unix regular expression syntaxes that supports matching a string early in the regular expression and then testing for the matched string later in the same regex. If our pattern matches, then we end up replacing the entire original line with just the value of the <string> we matched.

This is where it starts to get tricky. It turns out that the replacement we do on the RHS of the "s/.../.../" expression doesn't matter. I'm really only using the substitution to verify that we have a line that matches our criteria. This, in turn, allows me to use sed's branching operator to control whether or not we output the original line. You see sed has a goto-like operator "t <label>" which will jump to the specified <label> if and only if there has been a successful "s/.../.../" operation since the current line was read in (or since the last "t" operation if there's been more than one).

So if our substitution was successful, then this is a line with matching "subj" and "attachment" titles and we want to print out the line. In this case our "t" operation tells sed to jump to the label "hit" and start executing statements from there. If the substitution didn't work, then we just fall through to the next statement after the "t".

Thus when we don't match what happens is the sed operator "d", which simply means "discard the current pattern space and move on to the next line". Think of it like "continue" or "next" in many popular programming languages. The next thing in our sed program is the label ":hit", which is where we'll jump to if the "s/.../.../" operator worked. The sed command we invoke here is "g", which overwrites the current pattern space with the contents of the hold space. Remember how we saved the original line into the hold space back at the beginning of our program? Well now we bring that back. And finally, there's an implicit "print the contents of the pattern space" at the end of every sed block which takes care of actually outputting the line for us.

Pretty neat, huh? I was feeling really good about myself until I got a follow-up email from Ryan with a few more sample log entries:

BE1342109.A1C1F|attachment = ATT49396.txt | subj="FW: snow"
21E3430E7.A8583|attachment = Scan001.pdf | subj="FW: PROJECT/TASK"
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"

Yep, it turns out that the "subj" and "attachment" can appear in either order. Well, back to the drawing board:

$ sed 'h; 
s/.*subj="\([^"]*\)".*attachment = \1$/\1/; t hit;
s/.*attachment = \(.*[^ ]\) *|.*subj="\1"$/\1/; t hit; d;
:hit g' input

67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"

The new code isn't actually all that different from the original solution-- I've just split things onto multiple lines for greater readability. We have the "h" operator, a substitution that checks for 'subj="<string>"' followed by 'attachment = <string>', and the "t" operator just as before. However, this time if we fail to get a hit, then we try looking for 'attachment = <string>' followed by 'subj="<string>"' instead. Only if both of those operations fail, do we give up and do "d". If either operation succeeds, then we jump to ":hit" and do the operations to print out the line.

The pattern match for the 'attachment = <string>' is a little tricky in the second case because there are no quotes around the attachment name and the attachment name can contain spaces. So we have to explicitly match "'attachment<space>=<space>", followed by "some stuff that ends in a non-space character", followed by some spaces, and then a pipe character ("|") which is our field separator. Yeesh!

Anyway, this has been your first high-speed introduction to the kinky joy that is sed. Tim, I bet you don't have anything this dirty in your Powershell arsenal!

Tim feeds his expressions prunes, so they stay regular:

PowerShell may not have sed, but it does have built-in regular expressions. These expression are way cooler than their incontinent brethren of cmd.exe.

Similar to Hal's approach, we first read the file and look for strings containing a subject. This is accomplished by piping Get-Content (alias gc) into Select-String where we filter using a regular expression.

PS C:\> gc log.txt | Select-String 'subj="(?<Subject>[^"]+)
B1CE33BED.A58D0|subj="Who?" | subj="Who?"
67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
19B4D27D2.A8CE7|subj="FW: Two New Bud Light Commercial's" | attachment = Bud_Lite_Pick_Up_Line.mpg
...


This regular expression matches on lines contain something like the following:
subj="<text that doesn't contain a double quote>

The content of the text that doesn't contain double quotes is part of a named group and contains the subject we would like to use later. The syntax for using a named group is:

(?<GroupName>regex)


Until I prepped for this episode, I didn't realize that the Select-String cmdlet will populate the $matches variable; I thought that was only done via the -Match operator. This little trick makes our command much shorter and easier to read than using the -Match operator.

As regular readers (badabing!) might remember, the $matches variable contains the groups matched by our regular expression. Since we used a named group, it makes it much easier to access the group we want by simply using $matches.GroupName. This variable can be used further down the pipeline, and we can use this little trick to get our final command.

PS C:\> cat .\log.txt | Select-String 'subj="(?<Subject>[^"]+)' | 
Select-String "attachment = $($matches.subject)"


67BD53BED.AF311|subj="January 14.docx.doc" | attachment = January 14.docx.doc
8657C30E7.AC7A5|attachment = IMG00005.jpg | subj="IMG00005.jpg"


The results from our first command are piped into Select-String which filters for strings containing "attachment = <subject name> and that gives us the output we are looking for.

Tuesday, January 18, 2011

Episode #130: Eenie, Meanie, Miney, Mo...

Hal gets selective

Recently I was doing an audit of USB devices that had been connected to numerous Windows machines on a network. The input data I had looked like this:

KeyStore XP2G
080909524d94e5
Sat Jun 30 22:42:07 2009
Apple iPod
000A270010C4E86E
Fri Jan 16 23:24:15 2009
M-Sys Dell Memory Key
086086412140E1C2
Fri Jan 16 23:15:30 2009
OLYMPUS u810/S810
000J55024022
Wed Jan 14 19:03:58 2009
...

The input file was a collection of three line "records" for each device. The first line was the "friendly name" of the device, the second line was the device serial number, and the third line was the date the device was last connected. I had one input file per machine.

What I needed to do was to extract just the serial numbers from each input file. Since the serial numbers of USB devices can have widely varying formats, I couldn't easily write a regular expression to match them. Instead I needed to extract the lines by line number-- the 2nd, 5th, 8th lines and so on. awk is actually really useful for this:

$ awk '!((FNR - 2) % 3)' *
080909524d94e5
000A270010C4E86E
086086412140E1C2
000J55024022
...

Recall from Davide Brini's solution a few weeks ago, that FNR is the "record number" in the current file. When using awk's default record separator which is newline, that means that FNR corresponds to the line number in the current file. So I'm subtracting two from the line number and then doing a "modulo 3" operation-- so the expression will be zero on lines 2, 5, 8, 11, ... Therefore "not" that expression, aka "!(...)", will evaluate to true on those lines only. Since there's no command block after the expression "{print}" is assumed, and I output the lines I want.

Now the reason I was pulling out the serial numbers is that I wanted to see which devices had been connected to multiple systems. The easy way to do this is to just slap on a little sort and uniq action:

$ awk '!((FNR - 2) % 3)' * | sort | uniq -d
080909524d94e5
086086412140E1C2
...

But just to save Davide the trouble of sending me an email, here's the "awk-only" version:

$ awk '!((FNR - 2) % 3) && (++a[$1] == 2)' * 
086086412140E1C2
080909524d94e5
...

By adding another clause after a logical "and" ("&&") I ensure that the second clause only gets executed on the lines containing serial numbers. In the second clause I'm creating values in an array that is indexed by the serial number. The values are a count of the number of times we've seen each serial number-- "++a[$1]" adds one to the value of a[$1] before evaluating the conditional. After we've incremented our accumulator value, we check to see if the value is 2, meaning that this is the second time we've encountered a given serial number. If that's true then our implicit "{print}" happens again and we output the serial number. I don't care if the serial number appears in more than two different files, just that it appears in at least two.

Of course the output of the "sort | uniq -d" version is in a different order than the awk-only version because of the "sort" in the first solution. But since the serial number format varies widely anyway, I'm not sure sorting is that useful in any case. You could always pipe the awk output into sort if sorting is important to you.

I'm pretty sure Tim can knock this one out of the park using Powershell. I wonder what a CMD.EXE solution would look like..?

Tim is selective too

Hal likes to taunt me with digs at CMD.EXE, and this week I'll have to concede. I got about 90% of the way through writing the big ol' command and decided it was getting ridiculous. If you want to know what it would have looked like go check out Ed's final episode. The command would have worked but it would be completely impractical and no one would have used it, in short, a circus act. Instead, let's do something practical. On to PowerShell...

The PowerShell cmdlet used to read a file is Get-Content (aliases cat, gc, and type). But we don't just need to read a file, we need to read a file and get every third line. Here's how to do just that:

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] }
080909524d94e5
000A270010C4E86E
086086412140E1C2
000J55024022
...


Get-Content's ReadCount parameter is used to "specifies how many lines of content are sent through the pipeline at a time." The default value is 1, so normally each line is sent down one at a time. Setting this value to 3 means that 3 lines at a time will be sent down the pipeline.

Inside the ForEach-Object's scriptblock, the current pipeline object ($_) contains the three lines passed down the pipeline. In this group of three lines we are looking for the second item in the array. The second item in the array is represented by the array index of 1. Remember, the first item in an array is 0, so the second is 1, and third is 2.

Now that we have the serial numbers, let's look for duplicates. This is really easy with the Group-Object cmdlet.

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] } | Group-Object -NoElement

Count Name
----- ----
1 080909524d94e5
2 000A270010C4E86E
2 086086412140E1C2
...


The NoElement switch means that the original objects are not stored, so we have just the count serial number.

In addition, we can filter for counts greater than 1.

PS C:\> Get-Content -Path * -ReadCount 3 | % { $_[1] } |
Group-Object -NoElement | Where-Object { $_.Count -gt 1 }


Count Name
----- ----
2 000A270010C4E86E
2 086086412140E1C2
...


But that is a long command, and I like to be brief, so this is the short method:

PS C:\> gc * -r 3 | % { $_[1] } | group -n | ? { $_.Count -gt 1 }


So we did what Hal did, now let's up the ante and turn the contents of these files into objects.

PS C:\> gc * -r 3 | select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},@{Name="Date";Expression={$_[2]}}


Name Serial Date
---- ------ ----
KeyStore XP2G 080909524d94e5 Tue Jun 30 22:42:07 2009
Apple iPod 000A270010C4E86E Fri Jan 16 23:24:15 2009
...


The Select-Object cmdlet uses hashtables to allow you to manually create properties. Hashtables use key-value pairs. To create a property the Name is [obviously] used to set the name of the property. The Expression is used to set the value of the property. So we have objetified everything, but the date is still a string. To convert it to a Date object we can use .NET to convert the string to a native Date Object.

PS C:\> gc * -r 3 | select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},
@{Name="Date";Expression={[datetime]::ParseExact($_[2], "ddd MMM dd HH:mm:ss yyyy", $null)}}


Name Serial Date
---- ------ ----
KeyStore XP2G 080909524d94e5 6/30/2009 10:42:07 PM
Apple iPod 000A270010C4E86E 1/16/2009 11:24:15 PM
M-Sys Dell Memory Key 086086412140E1C2 1/16/2009 11:15:30 PM
...


Now we can do whatever we want with it, such as exporting the results to a CSV to use in Excel, filter based on the properties, or look for duplicates as we did above.

One thing that might be useful is knowing into which computers the USB devices were plugged. Assuming the file name is descriptive we can use it to add an additional property to our objects.

PS C:\> ls | % { $file = $_.Name; gc $_ -r 3 |
select @{Name="Name"; Expression={$_[0]}},
@{Name="Serial";Expression={$_[1]}},
@{Name="Date";Expression={[datetime]::ParseExact($_[2], "ddd MMM dd HH:mm:ss yyyy", $null)}},
@{Name="File";Expression={$file}}}


Name Serial Date File
---- ------ ---- ----
KeyStore XP2G 080909524d94e5 6/30/2009 10:42:07 PM Alpha.txt
Apple iPod 000A270010C4E86E 1/16/2009 12:20:00 PM Alpha.txt
...
Apple iPod 000A270010C4E86E 1/21/2009 11:24:15 PM Bravo.txt
M-Sys Dell Memory Key 086086412140E1C2 1/16/2009 11:15:30 PM Bravo.txt
...


This command starts with Get-ChildItem (alias ls) to get each file in the directory. The files are piped into ForEach-Object where the filename is stored in a variable for use later. The rest of the command is very similar as our original command, the only difference is the input to Get-Content is specified by the current pipeline object, instead of our wildcard (*) as before.

Now we can do all sorts of filtering.

PS C:\> ... | ? { $_.Serial -eq "000A270010C4E86E" }
Name Serial Date File
---- ------ ---- ----
Apple iPod 000A270010C4E86E 1/16/2009 12:20:00 PM Alpha.txt
Apple iPod 000A270010C4E86E 1/21/2009 11:24:15 PM Bravo.txt
...


Put that in your pipeline and smoke it, Hal!

Tuesday, January 11, 2011

Episode #129: Writing on the Wall

Tim wants to be heard:

Hal and I realized that in the previous episode we brought up a new topic but never explained it. I broke out the "msg" command, and he used "wall." In case you couldn't figure it out, these commands are used to send a message to users on the system.

The Windows command Msg is used to send a message to one or more users based on username, sessionname, or sessionid. The username is the most common way of directing a message to a user.

C:\> msg hal You have no chance to survive, make your time!


This command simply sends a message to Hal. As mentioned above, a message can also be directed based on the session name or id. To determine the session id or name refer to episode 62.

We can also send a message to all users on the system by using the asterisk.

C:\> msg * Someone set up us the bomb!


What if we don't want to send the message to all the users, but more than one user? We can do that! It does require that we have a file containing a list of usernames to whom we would like to direct our message.

C:\> msg @mostlyeveryone.txt Someone set up us the bomb!


We can also send the messages to users on other systems by using the /SERVER switch.

C:\> msg * /SERVER:otherbox All your base are belong to us!


However, this command doesn't just send messages, but also can be used to get an acknowledgment. The /V option displays information about which actions have been performed, such as sending a message and acknowledgments. The /W option waits for a response from the users. Say we send a message to Hal, and want to make sure he gets its, this is how we would do it:

C:\> msg hal /V /W Did you make your time?
Sending message to session Console, display time 60
Message to session Console responded to by user


The first message lets us know that a message was sent to Hal. The second means that either Hal responded, or the 60 second timer elapsed. Its a bit weird that the message is the same either way, but welcome to the wonderful world of Windows commands.

If we don't think that 60 seconds is long enough for Hal to respond, we can use the /TIME option to explicitly specify the duration of the message.

C:\> msg hal /V /W /TIME:3600 Did you make your time?


This command will wait one hour for a response; more than enough time for Hal to "make his time!"

By the way, these lost-in-translation quotes are taken from the internet meme All Your Base Are Belong To Us!. As a side note, I tried to use these quote in an earlier episode, but Hal corrected my grammar and fixed it. I can't believe Hal doesn't have endless hours to waste watching silly videos on the internet. Is that what the internet is for?

Hal is a little hard of hearing:

I guess I'm just having trouble keeping up with you kids and all your Internet shenanigans. But all of this writing to people's terminals is making me fondly remember my days using dumb terminals to talk with my friends on time-sharing systems. You can't type faster than 9600 baud, so who the heck needs 10Gbps? And get off my lawn!

If I wanted to tell Tim to get off my lawn, I could do that with the write command:

$ write tim
write: tim is logged in more than once; writing to pts/2
Get off my lawn!
^D

Specify the user you want to send a message to as an argument. As you can see, Tim is logged in on multiple PTYs, so the write command will by default send the message to the lowest numbered device. But you can specify a specific device to write to as an optional argument: "write tim pts/2", for example.

Once you hit return on the command line, anything you type in subsequent lines is sent to the specified user (each line normally gets sent immediately when you hit Enter). When you're done entering text, just hit <Ctrl>-D. Tim ends up seeing a message like this:

Message from hal@caribou on pts/1 at 16:43 ...
Get off my lawn!
EOF


Now random grumpy old men shouting into your terminal windows can be distracting when you're trying to get work done. So Tim has the option of blocking messages on his PTY with "mesg n":

$ mesg
is y
$ ls -l /dev/pts/2
crw--w---- 1 tim tty 136, 2 2011-01-10 16:50 /dev/pts/2
$ mesg n
$ ls -l /dev/pts/2
crw------- 1 tim tty 136, 2 2011-01-10 16:50 /dev/pts/2

With no arguments, the mesg command tells you what the current status of your PTY is-- the default is to be accepting messages, or "y". As you can see in the output above, running "mesg n" simply removes the group-writable flag from your PTY. The write command is set-GID to group "tty" so that it can write messages to users terminals when "mesg y" is set.

But in our last Episode, I used the wall command to send New Year's greetings to everybody on the system. In its simplest form, wall accepts input on the standard in and blasts it to all currently connected users:

$ echo Get off my lawn! | wall

And the users see:

Broadcast Message from hal@caribou                                             
(/dev/pts/1) at 16:58 ...

Get off my lawn!

If you are the superuser, wall can also be used to write messages stored in a text file.

# wall /etc/shutdown-message

The other advantage to running wall as the superuser is that your message will even go to the terminals of those users who have set "mesg n". After all, "mesg n" works by changing permissions on the PTY devices, but root can write to any file regardless of permissions.

Now that I've educated you whipper-snappers on this outmoded technology, it's time for my nap. Why don't you kids go shoot some marbles or play with your hula-hoops?