Changelog:

  • 23 February 2023: mark scenario 3 as if you have time and as tentatively won’t be required
  • 24 February 2023: complete tries to use a nonce as tries to use a nonce to prevent replay attacks
  • 28 February 2023 11:50p: update securelab.tar to improve error messages (by removing text about internals of the lab; no functionality changes)
  • 1 March 2023: correct description of what signature is in struct message (in lab writeup and comments in lab.h; no functionality changes): using from’s public key -> using from’s private key
  • 1 March 2023: correct scenario 3 hint to state that you should use A to decrypt messages from B (since A will talk to you and not B) rather than the other way around
  • 1 March 2023: make it clearer that new_message can encrypt messages
  • 1 March 2023: update securelab.tar to not give cryptic error when encrypted messages are much shorter than expected; add note about problems with manipulating encrypted messages; add missing ) in example code
  • 1 March 2023: note possible crash from earlier securelab.tar
  1. Download securelab.tar [last updated 2023-03-01 3:45p] and extract it.

  2. In this lab, you will act as a network attacker on several examples of attempts to communicate securely. Since this a new lab, I’m not sure how many are easily doable in the lab time. Do as many as you can within the lab time. I recommend working with a partner.

    Run

    make

    to build the testing program and then run

    ./lab 0 normal

    to see the first scenario. (It is also listed below in this document.) You’ll see a transcript showing an exchange where A asks B to pay M $1000, then asks B to pay $438 dollars.

    At the end of the simulation, we report how much M is paid by B.

    You are playing the role of the network attacker M and will demostrate how you get B to pay you more by having control over the network.

  3. When sending messages over the network, our testing program can call one of the forward_...() functions in attack.c. In this function, you can process the messages sent between A and B, including changing messages, adding new messages, or discarding messages.

    When you run ./lab N normal, we’ll forward messages wihtout changes function for scenario N (where N is 0, 1, 2, 3, or 4).

    When you run ./lab N attack, we’ll use the forward_attack_N function to decide what to do with sent messages.

  4. For scenario 0, Modify the forward_attack_0 function so B thinks A asked them to pay M (you) $10001438 instead of $1438. (Hint: This does not require sending a lot of messages.)

  5. Do the same for scenario 1 and forward_attack_1, which you can run with

    ./lab 1 normal

    (non-attacked version) or

    ./lab 1 attack
  6. Scenario 2 is trickier. Get B to pay at least $2000 (we don’t care about the exact amount) instead of $1438.

  7. (optional) If you have time, do the same as scenario 2 for scenario 3. In this scenario, B initiates the communication with A and tries to use a nonce to prevent replay attacks. It will help that A will also accept messages from you (M), but will always indicate that there are no more payments. You can use this to run a man-in-the-middle attack on B and A; see also the famous flaws of the Needham-Schroeder protocol. The basic idea is that because A will communicate with you (M), you can get them to decrypt B’s message to A for you.

  8. Submit your attack.c to the submission site or show your code to a TA for checkoff.

    (This is a new lab, so I’m not sure how much typical students will complete. But I’m expecting that most students will not complete scenario 3 within the lab time, and so it will not be expected to be complete for submission to the submission site.)

1 Our simulation API

[Note added 1 March:] If your simulation crashes with an error like thread panicked at range start index 18446744073709551594 out of range for slice of length 10, then this is from sending a message that is marked as encrypted but whose data is too short. Versions of securelab.tar after 2023-03-01 3:45pm give a better error message in this case.

The code for forward_normal looks like:

void forward_normal(struct message *message) {
    send_message(message);
}

It just forwards the message without making changes.

You can modify this to change the messages in various ways, for example:

1.1 Manipulating and creating messages

The struct message is defined as follows:

struct message {
    char from;              // message sender
    char to;                // message recipient
    bool is_encrypted;      
    bool is_signed;         // if true, 'data' is signed using from's private key
    char data[1024];        
    size_t data_size;
    char signature[64];     // only used if is_signed is true
};

to indicates where the network will attempt to deliver the message when you can send_message().

Signed messages will have signature set to some meaningful data.

You can construct messages (including encrypting them) using the following utility function:

struct message *new_message(
    char from,
    char to,
    const char *data,   // NULL-terminated string
    bool is_encrypted,  // encrypt the message
    bool is_signed      // sign the message --- only supported if 'from' is 'M'
);

You can decrypt messages encrypted to M using:

void decrypt_message_for_M(
    const struct message *message,
    char *buffer,
    size_t buffer_size
);

which will copy the decrypted contents of the message into buffer (truncating the contents if the buffer_size is too small).

Encrypting a message will public-key encrypt it to to’s public key. new_message has a list of all the public keys to do this.

If you manipulate the data or data_size of an encrypted message directly or unset the is_encrypted field of an existing message (instead of encrypting a new message to the public key), then most likely the resulting message will be considered malformed.

Signing a message will only work if from is M; new_message does not have any other private keys.

2 The scenarios

2.1 Scenario 0

A->B [not encrypted]: PAY $1000 TO M
B->A [not encrypted]: PAID
A->B [not encrypted]: PAY $0438 TO M
B->A [not encrypted]: PAID

2.2 Scenario 1

A->B [encrypted to B's public key]: PAY $1000 TO M
B->A [encrypted to A's public key]: PAID
A->B [encrypted to B's public key]: PAY $0438 TO M
B->A [encrypted to A's public key]: PAID

2.3 Scenario 2

A->B [encrypted to B's public key, then signed by A's private key]: PAY $1000 TO M
B->A [encrypted to A's public key, then signed by B's private key]: PAID
A->B [encrypted to B's public key, then signed by A's private key]: PAY $0438 TO M
B->A [encrypted to A's public key, then signed by B's private key]: PAID

2.4 Scenario 3

B->A [encrypted to A's public key]: START [random number 1]
A->B [encrypted to B's public key]: [random number 1] PAY $1000 TO M
B->A [encrypted to B's public key]: [random number 1] PAID
B->A [encrypted to A's public key]: START [random number 2]
A->B [encrypted to B's public key]: [random number 2] PAY $0438 TO M
B->A [encrypted to B's public key]: [random number 2] PAID
B->A [encrypted to A's public key]: START [random number 3]
A->B [encrypted to B's public key]: [random number 3] NO MORE PAYMENTS FOR YOU

2.5 Scenario 4

B->A [encrypted to A's public key, then signed by B's private key]: START [random number 1]
A->B [encrypted to B's public key, then signed by A's private key]: [random number 1] PAY $1000 TO M
B->A [encrypted to A's public key, then signed by B's private key]: [random number 1] PAID
B->A [encrypted to A's public key, then signed by B's private key]: START [random number 2]
A->B [encrypted to B's public key, then signed by A's private key]: [random number 2] PAY $0438 TO M
B->A [encrypted to A's public key, then signed by B's private key]: [random number 1] PAID
B->A [encrypted to A's public key, then signed by A's private key]: START [random number 3]
A->B [encrypted to B's public key, then signed by A's private key]: [random number 3] NO MORE PAYMENTS FOR YOU