Changelog:

  • 3 Nov 2023: update securelab.tar for this semester
  • 6 Nov 2023: remove notes about old versions of securelab.tar
  • 6 Nov 2023: for scenario 3 request that B not think A sent too many payments
  • 12 Nov 2023: hopefully be more clear about ./lab X attack being what you have to get working

Possibly working with a partner or two —

  1. Download securelab.tar [last updated 3 November] 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 without 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.)

    When you’re successful, this should be reflected in the output ./lab 0 attack.

  5. Do the same for scenario 1 and forward_attack_1. You can examine what scenario 1 is with

    ./lab 1 normal

    and run your attack on it with

    ./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. Do the same for scenario 3 as scenario 2 without causing B to think A sent too many payment requests.

  8. (optional) If you have time, do the same as scenario 4 for scenarios 2 and 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.

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

    If you submit the lab, we expect scenarios 1-3 to be complete.

    If you checkoff the lab, we will give credit if you do not complete scenario 3 but have significant progress by the end of the lab.

1 Our simulation API

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

A->B [encrypted to B's public key, then signed by A's private key]: PAY $0001 TO M
B->A [encrypted to A's public key, then signed by B's private key]: PAID #1 OF UP TO 3
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 #2 OF UP TO 3
A->B [encrypted to B's public key, then signed by A's private key]: PAY $0437 TO M
B->A [encrypted to A's public key, then signed by B's private key]: PAID #3 OF UP TO 3

2.5 Scenario 4

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.6 Scenario 5

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