Skip to content

Commit

Permalink
Version 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Tylous committed May 6, 2021
1 parent 79a9b95 commit a32838d
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 216 deletions.
40 changes: 19 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

Publically published Sep 4, 2018

Vibe is a tool designed to preform post-ex lateral movement techniques while remaining undetected by network detection tools including Threat Hunting appliances.
Vibe works by pulling down all information about a domain, allowing users to perform the same domain net commands offline. Vibe also enumerates additional information that is not typically shown in these queries.
Vibe also provides the ability to scan systems to see what shares are available and what privileges the account used, has access to. Vibe also provides the ability to enumerate user’s currently logged into systems, as well as, who has been logged in, while remaining undetected.

Vibe is a tool designed to perform post-ex lateral movement techniques while remaining undetected by network detection tools including Threat Hunting appliances. Vibe works by pulling down all information about a domain, allowing users to perform the same domain net commands offline. Vibe also enumerates additional information that is not typically shown in these queries. Vibe also provides the ability to scan systems to see what shares available and what privileges the account, has access to. Vibe also provides the ability to enumerate users currently logged into systems, as well as who has been logged in, while remaining undetected.




## Installation


Vibe was developed with Python version 2.7
Vibe was developed with Python version <strike>2.7</strike> 3.0

Tested and supported on Kali Linux and Ubuntu.

Expand All @@ -27,8 +26,8 @@ Vibe uses the following external dependencies:

To install run following commands:
```
sudo apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev
pip install -r requirements.txt
sudo apt-get install libsasl2-dev python3-dev libldap2-dev libssl-dev
pip3 install -r requirements.txt
```

Expand All @@ -37,9 +36,8 @@ pip install -r requirements.txt


```
./vibe.py -h
usage: main [-h] -U username -P password -D domain -I IP [-o] [-r] [-p PORT]
[-u]
~/Vibe# python3 ./vibe.py -h
usage: main [-h] -U username [-P password] -D domain -I IP [-o] [-r] [-p PORT] [-u]
optional arguments:
-h, --help show this help message and exit
Expand All @@ -53,9 +51,7 @@ optional arguments:
-o, --offline Offline Mode
-r, --remove Remove Database
-p PORT, --port PORT Specify a specific port to connect on (default is 636)
-u, --unencrypted Specify a specific for unencrypted mode (if LDAPS is
not available)
-u, --unencrypted Specify a specific for unencrypted mode (if LDAPS is not available)
```

Expand All @@ -78,13 +74,14 @@ root@kali:~/# ./vibe.py -U admin -P Password! -D STARLABS.local -I 172.16.144.18
(@Tyl0us)
[+] Credentials Valid, Generating Database
[+] Table 1/4 : Generating Group Table
[+] Table 2/4 : Generating User Table
[+] Table 3/4 : Generating Computer Table
[+] Table 4/4 : Generating Password Policy Table
[+] Sucessful Database Created
0.434292078018
[+] Credentials valid, generating database
[+] Table 1/5 : Generating User Table
[+] Table 2/5 : Generating Group Table
[+] Table 3/5 : Generating Computer Table
[+] Table 4/5 : Generating Password Policy Table
[+] Table 5/5 : Generating SPN Table
[+] Database successfully created
[*] 0.11863517761230469
>>help
Commands
========
Expand All @@ -99,7 +96,7 @@ search Searches for a key word(s) through every field of every tab
share_hunter Scans target(s) enumerating the shares on the target(s) and the level of access the specified user, using -d/--domain, -u/--user, -p/--password. Can take a list or range of hosts, using -t/--target and --jitter/-j to add a delay
show Shows the contents of Users, Computers, Credentials, Groups, Password policy, Store, Credentials, Files Servers and Access tables
store Displays the contents of a specific table. Example: 'show [table name] (access, creds, computers, file servers, pwdpolicy, users)
export Export the contents of the database to a path in one of the following formats: CSV, HTML. (using with -f or --filetype)
export Export the contents of the database to a path in one of the following formats: csv, html. (using with -f or --filetype and -p or --path for the file path)
exit Exit Vibe
>>
Expand Down Expand Up @@ -176,7 +173,8 @@ There are several different options that can be used with the ```show``` command
* file servers
* groups
* pwdpolicy
* store
* store
* spn
* users

