# SECCON CTF Quals - 2019
## Pwn / 332 - lazy> ```> lazy.chal.seccon.jp 33333> ```### solution
By [@jaidTw](https://github.com/jaidTw)
Credits to [@HexRabbit](https://blog.hexrabbit.io)
This challenge didn't give us any file. Connect to the challenge first.```1: Public contents2: Login3: Exit```Select option 1```Welcome to public directoryYou can download contents in this directorydiary_4.txtdiary_3.txtdiary_1.txtlogin_source.cdiary_2.txt```I can download login_source.c, there were only soure code of functions `login` and `input`.```c#define BUFFER_LENGTH 32#define PASSWORD "XXXXXXXXXX"#define USERNAME "XXXXXXXX"
int login(void){        char username[BUFFER_LENGTH];        char password[BUFFER_LENGTH];        char input_username[BUFFER_LENGTH];        char input_password[BUFFER_LENGTH];
        memset(username,0x0,BUFFER_LENGTH);        memset(password,0x0,BUFFER_LENGTH);        memset(input_username,0x0,BUFFER_LENGTH);        memset(input_password,0x0,BUFFER_LENGTH);
        strcpy(username,USERNAME);        strcpy(password,PASSWORD);
        printf("username : ");        input(input_username);        printf("Welcome, %s\n",input_username);
        printf("password : ");        input(input_password);
        if(strncmp(username,input_username,strlen(USERNAME)) != 0){                puts("Invalid username");                return 0;        }
        if(strncmp(password,input_password,strlen(PASSWORD)) != 0){                puts("Invalid password");                return 0;        }
        return 1;}
void input(char *buf){        int recv;        int i = 0;        while(1){                recv = (int)read(STDIN_FILENO,&buf[i],1);                if(recv == -1){                        puts("ERROR!");                        exit(-1);                }                if(buf[i] == '\n'){                        return;                }                i++;        }}```Apparently, there's a out-of-bound read inside `input()`, it won't stop reading until a `'\n'` is encountered. Because `\x00` won't stop the read, in `input(input_password)`, we can overwrite `username`, `input_username`, `password`, `input_password` into same strings to pass the check.
There was a new option after successfully logged in.```Logged in!1: Public contents2: Login3: Exit4: Manage```I could download the binary `lazy` using option 4, but `.` is not allowed in the input by the program, so I couldn't download `libc.so.6`.```Welcome to private directoryYou can download contents in this directory, but you can't download contents with a dot in the namelazylibc.so.6Input file name```After used the [script](./get_lazy.py) to get `lazy`, I started to reverse it.
First, in `login()`, here were the username and password, so just use them to login in the following expoits.```cstrcpy(username, "_H4CK3R_");strcpy(password, "3XPL01717");```
* In option 1 `public()`: Switch the directory to `./q/public`, but input use `fgets()`, no overflow here.```cunsigned __int64 public() {  char *HOME; // rax  char s[24]; // [rsp+0h] [rbp-20h]  if ( chdir("./q/public") == -1 ) {    ...  }  puts("Welcome to public directory");  puts("You can download contents in this directory");  listing();  fgets(s, 20, stdin);  download(s);  HOME = getenv("HOME");  if ( chdir(HOME) == -1 ) {    ...  }}```* In option 4 `filter()`: Switch the directory to `./q/private`. Input used `input()` here, so a stack overflow, followed by a format string vulnerability.```cvoid __fastcall filter(){  char *HOME; // rax  char s[24]; // [rsp+0h] [rbp-20h]   ...  if ( chdir("./q/private") == -1 ) {    ...  }  puts("Welcome to private directory");  puts("You can download contents in this directory, but you can't download contents with a dot in the name");  listing();  puts("Input file name");  input(s);  if ( strchr(s, '.') ) {     exit(-1);  }  printf("Filename : ");  printf(s);  puts("OK! Downloading...");  download(s);  HOME = getenv("HOME");  if ( chdir(HOME) == -1 ) {    exit(-1);  }}```Stack canary is on, so we need to leak the canary by format string first, then do the ROP.
Because the `.` check of the path is perform outside of `download()`, so I used ROP to first call `input()`, getting the filename wrote to a buffer, then call `download()` with the buffer to download anyfile. But soon, we found that when trying to download `libc.so.6`, we always got an unexpected EOF and terminated, so the libc was incomplete(~4MB/10MB), I tried many tools such as `objdump`, `nm` and `one_gadget`, but none of them worked with a incomplete libc.
Later, I tried another approach: control the directory using `chdir()`, then call `listing()` to traverse and search for the flag, then use `download()` to get the flag. This looked promising and I successfully read the directory.```run.shlazyld.socat.profilelibc.so.6810a0afb2c69f8864ee65f0bdca999d7_FLAG.bashrcq.bash_logout```Unfortunately, the filename of `810a0afb2c69f8864ee65f0bdca999d7_FLAG` exceeded the limit of `download()` (>27), I could only try to use `open()`, `read()`, `puts()` to read the file (There's no a rdx gadget, but you can assign rdx an appropriate value for `read()` by calling `strlen()`).
Now, I could read most of the files, including those under `/proc`, however, I still couldn't read the flag, and I found there was a `cat` in the same directory, so I guessed the flag should be read by the `cat` with setuid. This means the only ways to get the flag were to invoke the shell or use `execve` to run the setuid `cat`, which I thought were impossible without using libc.
When I was almost giving it up, my teammate [@HexRabbit](https://blog.hexrabbit.io) found that IDA Pro can parse the incomplete `libc.so.6`, so we only need to leak `__libc_start_main` from stack, calculate the address of `system`, then use ROP to read `sh` and jump to `system`, and that's it.
Here's the [script](./exp.py)
```$ ./cat 810a0afb2c69f8864ee65f0bdca999d7_FLAGSECCON{Keep_Going!_KEEP_GOING!_K33P_G01NG!}```