commit 58d7943c0d6b6b425b0c1bc2b5f3a4d81087dbf8
parent 4c3995a8afb34136ab3e1bc02647f3d8e3824638
Author: Jack Sangdahl <jack@pngu.org>
Date: Mon, 13 Oct 2025 21:52:01 -0600
challenge 13: ecb cut-and-paste
Diffstat:
2 files changed, 152 insertions(+), 1 deletion(-)
diff --git a/lib/aes.c b/lib/aes.c
@@ -15,7 +15,23 @@ struct bytes pkcs7(const char *data, const size_t data_len, size_t block_size) {
}
struct bytes pkcs7_strip(unsigned char *data, size_t data_len) {
- size_t padding = data[data_len - 1], res_len = data_len - padding;
+ if (!data_len)
+ return (struct bytes){NULL, 0};
+ size_t padding = data[data_len - 1];
+ if (!padding || padding > data_len) {
+ unsigned char *res =
+ (unsigned char *)malloc(data_len * sizeof(unsigned char));
+ memcpy(res, data, data_len);
+ return (struct bytes){res, data_len};
+ }
+ for (size_t i = data_len - padding; i < data_len; i++)
+ if (data[i] != padding) {
+ unsigned char *res =
+ (unsigned char *)malloc(data_len * sizeof(unsigned char));
+ memcpy(res, data, data_len);
+ return (struct bytes){res, data_len};
+ }
+ size_t res_len = data_len - padding;
unsigned char *res = (unsigned char *)malloc(res_len * sizeof(unsigned char));
memcpy(res, data, res_len);
return (struct bytes){res, res_len};
diff --git a/src/13_ecb_cut_paste.c b/src/13_ecb_cut_paste.c
@@ -0,0 +1,135 @@
+// ECB cut-and-paste
+#include "aes.h"
+#include "random.h"
+
+/*
+ * write a k=v parsing routine, as if for a structured cookie. the routine
+ * should take: foo=bar&baz=qux&zap=zazzle
+ * ... and produce:
+ * {
+ * foo: 'bar',
+ * baz: 'qux',
+ * zap: 'zazzle'
+ * }
+ * (you know, the object; i don't care if you convert it to json). now write a
+ * function that encodes a user profile in that format, given an email address.
+ * you should have something like: profile_for("foo@bar.com")
+ * ... and it should produce:
+ * {
+ * email: 'foo@bar.com',
+ * uid: 10,
+ * role: 'user'
+ * }
+ * ... encoded as:
+ * email=foo@bar.com&uid=10&role=user
+ * your 'profile_for' function should not allow encoding metacharacters (& and
+ * =). eat them, quote them, whatever you want to do, but don't let people set
+ * their email address to "foo@bar.com&role=admin". now, two more each
+ * functions. generate a random aes key, then: a. encrypt the encoded user
+ * profile under the key; "provide" that to the "attacker" b. decrypt the
+ * encoded user profile and parse it using only the user input to
+ * 'profile_for()' (as an oracle to generate "valud" ciphertexts) and the
+ * ciphertexts themselves, make a 'role=admin' profile.
+ */
+
+unsigned char aes_key[16];
+
+void replace_chars(char *s, char find, char replace) {
+ for (int i = 0; s[i]; i++)
+ if (s[i] == find)
+ s[i] = replace;
+}
+
+void kv_pairs(const char *cookie) {
+ char *copy = strdup(cookie);
+ char *token = strtok(copy, "&");
+ printf("{\n");
+ while (token) {
+ char *equals = strchr(token, '=');
+ if (equals) {
+ *equals = '\0';
+ printf(" %s: %s\n", token, equals + 1);
+ }
+ token = strtok(NULL, "&");
+ }
+ printf("}\n");
+ free(copy);
+}
+
+char *profile_for(const char *email) {
+ char *copy = strdup(email);
+ replace_chars(copy, '&', ' ');
+ replace_chars(copy, '=', ' ');
+ size_t cookie_size =
+ strlen("email=") + strlen(copy) + strlen("&uid=10&role=user") + 1;
+ char *cookie = (char *)malloc(cookie_size * sizeof(char));
+ snprintf(cookie, cookie_size, "email=%s&uid=10&role=user", copy);
+ free(copy);
+ return cookie;
+}
+
+struct bytes encrypt(const char *profile) {
+ size_t profile_len = strlen(profile);
+ struct bytes padded = pkcs7(profile, profile_len, 16);
+ struct bytes ciphertext =
+ aes_encrypt_ecb((char *)padded.data, padded.len, aes_key, 16);
+ free(padded.data);
+ return ciphertext;
+}
+
+char *decrypt(const unsigned char *ciphertext, size_t ciphertext_len) {
+ struct bytes decrypted =
+ aes_decrypt_ecb(ciphertext, ciphertext_len, aes_key, 16);
+ struct bytes stripped = pkcs7_strip(decrypted.data, decrypted.len);
+ char *plaintext = (char *)malloc((stripped.len + 1) * sizeof(char));
+ memcpy(plaintext, stripped.data, stripped.len);
+ plaintext[stripped.len] = '\0';
+ free(decrypted.data);
+ free(stripped.data);
+ return plaintext;
+}
+
+struct bytes byte_slice(const struct bytes *input, size_t start,
+ size_t length) {
+ struct bytes res;
+ res.data = (unsigned char *)malloc(length * sizeof(unsigned char));
+ res.len = length;
+ memcpy(res.data, input->data + start, length);
+ return res;
+}
+
+struct bytes byte_concat(const struct bytes *a, const struct bytes *b) {
+ struct bytes res;
+ res.len = a->len + b->len;
+ res.data = (unsigned char *)malloc(res.len * sizeof(unsigned char));
+ memcpy(res.data, a->data, a->len);
+ memcpy(res.data + a->len, b->data, b->len);
+ return res;
+}
+
+int main(void) {
+ srand(time(NULL));
+ unsigned char *key = random_bytes(16);
+ memcpy(aes_key, key, 16);
+ free(key);
+ char *email1 = "jack@pngu.org";
+ char *profile1 = profile_for(email1);
+ struct bytes encrypted1 = encrypt(profile1);
+ struct bytes norole = byte_slice(&encrypted1, 0, 32);
+ char *email2 = "0123456789admin\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+ char *profile2 = profile_for(email2);
+ struct bytes encrypted2 = encrypt(profile2);
+ struct bytes admin = byte_slice(&encrypted2, 16, 16);
+ struct bytes payload = byte_concat(&norole, &admin);
+ char *decrypted = decrypt(payload.data, payload.len);
+ kv_pairs(decrypted);
+ free(profile1);
+ free(profile2);
+ free(encrypted1.data);
+ free(encrypted2.data);
+ free(norole.data);
+ free(admin.data);
+ free(payload.data);
+ free(decrypted);
+ return 0;
+}