Rating:
# 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 contents
2: Login
3: Exit
```
Select option 1
```
Welcome to public directory
You can download contents in this directory
diary_4.txt
diary_3.txt
diary_1.txt
login_source.c
diary_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 contents
2: Login
3: Exit
4: 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 directory
You can download contents in this directory, but you can't download contents with a dot in the name
lazy
libc.so.6
Input 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.
```c
strcpy(username, "_H4CK3R_");
strcpy(password, "3XPL01717");
```
* In option 1 `public()`: Switch the directory to `./q/public`, but input use `fgets()`, no overflow here.
```c
unsigned __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.
```c
void __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.sh
lazy
ld.so
cat
.profile
libc.so.6
810a0afb2c69f8864ee65f0bdca999d7_FLAG
.bashrc
q
.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_FLAG
SECCON{Keep_Going!_KEEP_GOING!_K33P_G01NG!}
```