Tuesday, October 30, 2007

Write once, debug everywhere

I basically never program in Java. A while back I was forced to write a JNI wrapper for polymail, but just recently I got to write a complete, if little, Java program.

You can read about the program here.

It worked great in Sun's phone emulator, it worked great on my Motorola RAZR that's running Sun's JVM. And then someone else tried it out. And it failed. It failed in a weird way... the text labels on UI elements were invisible. The program itself ran, but there's no text.

This is on a Motorola Q with Windows Mobile 5.0 and the IBM WebSphere Everywhere Micro Environment. That environment is supposedly compatible with the CLDC 1.0/MIDP 2.0 that I built the JAR for. That's the same environment running on my RAZR.

So much for write once, run everywhere. 12 years, let me just repeat that: 12 years, after the introduction of this technology I'm reduced to downloading IBM's entire environment just to be able to debug an application because it doesn't work.

And people wonder why I shy away from Java. Every time I touch it, it bites me.

Monday, October 29, 2007

A Java client implementation of Steve Gibson's PPP

I recently produced an open source implementation of Steve Gibson's Perfect Paper Passwords system in C. It occurred to me that a better implementation would be a Java client for my mobile phone (thus eliminating the need for printing and carrying the paper passwords).

Here's my PPP client implementation running on my Motorola RAZR. It's written in Java using CLDC 1.0 and MIDP 2.0.




You can download and install the JAR file. The current version is 1.0.0.

Thursday, October 18, 2007

Times Square: a fun spammer GIF

Nick FitzGerald reported a neat spammer image trick to me the other day. It's entered in The Spammers' Compendium that involves using animation to display the word Viagra emulating a flashing neon sign.

Since many OCR systems merge the layers together before OCR this image is actually in the 'wrong' order. Once merged the letters are in the order VIRAAG.

Monday, October 15, 2007

SOC Update and Google Maps integration

After receiving some feedback on my Simple code for entering latitudes and longitudes I've made a couple of changes:

1. Replace the letter V with the symbol @ in the alphabet to remove confusion between U and V. Implementations should automatically map V to U if entered.

2. Changed the checksum to the following calculation:


C = p0 + p1 * 2 + p2 * 3 + p3 * 4 + p4 * 5 + p5 * 6 + p6 * 7 + p7 * 8 + p8 * 9 mod 29


To make it a bit easier to visualize here's an integration of SOC with Google Maps. You can either type in an address to navigate to that address and see the SOC, or type in a SOC to navigate to that location.





Enter address to find:

Or, enter a SOC:

Friday, October 12, 2007

An open source implementation of Steve Gibson's PPP algorithm

Steve Gibson has come up with a simple two-factor password scheme that relies on printed cards of passcodes generated using a combination of SHA-384 and Rijndael. The idea is that a system could prompt the user for one of the passcodes in addition to their normal password.

Steve calls this his Perfect Paper Passwords system and has given a detailed description of the algorithm.

As usual he's released code written in assembly language as a DLL for Windows. He hasn't released his source code (he never does), so I thought it would be interesting to write my own implementation of his algorithm. Here's the C code:

#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "rijndael.h"
#include "sha2.h"

#pragma pack(1)

typedef unsigned char Byte;

typedef union __Passcode {
unsigned long as_long;
struct {
Byte byte[4];
} bytes;
} Passcode;

typedef struct __PasscodeString {
char character[5];
} PasscodeString;

typedef unsigned long long SixtyFour;

typedef union __OneTwoEight {
struct {
SixtyFour low;
SixtyFour high;
} sixtyfour;
Byte byte[16];
} OneTwoEight;

typedef struct __SequenceKey {
Byte byte[SHA384_DIGEST_SIZE];
} SequenceKey;

typedef unsigned long DWord;

const char * alphabet = "23456789!@#%+=:?abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ";

void inc( OneTwoEight * i )
{
++i->sixtyfour.low;
if ( i->sixtyfour.low == 0 ) {
++i->sixtyfour.high;
}
}

void add( OneTwoEight * to, OneTwoEight * addend )
{
SixtyFour low = to->sixtyfour.low;

low += addend->sixtyfour.low;

if ( ( low < to->sixtyfour.low ) || ( low < addend->sixtyfour.low ) ) {
++to->sixtyfour.high;
}

to->sixtyfour.low = low;
}

