Writeup - Easy Math 1 (UIUCTF 2022)


UIUCTF 2022 - easy math 1 Writeup

  • Type - pwn
  • Name - easy math 1
  • Points - 88

Description

1
2
3
4
5
Take a break from exploiting binaries, and solve a few* simple math problems!

$ ssh [email protected], password is ctf

author: kuilin

Writeup

This pwn problem was pretty interesting and not too difficult to implement. Once you signed in to the server using the provided credentials, you were greeted with four challenge files:

1
2
3
4
5
6
7
8
9
10
11
$ ssh [email protected]
[email protected]'s password:
== proof-of-work: disabled ==
ctf@test-center:~$ ls -l
-rw-r--r-- 1 ctf ctf 265 Jul 30 18:48 README
-r-sr-xr-x 1 admin admin 13296 Jul 30 18:50 easy-math
-rw-r--r-- 1 ctf ctf 1348 Jul 29 09:01 easy-math.c
-r-------- 1 admin admin 327 Jul 29 09:01 flag
ctf@test-center:~$ cat flag
cat: flag: Permission denied
ctf@test-center:~$

The README contained some information about what programs were installed on the Ubuntu 18.04 instance to help with on-prem solve scripts. The flag file was only readable by the admin user. Then, the source code and binary for easy-math were provided. The whole source code can be found here, but the important parts are below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define MATH_PROBLEMS 10000

int take_test() {
int urand = open("/dev/urandom", O_RDONLY);
if (!urand) return 1;
unsigned char urand_byte;

for (int i=0; i<MATH_PROBLEMS; i++) {
if (read(urand, &urand_byte, 1) != 1) return 1;
int a = urand_byte & 0xf;
int b = urand_byte >> 4;
printf("Question %d: %d * %d = ", i+1, a, b);

int ans;
if (scanf("%d", &ans) != 1) return 1;
if (ans != a * b) return 1;
}

close(urand);
return 0;
}

int main() {
setreuid(geteuid(), getuid());
setvbuf(stdout, NULL, _IONBF, 0);

// ...

if (take_test()) {
printf("You have failed the test.\n");
return 1;
}

setreuid(getuid(), getuid());
system("cat /home/ctf/flag");
return 0;
}

The gist of this script is that 10,000 simple math problems will be thrown at you, and if all of them are solved correctly, then the flag will be printed out. However, this binary wasn’t accessible through a netcat listener, but rather when signed in to the SSH server. There were two approaches I could take - develop a script that would sign in to SSH and answer all the problems, or create a script on the server that would solve it for me. I was lazy, so I decided to take the first route.

My solve script (math.py) hinged on the Python library pexpect. This library allowed you to run system commands, capture the output, and send custom input dynamically by using the spawn(), expect(), and sendline() commands. It SSHed into the system, ran the executable, and captured both numbers. It then passed it into eval and sent the result to the program through the SSH tunnel. It printed out each question it solved and then the flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ python3 math.py
Question 1: 13 * 3 = 39
Question 2: 6 * 0 = 0
Question 3: 10 * 9 = 90
Question 4: 7 * 15 = 105
Question 5: 14 * 5 = 70
Question 6: 13 * 12 = 156
...
Question 9999: 15 * 8 = 120
Question 10000: 11 * 9 = 99
Flag!
Nice job! Now, the question is, did you do it the fun way,
or by hiding behind your ssh client?

Part 1 flag:
uiuctf{now do it the fun way :D}

To solve part 2, use `ssh [email protected]` (password is still ctf)
This time, your input is sent in live, but you don't get any output until
after your shell exits.
ctf@test-center:~$

Flag: uiuctf{now do it the fun way :D}