This is a write-up for the picoCTF challenge “flag_shop”. PicoCTF is a CTF (Capture the Flag) platform created by Carnegie Mellon University to solve challenges in six different cyber security domains including web exploitation, reverse engineering, binary exploitation, and more. Writeups for this challenge were fragmented and lacked detail, so I took a crack at it to help bring some clarity!
Description:
Solution:
In this challenge we deal with a simple flag store and are provided its source written in C.
The service provides three options:
- Check Account Balance
- Buy Flags
- Exit
The second option contains 2 flag types: fake flags and a real flag. The real flag costs $100,000, but our starting balance is a mere $1100. How should we proceed? The source code will provide direction.
1#include <stdio.h>
2#include <stdlib.h>
3int main()
4{
5 setbuf(stdout, NULL);
6 int con;
7 con = 0;
8 int account_balance = 1100;
9 while(con == 0){
10
11 printf("Welcome to the flag exchange\n");
12 printf("We sell flags\n");
13
14 printf("\n1. Check Account Balance\n");
15 printf("\n2. Buy Flags\n");
16 printf("\n3. Exit\n");
17 int menu;
18 printf("\n Enter a menu selection\n");
19 fflush(stdin);
20 scanf("%d", &menu);
21 if(menu == 1){
22 printf("\n\n\n Balance: %d \n\n\n", account_balance);
23 }
24 else if(menu == 2){
25 printf("Currently for sale\n");
26 printf("1. Defintely not the flag Flag\n");
27 printf("2. 1337 Flag\n");
28 int auction_choice;
29 fflush(stdin);
30 scanf("%d", &auction_choice);
31 if(auction_choice == 1){
32 printf("These knockoff Flags cost 900 each, enter desired quantity\n");
33
34 int number_flags = 0;
35 fflush(stdin);
36 scanf("%d", &number_flags);
37 if(number_flags > 0){
38 int total_cost = 0;
39 total_cost = 900*number_flags;
40 printf("\nThe final cost is: %d\n", total_cost);
41 if(total_cost <= account_balance){
42 account_balance = account_balance - total_cost;
43 printf("\nYour current balance after transaction: %d\n\n", account_balance);
44 }
45 else{
46 printf("Not enough funds to complete purchase\n");
47 }
48
49
50 }
51
52 }
53 else if(auction_choice == 2){
54 printf("1337 flags cost 100000 dollars, and we only have 1 in stock\n");
55 printf("Enter 1 to buy one");
56 int bid = 0;
57 fflush(stdin);
58 scanf("%d", &bid);
59
60 if(bid == 1){
61
62 if(account_balance > 100000){
63 FILE *f = fopen("flag.txt", "r");
64 if(f == NULL){
65
66 printf("flag not found: please run this on the server\n");
67 exit(0);
68 }
69 char buf[64];
70 fgets(buf, 63, f);
71 printf("YOUR FLAG IS: %s\n", buf);
72 }
73
74 else{
75 printf("\nNot enough funds for transaction\n\n\n");
76 }}
77
78 }
79 }
80 else{
81 con = 1;
82 }
83
84 }
85 return 0;
86}
Our goal here is to end up with an account balance greater than $100,000 to buy the real flag.
While analyzing the source, I identified a section on line 39 where the program calculates the total cost of flags to be purchased. Recognizing that number_flags
is an integer input multiplied by 900 to calculate total_cost
, we may be able to exploit an integer overflow that occurs in this calculation.
If the total_cost
value can be manipulated to a negative value, it could trick the program’s logic by subtracting a negative amount from our account balance, thereby boosting our funds (adding total_cost
to our account balance).
By definition, an integer overflow occurs when an integer value is incremented to a value that is too large to store in the associated representation. When this occurs, the value may wrap to become a very small or negative number. While this may be intended behavior in circumstances that rely on wrapping, it can have security consequences if the wrap is unexpected. This is especially the case if the integer overflow can be triggered using user-supplied inputs [CWE-190].
number_flags
and total_cost
are both declared as an int
variable written in C. In this case, int
is signed and contains 32 bits.
Note: Binaries might be 32 or 64 bit which could affect integer sizes. While users could make an assumption about an integers size, it is a good idea to verify. In C, you could always check with
sizeof(int)
which should return the size in bytes. In the case of a 32-bit integer, this function call should return 4 bytes.
Our hint from picoCTF tells us that “Two’s complement can do some weird things when numbers get really big!”. In short, two’s complement is a mathematical method that is used to represent negative numbers with positive binary numbers.
Take a look at this chart that uses a 4-bit representation in two’s complement format to represent the signed integers:
The positive integer 1 in binary is 0001. To represent -1 using two’s complement, you would invert the bits to get 1110 and then add 1 to get 1111.
As mentioned previously, the integer overflow will occur when an integer value is incremented to a value that is too large to store in the associated representation. In this instance, the signed 4-bit integer can represent values from -8 to 7 and when you try to add 1 to the 7, the result will be -8 as the value has “wrapped around” due to the overflow — this is because we do not have enough bits to represent an 8. An 8 in binary is 00001000, this is too long for our 4-bit binary representation, so everything but the last four bits will be truncated, leaving 1000. A quick look at our chart tells us that it is -8.
The range of values for a signed 32-bit integer are from -(2^31) to 2^31 - 1 or -2,147,483,648 to 2,147,483,647.
Remember, once we get to the upper limit of what we can represent in two’s complement, then the very next number in binary counting is a negative. Therefore, to calculate the value of number_flags
that would trigger the integer overflow, we can take the upper limit of 2,147,483,647 and divide by 900, price per flag. The result is 2386092.94111, and we can round this number to 2386100 to ensure that the overflow occurs reliably.
This will exceed the maximum representable value for the integer and hopefully then make our account balance go up so we can purchase the flag.
Flag:
picoCTF{m0n3y_bag5_68d16363}