void ConvertPasscodeToString( PasscodeString * passcodeString,
Passcode passcodeValue )
{
Byte bytes[4];

bytes[0] = passcodeValue.bytes.byte[0] & 0x3f;
bytes[1] = ( ( passcodeValue.bytes.byte[0] & 0xc0 ) >> 6 ) +
( ( passcodeValue.bytes.byte[1] & 0x0f ) << 2 );
bytes[2] = ( ( passcodeValue.bytes.byte[1] & 0xf0 ) >> 4 ) +
( ( passcodeValue.bytes.byte[2] & 0x03 ) << 4 );
bytes[3] = ( ( passcodeValue.bytes.byte[2] & 0xfc ) >> 2 );

int i;
for ( i = 0; i < 4; ++i ) {
passcodeString->character[i] = alphabet[bytes[i]];
}

passcodeString->character[4] = '\0';
}

void RetrievePasscodes( Passcode passcodeListBuffer[],
OneTwoEight firstPasscodeNumber,
int passcodeCount,
SequenceKey * sequenceKey )
{
int i;

#define KEY_BITS (int)256
Byte key[KEYLENGTH(KEY_BITS)];

for ( i = 0; i < KEYLENGTH(KEY_BITS); ++i ) {
key[i] = sequenceKey->byte[i+16];
}

unsigned long rk[RKLENGTH(KEY_BITS)];
OneTwoEight plain;

for ( i = 0; i < 16; ++i ) {
plain.byte[i] = sequenceKey->byte[i];
}

OneTwoEight block = firstPasscodeNumber;
unsigned int skip = (unsigned int)(block.sixtyfour.low & 0xF);

SixtyFour carry = block.sixtyfour.high & 0xF;
block.sixtyfour.high >>= 4;
block.sixtyfour.low >>= 4;
block.sixtyfour.low |= (carry << 60);

OneTwoEight temp = block;
add( &block, &temp );
add( &block, &temp );
add( &plain, &block );

int nrounds = rijndaelSetupEncrypt( rk, key, KEY_BITS );
Byte cipher[16*3];

int c = 0;

while ( passcodeCount > 0 ) {
rijndaelEncrypt( rk, nrounds, (Byte *)&plain.byte[0], &cipher[0] );
inc( &plain );
rijndaelEncrypt( rk, nrounds, (Byte *)&plain.byte[0], &cipher[16] );
inc( &plain );
rijndaelEncrypt( rk, nrounds, (Byte *)&plain.byte[0], &cipher[32] );
inc( &plain );

for ( i = skip; ( i < 16 ) && ( passcodeCount > 0 ); ++i ) {
passcodeListBuffer[c].bytes.byte[0] = cipher[i*3];
passcodeListBuffer[c].bytes.byte[1] = cipher[i*3+1];
passcodeListBuffer[c].bytes.byte[2] = cipher[i*3+2];
++c;
--passcodeCount;
}

skip = 0;
}
}

void GenerateSequenceKeyFromString( char * string,
SequenceKey * sequenceKey )
{
sha384( (const unsigned char *)string, strlen( string ),
(unsigned char *)sequenceKey );
}

void GenerateRandomSequenceKey( SequenceKey * sequenceKey ) {
struct timeval t;
gettimeofday( &t, 0 );

char t_buffer[61];
strftime( t_buffer, 60, "%c%d%e%H%I%j%m", localtime( &t.tv_sec ) );

char msecs_buffer[32];
sprintf( msecs_buffer, "%ld", t.tv_usec );

char hostname_buffer[256];
gethostname( hostname_buffer, 255 );

char pointer_buffer[16];
sprintf( pointer_buffer, "%p", sequenceKey );

char loadavg_buffer[256];
double samples[3];
getloadavg( samples, 3 );
sprintf( loadavg_buffer, "%f%f%f", samples[0], samples[1], samples[2] );

char buffer[1024];
sprintf( buffer, "%s-%s-%s-%s-%s", t_buffer, msecs_buffer, hostname_buffer,
pointer_buffer, loadavg_buffer );

GenerateSequenceKeyFromString( buffer, sequenceKey );
}

