정수 오버플로우(Integer Overflow) 문제이다.
https://play.picoctf.org/practice/
문제를 풀기 위해서 nc 접속.
nc를 접속했을 때의 메뉴 선택창이 나온다.
1번을 입력하면 계좌의 현재 잔고가 나온다.
2번 메뉴에서 있는 1337 플래그를 구매하려고 하면 가격은 100000달러이고, 수량이 1개 있다고 한다.
사보려고 했지만 잔고에 충분한 달러가 없기 때문에 구매할수 없다.
2번 메뉴의 1번은 입력에서 900을 곱해서 달러를 반환해주는 것 같다.
하지만 이후 잔고를 다시 확인했을 때도 달러는 1100이다.
왜 그러는지 문제에서 소스코드를 다운받아서 확인해보자.
<소스코드>
#include <stdio.h>
#include <stdlib.h>
int main()
{
setbuf(stdout, NULL);
int con;
con = 0;
int account_balance = 1100;
while(con == 0){
printf("Welcome to the flag exchange\n");
printf("We sell flags\n");
printf("\n1. Check Account Balance\n");
printf("\n2. Buy Flags\n");
printf("\n3. Exit\n");
int menu;
printf("\n Enter a menu selection\n");
fflush(stdin);
scanf("%d", &menu);
if(menu == 1){
printf("\n\n\n Balance: %d \n\n\n", account_balance);
}
else if(menu == 2){
printf("Currently for sale\n");
printf("1. Defintely not the flag Flag\n");
printf("2. 1337 Flag\n");
int auction_choice;
fflush(stdin);
scanf("%d", &auction_choice);
if(auction_choice == 1){
printf("These knockoff Flags cost 900 each, enter desired quantity\n");
int number_flags = 0;
fflush(stdin);
scanf("%d", &number_flags);
if(number_flags > 0){
int total_cost = 0;
total_cost = 900*number_flags;
printf("\nThe final cost is: %d\n", total_cost);
if(total_cost <= account_balance){
account_balance = account_balance - total_cost;
printf("\nYour current balance after transaction: %d\n\n", account_balance);
}
else{
printf("Not enough funds to complete purchase\n");
}
}
}
else if(auction_choice == 2){
printf("1337 flags cost 100000 dollars, and we only have 1 in stock\n");
printf("Enter 1 to buy one");
int bid = 0;
fflush(stdin);
scanf("%d", &bid);
if(bid == 1){
if(account_balance > 100000){
FILE *f = fopen("flag.txt", "r");
if(f == NULL){
printf("flag not found: please run this on the server\n");
exit(0);
}
char buf[64];
fgets(buf, 63, f);
printf("YOUR FLAG IS: %s\n", buf);
}
else{
printf("\nNot enough funds for transaction\n\n\n");
}}
}
}
else{
con = 1;
}
}
return 0;
}
<코드의 일부>
2번 메뉴중에서 우리가 만족시켜야 하는 부분이다.
먼저, number_flags 변수를 입력을 받는다. 이때, 이 값은 0보다 커야한다.
이후 number_flags의 값에 900을 곱한 값과 현재 잔고의 값을 비교하게 된다. 여기서 total_cost가 현재 잔고 값보다 작아야 한다.
그러면 현재 잔고에 total_cost 값을 빼주게 된다.
다시 정리해보면,
1. 입력받는 number_flags는 양수값
2. number_flags*900 <= 1100(현재 잔고)
3. 2번이 참이라면, 1100(현재잔고) - number_flags*900
4. 현재잔고가 100,000 이여야 구매가능
사실 일반적인 상식으로는 이 세가지의 조건을 충족할 수 없다.
하지만 프로그래밍 상에서는 각 자료형들의 값 범위가 있기 때문에 조건을 충족 시킬 수 있다.
바로 정수오버플로우가 발생하기 때문이다.
number_flags의 자료형은 int이고, int 자료형의 값 범위는 '-2,147,483,648 ~ 2,147,483,647' 이다.
여기서 number_flags에 900을 곱한 값이 2,147,483,647 보다 높게 설정된다면, 정수 오버플로우가 발생한다.
직접 확인해보자
입력 값에 900을 곱하기 때문에 int 자료형의 양수 최대값에서 900을 나누었다. 이 결과 값을 문제에 넣어보자 (2,386,092)
값이 2,147,482,800이 된다. 여기서 이 값이 2,147,483,647이 아닌 이유는 나누기를 하면서 나머지 값이 버려졌기 때문이다. 두 값을 빼보면 나머지 값은 847인 것을 알 수 있다.
만약, number_flags 값이 2,386,092가 아닌 2,386,093이라면 *900을 수행한 값은 2,147,483,700이 되어야 한다.
계산기에서도 뭔가 이상한 오류가 발생하는걸 알 수 있다.
분명 양수와 양수의 곱인데 음수가 나오는 이런 이상한 현상이 발생하는 것이다.
그림처럼 양수최대값과 음수최대값은 서로 1의 값 차이로 붙어있다.
그래서 곱하기를 수행한 값이 int 자료형의 양수최대 범위를 넘어서 음수최대범위부터 아래로 내려가는 것이었다.
2,147,483,700(곱하기를 수행한 값) - 2,147,483,647(양수의 최대범위) = 53
그래서 음수의 최대값 -2,147,483,648 보다 +52된 값(+1은 양수의 최대값 2,147,483,647에서 음수의 최대값 -2,147,483,648으로 넘어갈 때), 즉 -2,147,483,596이 된다.
문제에 직접 2,386,093 값을 입력해보자.
나의 계좌 잔고가 음수 값으로 설정된 것을 확인할 수 있다.
다시 우리가 충족해야 할 조건을 확인해보자.
1. 입력받는 number_flags는 양수값
2. number_flags*900 <= 1100(현재 잔고)
3. 2번이 참이라면, 1100(현재잔고) - number_flags*900
4. 현재잔고가 100,000 이여야 구매가능
1, 2번 조건이 충족되고, 3번이 수행될때 account_balance(현재잔고) 1100에서 number_flags*900(total_cost) 값을 뺀다.
total_cost값이 음수라면 account_balance(현재잔고) - (-total_cost) 연산을 통해 계좌의 잔액을 더할 수 있다.
플래그를 얻을 수 있다.
이 문제에서는 정수오버플로우에 대한 지식을 요구한다.
+) 정수오버플로우에 대해서 이해했다면, 플래그 구매를 할 수 있는 가장 작은 number_flags 값을 계산해보는 것도 개념정리에 좋은 것 같다.
reference : C 언어 코딩 도장, 7.2 오버플로우와 언더플로우 알아보기 - https://dojang.io/mod/page/view.php?id=32