BTRom, a Linux Trojan Eraser

BTRom removes Trojans that were installed into the Linux kernel. It tries to do so by examining the kernel's system call table and looking for anomalies.

This tool works with the Linux 2.0 and 2.2 kernels.

The program first attempts to detect if you are using a BIG_KERNEL (a bzImage) or not (a zImage). One of the differences is the address of the kernel in memory. BIG_KERNEL starts at 0xc0000000 while the other starts at 0x00100000.

The system call table (sct) has the entries of all the system calls. If you modify the sct, the new entry must be `out of range'. BTRom will try to fix these `out of range' (`out of range' is an entry that has a value out of the start_of_the_kernel and the_start_of_the_kernel + some_value) system calls with their original values. The original values are stored in the System.map, which is used like an anti-virus signature file.

Quick install:

  compile:
1) edit config.h and Makefile. Modify it if you want.
      $ vi config.h
      $ vi Makefile

2) make
      $ make

  install:
1) become root
      $ su -

2) install the module mbtrom
      # insmod mbtrom

3) run btrom
      # ./btrom _nr_mbtrom_ [options]

4) uninstall the module mbtrom
      # rmmod mbtrom

NOTE: This program does not uninstall Trojan modules. It is only able to disables the Trojans, so that you can uninstall it with 'rmmod'.

<++> linenoise/btrom/Makefile
#
# Makefile del b t r o m
#


## BUG. This must be the same as the one in config.h
SYSTEM_MAP = "/usr/src/linux/System.map"

AWK = awk
CC = gcc
#CFLAGS = -DSYSTEM_MAP=$(SYSTEM_MAP)

all: parse btrom mbtrom

parse:
$(AWK) -f sys_null.awk $(SYSTEM_MAP) > sys_null.h

btrom: btrom.o
$(CC) btrom.c -O2 -Wall -o btrom

mbtrom:
$(CC) -c -O3 -Wall -fomit-frame-pointer mbtrom.c

clean:
rm -f mbtrom.o btrom.o btrom sys_null.h
<-->
<++> linenoise/btrom/btrom.c
/*
* btrom - Borra Trojanos Modulo
* por Riq
* 1/Dic/98: 0.3 - Compatible con kernel 2.2 y soporta BIG_KERNEL
* 25/Nov/98: 0.2 - Version inicial. Soporta kervel 2.0 i386
*/
#include <stdio.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fnmatch.h>
#include <strings.h>
#include <linux/sys.h>

#include "config.h"
#include "sys_null.h"

FILE *sm;
FILE *au;
int quiet;
int borrar;
int dif_n_s;
unsigned int big_kernel;

/***********************************************************************
System.map
************************************************************************/
int sm_b_x_nom( unsigned int *address, char *estoy )
{
char buffer[200];
char sys_add[20];

fseek(sm,0L,SEEK_SET);
while( fgets(buffer,200,sm) ) {
if( fnmatch(estoy,buffer,0)==0 ) {
strncpy(sys_add,buffer,8);
sys_add[8]=0;
*address = strtoul(sys_add,(char **)NULL,16);
return 1;
}
}
return 0;
}

int sm_busca_x_nombre( unsigned int *address, char *estoy)
{
char nombre[50];

sprintf(nombre,"*T sys_%s\n",estoy);
return sm_b_x_nom(address, nombre);
}

FILE* sm_open()
{
return fopen( SYSTEM_MAP, "r" );
}

/***********************************************************************
asm/unistd.h
************************************************************************/
void au_dame_el_nombre( char *dst, char *orig )
{
int i,j;

j=i=0;
while( orig[i]!='_' )
i++;
i=i+5;
while( orig[i]!=' ' && orig[i]!='\t' )
dst[j++]=orig[i++];
dst[j]=0;
}