Below are some examples of the information stored:
Expand Down
89 changes: 51 additions & 38 deletions lib/AD.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def ldap_query(self,l, base_dn, subtree, objectFilter, attrs):

def ADtime(self, time):
global date
time = time.replace( "'", "" )
seconds = int(time) / 10000000
if seconds == 0:
epoch = 0
Expand Down Expand Up @@ -81,13 +82,13 @@ def main(self, IP, lusername, domain, password, unencrypted, port):

try:
con.simple_bind_s(lusername + '@' + domain, password)
print colors.GRN + "[+] "+ colors.NRM + "Credentials valid, generating database"
print(colors.GRN + "[+] "+ colors.NRM + "Credentials valid, generating database")
except ldap.INVALID_CREDENTIALS:
print colors.RD + "[-] "+ colors.NRM + "Username or password is incorrect."
print(colors.RD + "[-] "+ colors.NRM + "Username or password is incorrect.")
os.remove ( ".db/" + domain + ".db")
sys.exit()
except ldap.SERVER_DOWN:
print colors.RD + "[-] "+ colors.NRM + "Domain Controller either down or unreachable"
print(colors.RD + "[-] "+ colors.NRM + "Domain Controller either down or unreachable")
os.remove(".db/" + domain + ".db")
sys.exit()
dictionary=['distinguishedName', 'sAMAccountName']
Expand All @@ -96,8 +97,9 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
group_attributes = ['distinguishedName', 'sAMAccountName', 'description', 'memberOf', 'member', 'objectSid']
computer_attributes = ['description', 'memberOf', 'operatingSystem', 'operatingSystemVersion']
FGPP_attributes = ['msDS-LockoutDuration', 'msDS-MaximumPasswordAge', 'msDS-LockoutThreshold', 'msDS-MinimumPasswordLength', 'msDS-PasswordComplexityEnabled', 'msDS-PasswordHistoryLength','msDS-PSOAppliesTo','pwdProperties']
Kerb_attributes=['servicePrincipalName', 'sAMAccountName', 'description', 'pwdLastSet', 'memberOf']
searchScope = ldap.SCOPE_SUBTREE
object = ['user', 'group', 'computer', 'msDS-PasswordSettings', 'FGPP-PasswordSettings']
object = ['user', 'group', 'computer', 'msDS-PasswordSettings', 'FGPP-PasswordSettings', 'SPN']
if unencrypted == True:
l = ldap.initialize('ldap://' + IP)
else:
Expand All @@ -112,26 +114,30 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
try:
for obj in object:
searchFilter = '(&(objectCategory=' + obj + '))'
if obj == 'SPN':
print(colors.GRN + "[+] " + colors.NRM + "Table 5/5 : Generating SPN Table")
searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))"
results = self.ldap_query(l, baseDN, searchScope, searchFilter, Kerb_attributes)
if obj == 'group':
print colors.GRN + "[+] " + colors.NRM + "Table 1/4 : Generating Group Table"
print(colors.GRN + "[+] " + colors.NRM + "Table 2/5 : Generating Group Table")
results = self.ldap_query(l, baseDN, searchScope, searchFilter, group_attributes)
memberof = []
Desc = []
username = []
dictionary = dict ( zip ( usernamelist, samnamelist ) )
dictionary = dict ( list(zip( usernamelist, samnamelist )) )
if obj == 'user':
print colors.GRN + "[+] " + colors.NRM + "Table 2/4 : Generating User Table"
print(colors.GRN + "[+] " + colors.NRM + "Table 1/5 : Generating User Table")
results = self.ldap_query ( l, baseDN, searchScope, searchFilter, user_attributes )
memberof = []
Desc = []
username = []
if obj == 'computer':
print colors.GRN + "[+] " + colors.NRM + "Table 3/4 : Generating Computer Table"
print(colors.GRN + "[+] " + colors.NRM + "Table 3/5 : Generating Computer Table")
results = self.ldap_query ( l, baseDN, searchScope, searchFilter, computer_attributes )
memberof = []
Desc = []
if obj == 'msDS-PasswordSettings':
print colors.GRN + "[+] " + colors.NRM + "Table 4/4 : Generating Password Policy Table"
print(colors.GRN + "[+] " + colors.NRM + "Table 4/5 : Generating Password Policy Table")
results = l.search_s( baseDN, ldap.SCOPE_BASE )
if obj == 'FGPP-PasswordSettings':
FGPPDN = 'CN=Password Settings Container,CN=System,' + (baseDN)
Expand All @@ -145,42 +151,42 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
results_attrs = result[1]
name = result_dn[0]
if 'sAMAccountName' in results_attrs:
sAMAccountName = str( results_attrs['sAMAccountName'] ).replace( '[', '' ).replace( ']', '' )[1:-1]
sAMAccountName = str( results_attrs['sAMAccountName'] ).replace( '[b\'', '' ).replace( '\']', '' )
else:
sAMAccountName = ""
if 'description' in results_attrs:
description = str( results_attrs['description'] ).replace( '[', '' ).replace( ']', '' )[1:-1]
description = str( results_attrs['description'] ).replace( '[b\'', '' ).replace( '\']', '' )
else:
description = ""
if 'profilePath' in results_attrs:
profilepath = str( results_attrs['profilePath'] ).replace( '[', '' ).replace( ']', '' )[
profilepath = str( results_attrs['profilePath'] ).replace( '[b\'', '' ).replace( '\']', '' )[
1:-1]
else:
profilepath = ""
if 'homeDirectory' in results_attrs:
homeDirectory = str ( results_attrs['homeDirectory'] ).replace ( '[', '' ).replace ( ']', '' )[
1:-1]
homeDirectory = str ( results_attrs['homeDirectory'] ).replace ( '[b\'', '' ).replace ( '\']', '' )
else:
homeDirectory = ""
if 'pwdLastSet' in results_attrs:
time = str( results_attrs['pwdLastSet'] ).replace( '[', '' ).replace( ']', '' )[1:-1]
time = str( results_attrs['pwdLastSet'] ).replace( '[b\'', '' ).replace( '\']', '' )
self.ADtime( time )
pwdLastSet = date
else:
pwdLastSet = ""
if 'lastLogon' in results_attrs:
time = str( results_attrs['lastLogon'] ).replace( '[', '' ).replace( ']', '' )[1:-1]
time = str( results_attrs['lastLogon'] ).replace( '[b\'', '' ).replace( '\']', '' )
self.ADtime( time )
lastLogon = date
else:
lastLogon = ""
if 'primaryGroupID' in results_attrs:
gidnum = str( results_attrs['primaryGroupID'] ).replace( '[', '' ).replace( ']', '' ).replace('\'', '' )
gidnum = str( results_attrs['primaryGroupID'] ).replace( '[b\'', '' ).replace( '\']', '\n' )
else:
pass
if 'memberOf' in results_attrs:
membersOf = []
for memberOf in results_attrs["memberOf"]:
memberOf = memberOf.decode("utf-8")
r = memberOf.split( ',CN=' )[0].replace( 'CN=', '' ).replace(',Builtin,'+baseDN+'', '' ).replace(baseDN,'').replace('OU=',' ')
r = r.split(',')
membersOf.append(r[0])
Expand All @@ -196,8 +202,9 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
del trimmed_results['description']
if 'memberOf' in trimmed_results:
del trimmed_results['memberOf']
for key, value in trimmed_results.iteritems ():
for key, value in trimmed_results.items ():
for v in value:
v = v.decode("utf-8")
t = v.split ( ',CN=' )[0].replace ( 'CN=', '' ).replace ( baseDN,' ' ).replace ( 'OU=',' ' )
t = t.split ( ',' )
if str ( t[0] ) in usernamelist:
Expand All @@ -207,6 +214,7 @@ def main(self, IP, lusername, domain, password, unencrypted, port):

else:
for member in results_attrs["member"]:
member = member.decode("utf-8")
t = member.split ( ',CN=' )[0].replace ( 'CN=', '' ).replace ( baseDN, ' ' ).replace ( 'OU=', ' ' )
t = t.split ( ',' )
mem.append ( str ( t[0] ) )
Expand Down Expand Up @@ -235,80 +243,85 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
'PASSWORD_EXPIRED': 0x00800000
}
UAC = results_attrs["userAccountControl"]
for trigger, val in uac.items():
vUAC = ''.join(UAC)
for trigger, val in list(uac.items()):
vUAC = b''.join(UAC)
if int(vUAC) & val:
userac.append(trigger)
userac = str(userac).replace('[','').replace(']','').replace(',','\n').replace(' ', '').replace('\'','')

