728x90
Format String 취약점이 있는 C 코드 예시:
c
Copy code
#include <stdio.h>
void vulnerable_function(char* str) {
printf(str);
}
int main(int argc, char** argv) {
vulnerable_function(argv[1]);
return 0;
}
이 코드는 사용자가 입력한 문자열(argv[1])을 printf 함수에 직접 전달하고 있습니다. 이것은 Format String 취약점을 만들어냅니다. 왜냐하면 printf 함수는 첫 번째 인자로 포맷 문자열을 기대하는데, 이 예제에서는 사용자 입력을 직접 포맷 문자열로 사용하고 있기 때문입니다. 따라서 사용자는 %x, %n 등의 포맷 지정자를 이용하여 프로그램의 메모리를 읽거나 쓸 수 있게 됩니다.
취약점 분석: 이 코드의 주요 문제는 printf 함수가 사용자 제공 문자열을 포맷 문자열로 사용한다는 점입니다. 이로 인해 사용자는 포맷 지정자를 이용해 메모리를 읽거나 쓸 수 있게 됩니다. 이러한 취약점은 시스템의 메모리 레이아웃을 노출시키거나, 실행 흐름을 조작하거나, 임의의 코드를 실행하는 데 사용될 수 있습니다1.
취약점 패치: 이 취약점을 패치하는 가장 간단한 방법은 printf 함수에 사용자 제공 문자열을 직접 전달하는 대신 정적 포맷 문자열을 사용하는 것입니다. 예를 들면 다음과 같습니다:
c
Copy code
#include <stdio.h>
void safe_function(char* str) {
printf("%s", str);
}
int main(int argc, char** argv) {
safe_function(argv[1]);
return 0;
}
이제 printf 함수는 정적 포맷 문자열 "%s"를 사용하고 있으며, 사용자 입력은 이 포맷 문자열에 따라 처리됩니다. 따라서 사용자는 더 이상 포맷 지정자를 이용해 메모리를 읽거나 쓸 수 없게 되어 Format String 취약점이 패치되었습니다.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
// 표준 라이브러리를 포함합니다. 이것들은 기본적인 기능들을 제공합니다.
#define FLAG_BUFFER 128
#define MAX_SYM_LEN 4
// 상수를 정의합니다. FLAG_BUFFER는 파일에서 읽을 문자 수를 정의하고, MAX_SYM_LEN은 주식 심볼의 최대 길이를 정의합니다.
typedef struct Stonks {
int shares;
char symbol[MAX_SYM_LEN + 1];
struct Stonks *next;
} Stonk;
// 주식(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;
}
// view_portfolio(): 사용자의 포트폴리오를 출력합니다.
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;
}
// pick_symbol_with_AI(): "AI"를 사용하여 주식 심볼을 선택하고, 새로운 주식 객체를 만듭니다.
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);
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("%s", user_buf); //주요 개선점
// TODO: Actually use key to interact with API
view_portfolio(p);
return 0;
}
// buy_stonks(): 사용자가 주식을 구매할 수 있게 합니다. 이 함수는 주식을 선택하고 구매하는 프로세스를 시뮬레이션합니다.
Portfolio *initialize_portfolio() {
Portfolio *p = malloc(sizeof(Portfolio));
p->money = (rand() % 2018) + 1;
p->head = NULL;
return p;
}
// initialize_portfolio(): 사용자의 포트폴리오를 초기화합니다. 초기에는 사용자는 임의의 양의 돈을 가지고 있지만 주식은 보유하고 있지 않습니다.
void free_portfolio(Portfolio *p) {
Stonk *current = p->head;
Stonk *next = NULL;
while (current) {
next = current->next;
free(current);
current = next;
}
free(p);
}
// free_portfolio(): 사용자의 포트폴리오를 메모리에서 해제합니다. 이 함수는 프로그램 종료 시 호출됩니다.
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);
}
// main(): 프로그램의 시작점입니다. 사용자의 포트폴리오를 초기화하고, 사용자에게 주식을 구매하거나 포트폴리오를 확인하라는 메시지를 출력합니다. 사용자의 선택에 따라 적절한 함수가 호출됩니다
마찬 가지 논리로 pico CTF의 stonk코드도 이렇게 개선될 수 있다
python3 panda.py (format string 취약점을 노린 코드 현대 메모리 보호 기법도 통과)
[+] Starting local process './output_file': pid 4752
/home/htb-ac-592046/panda.py:11: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.recvuntil("View my")
/home/htb-ac-592046/panda.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.send("1\n")
/home/htb-ac-592046/panda.py:13: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.recvuntil("What is your API token?\n")
/home/htb-ac-592046/panda.py:16: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
r.send("%x" + "-%x"*40 + "\n")
[*] Stopped process './output_file' (pid 4752)
공격이 전혀 통하지 않는 것을 확인할 수 있다.
728x90
'hacking sorcerer' 카테고리의 다른 글
퓨어 알티지 DHA 오메가 3 (0) | 2023.06.18 |
---|---|
쥐를 잡아 미키마우스 (0) | 2023.06.16 |
GPT4가 분석한 memcpy (0) | 2023.06.08 |
gdb-peda와 흠냐 (0) | 2023.06.07 |
GPT-4랑 프로토스타 기본 프로그램을 가지고 직접 실습을 해보았다. (0) | 2023.06.02 |