int au_b_x_num( char *nombre, int numero )
{
char buffer[200];
char buscar[50];

/* FIXME: ?sera mas efectivo regexec() que fnmatch()? */
sprintf(buscar,AU_PREFIX"%i*",numero);
while( fgets(buffer,200,au) ) {
if( fnmatch(buscar,buffer,0)==0 ) {
au_dame_el_nombre(nombre,buffer);
return 1;
}
}
/* No encontre... entonces una segunda pasada */
fseek(au,0L,SEEK_SET);
while( fgets(buffer,200,au) ) {
if( fnmatch(buscar,buffer,0)==0 ) {
au_dame_el_nombre(nombre,buffer);
return 1;
}
}
return 0;
}

int au_busca_x_numero(char *nombre, int numero)
{
return au_b_x_num(nombre,numero);
}

FILE* au_open()
{
return fopen( ASM_UNISTD, "r" );
}

/*****************************************/
/* Comun a la primer y segunda recorrida */
/*****************************************/
int comun_1er_2da( int j, int i , char *nombre , char *c, int clean, unsigned int retval)
{
int a;
a = clean; /* bug fix */
nombre[0]=0;

/* i!=0 porque el asm/unistd del kernel 2.2 no viene */
if( i!=0 && au && au_busca_x_numero(nombre,i)) {
if( retval > big_kernel + LIMITE_SYSCALL ) {
*c = '*' ;
clean++;
} else
*c = ' ';
} else {
if( retval > big_kernel+LIMITE_SYSCALL )
*c = '!';
else
*c = '?';
clean++;
}
if(i==j) { /* modulo btrom */
*c='-';
clean=a;
} else if(retval==SYS_NULL || retval==0) {/* Null pointer */
*c='N';
clean=a;
}
return clean;
}
/**********************************************************************
primer_recorrida: Detectar troyanos
**********************************************************************/
int primer_recorrida(int j)
{
char nombre[50];
int address;
int i,old_clean,clean;
unsigned int retval;
char c;

old_clean=clean=0;
printf( "\n1st part: Detect trojans\n"
" [ ]=OK [N]=Null [-]=btrom\n"
" [?] Mmm...syscall\n"
" Address [*][!]=trojan routine\n"
" now System.map Num [ ] Syscall Name\n"
"----------------------------------------------\n");

for( i=0; i< NR_syscalls; i++ ){
__asm__ volatile (
"int $0x80":"=a" (retval):"0"(j),
"b"((long) (i)),
"c"((long) (0)),
"d"((long) (0)));

clean = comun_1er_2da(j,i,nombre,&c,clean,retval);
if( !quiet || clean > old_clean ) {
if( nombre[0]!=0 ) {
if( sm && sm_busca_x_nombre(&address,nombre)) {
if(retval!=address && retval < big_kernel + LIMITE_SYSCALL) {
dif_n_s++;
printf("%8x!%8x %3i [%c] %s\n",retval,address,i,c,nombre);
} else printf("%8x %8x %3i [%c] %s\n",retval,address,i,c,nombre);
} else printf("%8x %3i [%c] %s\n",retval,i,c,nombre);
} else printf("%8x %3i [%c]\n",retval,i,c);
old_clean = clean;
}
}
return clean;
}