if 'operatingSystem' in results_attrs:
OS = str(results_attrs["operatingSystem"]).replace('\']', '').replace('[\'', '')
OS = str(results_attrs["operatingSystem"]).replace('\']', '').replace('[b\'', '')
else:
OS = ""
if 'operatingSystemVersion' in results_attrs:
OSV = str( results_attrs["operatingSystemVersion"] ).replace( '\']', '' ).replace( '[\'', '' )
OSV = str( results_attrs["operatingSystemVersion"] ).replace( '\']', '' ).replace( '[b\'', '' )
else:
OSV = ""
if "msDS-MinimumPasswordLength" in results_attrs:
FGminpwd = (str ( results_attrs["msDS-MinimumPasswordLength"] ).replace ( '[\'', '' ).replace ('\']', '' ))
FGminpwd = (str ( results_attrs["msDS-MinimumPasswordLength"] ).replace ( '[b\'', '' ).replace ('\']', '' ))
else:
FGminpwd = ' '
if "msDS-LockoutThreshold" in results_attrs:
FGlcknum = (str ( results_attrs["msDS-LockoutThreshold"] ).replace ( '[\'', '' ).replace ( '\']', '' ))
FGlcknum = (str ( results_attrs["msDS-LockoutThreshold"] ).replace ( '[b\'', '' ).replace ( '\']', '' ))
else:
FGlcknum = ' '
if "msDS-PasswordHistoryLength" in results_attrs:
FGpwdhis = (str ( results_attrs["msDS-PasswordHistoryLength"] ).replace ( '[\'', '' ).replace ('\']', '' ))
FGpwdhis = (str ( results_attrs["msDS-PasswordHistoryLength"] ).replace ( '[b\'', '' ).replace ('\']', '' ))
else:
FGpwdhis = ' '
if "msDS-LockoutDuration" in results_attrs:
FGlckduration = results_attrs["msDS-LockoutDuration"]
FGlckdur = (str ((int ( str ( FGlckduration ).replace ( '[\'-', '' ).replace ( '\']', '' ) ) * 0.0000001) / 60 ))
FGlckdur = (str ((int ( str ( FGlckduration ).replace ( '[b\'-', '' ).replace ( '\']', '' ) ) * 0.0000001) / 60 ))
else:
FGlckdur = ' '
if "msDS-PasswordComplexityEnabled" in results_attrs:
FGpwdp = (str ( results_attrs["msDS-PasswordComplexityEnabled"] ).replace ( '[\'', '' ).replace ( '\']', '' ))
FGpwdp = (str ( results_attrs["msDS-PasswordComplexityEnabled"] ).replace ( '[b\'', '' ).replace ( '\']', '' ))
else:
FGpwdp = ' '
if "msDS-PSOAppliesTo" in results_attrs:
FGmember = (str ( results_attrs["msDS-PSOAppliesTo"] ).replace ( '[\'', '' ).replace ( '\']', '' ).replace (',CN=Users,DC=STARLABS,DC=local', '\n' ).replace('CN=',''))
FGmember = (str ( results_attrs["msDS-PSOAppliesTo"] ).replace ( '[b\'', '' ).replace ( '\']', '' ).replace (',CN=Users,DC=STARLABS,DC=local', '\n' ).replace('CN=',''))
else:
FGmember = ' '
if obj == 'msDS-PasswordSettings':
pwdpar = []
minpwd = str( results_attrs["minPwdLength"] ).replace( '[\'', '' ).replace( '\']', '' )
lcknum = str( results_attrs["lockoutThreshold"] ).replace( '[\'', '' ).replace( '\']', '' )
pwdhis = str( results_attrs["pwdHistoryLength"] ).replace( '[\'', '' ).replace( '\']', '' )
minpwd = str( results_attrs["minPwdLength"] ).replace( '[b\'', '' ).replace( '\']', '' )
lcknum = str( results_attrs["lockoutThreshold"] ).replace( '[b\'', '' ).replace( '\']', '' )
pwdhis = str( results_attrs["pwdHistoryLength"] ).replace( '[b\'', '' ).replace( '\']', '' )
lckdur = results_attrs["lockoutDuration"]
lckdur = str(
(int( str( lckdur ).replace( '[\'-', '' ).replace( '\']', '' ) ) * 0.0000001) / 60 )
pwdp = str( results_attrs["pwdProperties"] ).replace( '[\'', '' ).replace( '\']', '' )
(int( str( lckdur ).replace( '[b\'-', '' ).replace( '\']', '' ) ) * 0.0000001) / 60 )
pwdp = str( results_attrs["pwdProperties"] ).replace( '[b\'', '' ).replace( '\']', '' )
pwdpa = {
'DOMAIN_PASSWORD_COMPLEX': 1,
'DOMAIN_PASSWORD_NO_ANON_CHANGE': 2,
'DOMAIN_PASSWORD_NO_CLEAR_CHANGE': 4,
'DOMAIN_LOCKOUT_ADMINS': 8,
'DOMAIN_PASSWORD_STORE_CLEARTEXT': 16,
'DOMAIN_REFUSE_PASSWORD_CHANGE': 32}
for trigger, val in pwdpa.items():
vpwdp = ''.join( pwdp )
for trigger, val in list(pwdpa.items()):
vpwdp = ''.join( pwdp ).replace("[b'","")
if int( vpwdp ) & val:
pwdpar.append( trigger )
pwdpar = str( pwdpar ).replace( '[\'', '' ).replace( '\']', '' )
sql.Insert_Passwd(minpwd, lcknum, lckdur, pwdhis, (pwdp + " " + pwdpar))
if 'servicePrincipalName' in results_attrs:
for SPN in results_attrs["servicePrincipalName"]:
SPN = SPN.decode("utf-8")
r = SPN.split( ',CN=' )[0].replace( 'CN=', '' ).replace(',Builtin,'+baseDN+'', '' ).replace(baseDN,'').replace('OU=',' ')
if obj == "SPN":
sql.Insert_SPN(SPN, sAMAccountName, description, pwdLastSet, memberOf)
if obj == 'FGPP-PasswordSettings':
sql.Insert_FGPasswd(FGminpwd, FGlcknum, FGpwdhis, FGlckdur, FGpwdp, FGmember)
if obj == 'group':
sql.Insert_Groups(name, sids, description, str( membersOf), str(mem))
if obj == 'user':

usernamelist.append(name)
samnamelist.append(sAMAccountName)
sql.insert_Users(sAMAccountName, description, sids, homeDirectory, profilepath, pwdLastSet, lastLogon, userac, str(gidnum), (str(membersOf)))
if obj == 'computer':
sql.Insert_Computers(name, description, OS, OSV, (str(membersOf)))
except ldap.REFERRAL:
print colors.RD + "[-] "+ colors.NRM + "Incorrect fully qualified domain name. Please check your settings and try again."
print(colors.RD + "[-] "+ colors.NRM + "Incorrect fully qualified domain name. Please check your settings and try again.")
sys.exit()
sql.Close()
# try:
Expand All @@ -317,5 +330,5 @@ def main(self, IP, lusername, domain, password, unencrypted, port):
# print colors.RD + "[-] " + colors.NRM + "Error occured generating list of file shares"
# print "[*] Skipping Fileshare enumeration. As result show fileshare will not work (however manual queries will work"
# pass
print colors.GRN + "[+] "+ colors.NRM + "Database successfully created"
print(colors.GRN + "[+] "+ colors.NRM + "Database successfully created")
groupIDquery ()
Loading

0 comments on commit a32838d

Please sign in to comment.