Tut07: Socket Programming in Python

In this tutorial, we'll learn about basic socket programming in Python, and techniques for remote exploitation.

Step 1. nc command

The netcat command -- or nc for short -- is similar to the cat command, but for networking.

Here's a simple demo. Open two console windows side-by-side, and run a command in each, as shown below:

Console window 1Console window 2
$ nc -l 1234$ nc localhost 1234

The nc [address] [port] command connects to a server which is running at the given address and port. ("localhost" is an alias of 127.0.0.1, which is a reserved IP address that refers to your own computer.) nc -l [port] listens for connections to the given port, thus creating a very simple server.

Now type "hello" in console window 2 and hit <enter>:

Console window 1Console window 2
$ nc -l 1234$ nc localhost 1234
hellohello

Did you get the "hello" message in console 1? What if you type "world" as a reply in console 1?

You've just created a nice chat program! You can talk to your fellow students on our server this way :)

If it doesn't work, someone else may already be using port 1234 on the lab server. You can try a different port number, or try it on your local machine instead.

This advice applies to the rest of the tutorial, too -- it's a good idea to change port numbers from the defaults when trying things on the lab server.

Step 2. Rock, Paper, Scissors

Today's goal is to beat the computer in a game of rock-paper-scissors!

First, execute target and let it listen to some arbitrary port (e.g., 1234).

$ ssh lab07@<ctf-server-address>
$ mkdir /tmp/<your-secret-dir>
$ cd /tmp/<your-secret-dir>
$ cp -rf ~/tut07-socket ./
$ ./target 1234

In another console, use nc to connect to the target server that you just started:

$ ssh lab07@<ctf-server-address>
$ nc localhost 1234
Let's play rock, paper, scissors!
Your name>

FYI, on the server, the target service is already running on port 10700, and that's the one you'll need to beat to get the flag. You can also remotely connect to the service from outside the server:

$ nc <ctf-server-address> 10700

Do you want to explore the program a bit?

$ nc localhost 1234
Let's play rock, paper, scissors!
Your name> cs6265
Your turn> rock
You lose! Game over

You have to win 5 times in a row to win the whole game... so the odds aren't TOO bad for brute-forcing.

2.1. Socket Programming in Python

Let's use pwntools for socket operations. The following code snippet opens a socket on port 1234, reads 10 bytes from it, and writes them back to it:

from pwn import *

s = remote("localhost", 1234)
s.send(s.recv(10))
s.close()

We've provided some template code (template.py) to help you write a socket client program in Python.

Console window 1Console window 2
$ ./target 9736$ ./template.py

[Task] Your first task is to understand the template and write code that brute-forces the target server!

Just by playing the same move five (or more) times, you have a pretty high chance of winning (1/2^5 = 1/32)!

2.2. Timing Attack against the Remote Server!

Brute-forcing is dumb -- let's try a smarter approach.

Here's the most interesting part of target.c:

void start(int fd) {

  write(fd, "Let's play rock, paper, scissors!\nYour name> ", 44);

  char name[0x200];
  if (read_line(fd, name, sizeof(name) - 1) <= 0) {
    return;
  }

  srand(*(unsigned int*)name + time(NULL));

  int iter;
  for (iter = 0; iter < 5; iter ++) {

    write(fd, "Your turn> ", 11);

    char input[10];
    if (read_line(fd, input, sizeof(input) - 1) <= 0) {
      return;
    }

    int yours = convert_to_int(input);
    if (yours == -1) {
      write(fd, "Not recognized! You lost!\n", 26);
      return;
    }

    int r = rand();
    int result = yours - r % 3;
    if (result == 0) {
      write(fd, "Tie, try again!\n", 16);
      iter --;
      continue;
    }
    if (result == 1 || result == -2) {
      write(fd, "You win! try again!\n", 20);
    } else {
      write(fd, "You lose! Game over\n", 20);
      return;
    }
  }

  write(fd, "YOU WIN!\n", 9);
  dump_flag(fd);
}

Did you notice the use of srand() and name as a seed for the game?

srand(*(unsigned int*)name + time(NULL));

Since the name variable is what you've provided, and the time is predictable, you can abuse this information to win the match every single time! (If only it was always that easy to win jackpots...)

In order to do that, you need to be able to call C functions such as srand() and rand() from Python, so let's discuss how to do that. (This is similar to the "weak-random" challenge from lab04, so this might be familiar if you solved that -- or it might not, since there are a variety of ways to do it!)

1) Invoking a C function

ref. https://docs.python.org/3/library/ctypes.html

from ctypes import *

# How to invoke a C function in Python:
libc = cdll.LoadLibrary('libc.so.6')
libc.printf('hello world!\n')

This is how you can directly invoke the printf() function from Python. How would you invoke srand()/rand()?

2) Unpacking

There are several ways to cast a C string to an unsigned int in Python. When using pwntools, the best way is to use the u32() function, which is the inverse of the p32() function you're probably familiar with by now:

from pwn import *

print(u32(b'test'))  # prints 1953719668

Why is it 1953719668? The ASCII values for "t", "e", "s" and "t" are 0x74, 0x65, 0x73, 0x74. Because x86 is a little-endian architecture, four-byte integers are written and read in reversed byte order, i.e., 74 73 65 74. And 0x74736574 is 1953719668!


If you understand (1) and (2) above, you're ready to reliably beat the computer. Write a script that guesses the rand() output of the target and sends the winning move every time.

Once you get it working, don't forget that you can only get the "real" flag from port 10700 on our lab server:

$ nc <ctf-server-address> 10700

[Task] Guess the output of the target's rand() and send the winning move five times in a row to defeat the computer. Then submit the flag it prints.

Good luck!