/**********************************************************************
segunda_recorrida: Limpiar troyanos
**********************************************************************/
int segunda_recorrida(int j)
{
char nombre[50],dire[50];
int address;
int i,old_clean,clean,retval,key;
char c;
unsigned int k;


old_clean=clean=0;
printf( "\n2nd part: Clean Trojans\n"
" s = System.map address\n"
" c = clean address\n"
" m = manual address\n"
" i = ignore\n"
" now System.map Num [ ] Syscall Name\n"
"---------------------------------------\n");

for( i=0; i< NR_syscalls ; i++ ){
__asm__ volatile (
"int $0x80":"=a" (retval):"0"(j),
"b"((long) (i)),
"c"((long) (0)),
"d"((long) (0)));

clean = comun_1er_2da(j,i,nombre,&c,clean,retval);
if( clean > old_clean ) {
if( nombre[0]!=0 ) {
if( sm && sm_busca_x_nombre(&address,nombre)) {
if(retval!=address && retval < big_kernel + LIMITE_SYSCALL) {
dif_n_s++;
printf("%8x!%8x %3i [%c] %s <s/c/m/I>?",retval,address,i,c,nombre);
} else printf("%8x %8x %3i [%c] %s <s/c/m/I>?",retval,address,i,c,nombre);
} else printf("%8x %3i [%c] %s <c/m/I> ?",retval,i,c,nombre);
} else printf("%8x %3i [%c] <c/m/I> ?",retval,i,c);
old_clean = clean;

fseek(stdin,0L,SEEK_END);
key=fgetc(stdin);
switch(key) {
case 's':
k = address;
break;
case 'c':
k = SYS_NULL;
break;
case 'm':
printf("Enter an hexa address (ex: 001a1b):");
fseek(stdin,0L,SEEK_END);
fgets( dire,50,stdin );
k = strtoul(dire,(char **)NULL,16);
break;
default:
k=1;
break;
}
/* FIXME: 1 no se puede poner como address */
if(k!=1)
__asm__ volatile (
"int $0x80":"=a" (retval):"0"(j),
"b"((long) (i)),
"c"((long) (1)),
"d"((long) (k)));
}
}
return clean;
}

void help()
{
printf( "\nUsage: btrom nr_of_mbtrom [-c][-v]\n"
"\t1) Install the module mbtrom with`insmod mbtrom'\n"
"\t2) The module must return a value.If not see the README->bugs\n"
"\t btrom value_returned_by_mbtrom [-c][-v]\n"
"\t `v' is verbose. Recommended\n"
"\t `c' is clean. Cleans the trojans\n"
"\t3) Uninstall the module mbtrom with 'rmmod mbtrom'\n"
"\n"
"\tExamples:\n"
"\t btrom 215 -cv\n"
"\t btrom 214 -v\n"
"\t btrom 215\n"
"\nWarning: Dont put random numbers. Be careful with that!"
"\nRecommended: Do `btrom _number_ -v' before a cleaning\n\n"
);
exit(-1);
}

void chequear_argumentos( char *parametros )
{
int i,j;
i=strlen(parametros);

if(parametros[0]!='-') help();

for(j=1;j<i;j++) {
switch(parametros[j]) {
case 'c':
borrar = 1;
break;
case 'v':
quiet = 0;
break;
default:
help();
}
}
}

int main(int argc, char **argv, char **envp )
{
unsigned int retval;
int clean;
int i;

printf( "\n\n"
"b t r o m b y r i q\n"
"v"VERSION"\n");

if(argc <2 || argc >3 ) help();

quiet = 1; borrar = 0 ;
if( argc==3) chequear_argumentos(argv[2]);

au = au_open();
sm = sm_open();
if(!au && !quiet)
printf("Error while opening `asm/unistd.h' in `"ASM_UNISTD"'\n");
if(!sm && !quiet)
printf("Error while opening `System.map' in `"SYSTEM_MAP"'\n");

dif_n_s=0;


/* __NR_mbtrom number */
i = atoi( argv[1] );
if(!i)
help();

/* Chequeo si es BIG_KERNEL o no */
__asm__ volatile (
"int $0x80":"=a" (retval):"0"(i),
"b"((long) (0)),
"c"((long) (2)),
"d"((long) (0)));

big_kernel =(retval>BIG_KERNEL?BIG_KERNEL:SMALL_KERNEL);

/* Primer recorrida */
clean = primer_recorrida( i );

/* Mensaje del senior btrom */
printf( "\nb t r o m s a y s:\n");
if(dif_n_s>0) {
printf( "Your System.map seems to have a problem.\n");
if(dif_n_s<SYSMAP_LIMIT)
printf( "Wait. Perhaps this is not a System.map problem,\n"
"but something related with the new functions names.\n"
);
else
printf( "Are you sure that you have a valid System.map ?\n");
if(clean)
printf( "Oh no! The problem is the trojan that you have ;-)\n");
}


if(!clean) {
printf( "You system call table seems to be clean.\n");
if(quiet)
printf("If you want to be more sure use the `-v' option\n");
} else {
printf( "\nWhat do you want to do with the trojan?\n"
"What about cleaning it with `btrom _numero_ -c'?\n" );
}


/* Ah borrar los troyanos se ha dicho */
if(borrar && clean) {
if(au)
fseek(au,0L,SEEK_SET);
if(sm)
fseek(sm,0L,SEEK_SET);

segunda_recorrida( i );
}


if(au)
fclose(au);
if(sm)
fclose(sm);

return 0;
}
<-->
<++> linenoise/btrom/config.h
/*
config.h
usado por btrom.c y mbtrom.c
*/