int ConvertHexToKey( char * hex, SequenceKey * key )
{
int i, j;

for ( i = 0, j = 0; i < 96; i += 2, ++j ) {
char pair[3];
sprintf( pair, "%c%c", hex[i], hex[i+1] );
int x;
sscanf( pair, "%x", &x );
key->byte[j] = (Byte)x;
}
}

int main( int argc, char * argv[] )
{
if ( argc == 1 ) {
printf( "Error: You must provide the passphrase or sequence key as the first parameter\n" );
return 1;
}

SequenceKey key;

if ( strlen( argv[1] ) == 0 ) {
printf( "Generating random sequence key\n" );
GenerateRandomSequenceKey( &key );
} else {
if ( ( strlen( argv[1] ) == 96 ) && ( ConvertHexToKey( argv[1], &key ) ) ) {
printf( "Using entered sequence key\n" );
} else {
printf( "Generating sequence key from passphrase\n" );
GenerateSequenceKeyFromString( argv[1], &key );
}
}

printf( "Sequence Key: " );
int i;
for ( i = 0; i < SHA384_DIGEST_SIZE; ++i ) {
printf( "%2.2x", key.byte[i] );
}
printf( "\n" );

if ( argc == 4 ) {
OneTwoEight firstPasscode;

// Warning! This only uses the bottom 64-bits of argv[2] and hence
// can't convert a much higher number

firstPasscode.sixtyfour.low = atoi( argv[2] );
firstPasscode.sixtyfour.high = 0;

int count = atoi( argv[3] );

Passcode * pcl = malloc( sizeof( Passcode ) * count );

RetrievePasscodes( pcl, firstPasscode, count, &key );

for ( i = 0; i < count; ++i ) {
PasscodeString str;
ConvertPasscodeToString( &str, pcl[i] );
printf( "%s ", &str.character[0] );
}

printf( "\n" );
}

return 0;
}

Now that's a little less flexible than all the options given in Steve's ppp.exe implementation, but it does compute the correct output and can easily be modified if you want your own implementation with source of PPP.

It uses this SHA-384 implementation and this Rijndael implementation.

Here's the output of my ppp program producing the first 70 passcodes:

$ ./ppp 53303f97ddcf91ed74391fc5c3661246
32427e1c93c1a2e2836d006fa2653dc1
fb94f8fbeefa5f1e9263c12878e0a95e 0 70
Using entered sequence key
Sequence Key: 53303f97ddcf91ed74391fc5c3661246
32427e1c93c1a2e2836d006fa2653dc1
fb94f8fbeefa5f1e9263c12878e0a95e
VJNV gHoF PaRp T8FS tGw2 s%iT u7rp
ZvN@ MWGb %574 ?DVF btRq PLTA DDtm
C2TP Yin8 zMF@ a8%H zHvq Uwxc qkF7
YuUk 8Ca? :ZvZ T9:? wki+ KiHq d?9b
GY%5 !igR [email protected] [email protected] eyVm 5PwY CAVs
oKzK 43Mc nR%? [email protected] oZUs Tbec xn6B
9bVA UvJt DfAX =Gqp 7Abj M:6Y ENRs
aXX= Eokx WjTj %MPV McSA GFTK XMdY
49?Z Z?Hk G+A? zoK5 :Z8N z8NU WpM!
=AB% RrSq %7:Y %=P8 RKXr di#5 4T3L


Feel free to take my code and use it under the BSD license.

Thursday, October 11, 2007

More spammer crossword creativity

Nick FitzGerald writes in with a variant of the "1 across, 3 down" spammer content trick which looks like this:



The neat thing is that the crossword is created using HTML in a way that prevents a simple HTML-stripping spam filter from reading the brand names. To a simple spam filter this looks like:

CA
BREIT
OM
R
O
L
E
TIER
ING
G
X

The actual HTML (cleaned up by Nick) is:

<TABLE>
<TR>
<TD>
<DIV align=right>
CA<BR>
<BR>
BREIT<BR>
OM
</DIV>
</TD>
<TD>
<DIV align=center>
R<BR>
O<BR>
L<BR>
E
</DIV>
</TD>
<TD>
TIER<BR>
<BR>
ING<BR>
GA
</TD>
</TR>
<TR>
<TD>
<DIV align=center>
X
</DIV>
</TD>
</TR>
</TABLE>