/*
Modificar segun los gustos
*/

/* Numero que uno supone que esta vacio en la sys_call_table */
#define NUMERO_VACIO 215

/* Path al archivo System.map */
/* Si Ud. nunca compilo el kernel tal vez sea /boot/System.map */
/* FIXME: Usar el define del Makefile para no definir esto en 2 partes */
#ifndef SYSTEM_MAP
#define SYSTEM_MAP "/usr/src/linux/System.map"
#endif

/* Hay problemas con old y new. Gralmente no es problema de la System.map */
#define SYSMAP_LIMIT 8


/* Path al archivo asm/unistd.h */
#define ASM_UNISTD "/usr/include/asm/unistd.h"

/* Prefijo a buscar en asm/unistd.h*/
#define AU_PREFIX "#define*__NR_*"

/* Hasta donde llega el kernel space */
/* FIXME: No se cual es el limite realmente. Igual con esto anda :-) */
#define LIMITE_SYSCALL 0x00300000

/*
No modificar
*/
/* Version del btrom */
#define VERSION "0.3"

/* BIG_KERNEL y SMALL_KERNEL*/
#define BIG_KERNEL 0xc0000000
#define SMALL_KERNEL 0x00100000
<-->
<++> linenoise/btrom/mbtrom.c
/*
* modulo del btrom - Borra Trojanos Modulo
* 25/11/98 - por Riq
*
* compile with:
* gcc -c -O3 -fomit-frame-pointer mbtrom.c
*
*/
#define MODULE
#define __KERNEL__

#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#else
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif

#include <syscall.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/malloc.h>
#include <linux/dirent.h>
#include <linux/sys.h>
#include <linux/linkage.h>
#include <asm/segment.h>

#include "config.h"
#include "sys_null.h"

extern void *sys_call_table[];

int __NR_mbtrom;

int* funcion( int numero, int modo, unsigned int *address )
{
switch(modo){
case 0:
return sys_call_table[numero];
break;
case 2:
return (void *)&sys_call_table;
case 1:
default:
sys_call_table[numero]=address;
break;
}
return (void *)0;
}

int init_module(void)
{
__NR_mbtrom = NUMERO_VACIO ;

/* Chequea direccion vacia desde NUMERO_VACIO hasta 0 */
while ( __NR_mbtrom!= 0 &&
sys_call_table[__NR_mbtrom] != 0 &&
sys_call_table[__NR_mbtrom] != (void *)SYS_NULL )
__NR_mbtrom--;
if(!__NR_mbtrom ) { /* Si es 0 me voy */
printk("mbtrom: Oh no\n");
return 1;
}

sys_call_table[__NR_mbtrom] = (void *) funcion;


if( __NR_mbtrom != NUMERO_VACIO )
printk("mbtrom: Mmm...\n");
printk("mbtrom: -> %i <-\n",__NR_mbtrom);
return 0;
}

void cleanup_module(void)
{
sys_call_table[__NR_mbtrom] = 0;
printk("mbtrom: Bye.\n");
}
<-->
<++> linenoise/btrom/sys_null.awk
/sys_ni_syscall/ { print "#define SYS_NULL 0x"$1 }
<-->

The tool has been published in: Phrack Magazine 54, Article 3
  &"nbsp;"