picoCTF21 - Shop Writeup
Shop
Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at nc mercury.picoctf.net 11371
.
Upon first inspection, this is a go
binary. According to some of my friends, it’s annoying to reverse (haha).
scribbl@beepboop:~$ file source
source: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, Go BuildID=r5IKmnk_hVFErwy5ewa3/PyI570w85RI5Xa1aSnrW/RxmeFbAluXa5Hnisdodi/MJQIt60cZyLjm5Wta-r0, with debug_info, not stripped
Loading it into IDA, we trace through main()
without much success. There’s a get_flag()
function that we can follow through the menu code.
// main.get_flag
// local variable allocation has failed, the output may be wrong!
void __golang __noreturn main_get_flag()
{
string filename; // [esp+0h] [ebp-44h]
_DWORD *filenamea; // [esp+0h] [ebp-44h]
_BYTE filename_4[24]; // [esp+4h] [ebp-40h] OVERLAPPED
int elem[3]; // [esp+28h] [ebp-1Ch] BYREF
_DWORD a[2]; // [esp+34h] [ebp-10h] BYREF
runtime_eface a_8; // [esp+3Ch] [ebp-8h]
filename.str = (uint8 *)"flag.txt";
filename.len = 8;
*(retval_80D3040 *)&filename_4[4] = io_ioutil_ReadFile(filename);
main_check(*(error *)&filename_4[16]);
elem[0] = *(_DWORD *)&filename_4[4];
elem[1] = *(_DWORD *)&filename_4[8];
elem[2] = *(_DWORD *)&filename_4[12];
a_8 = 0LL;
a[0] = &RTYPE_string_0;
a[1] = &main_statictmp_14;
a_8 = runtime_convT2Eslice((runtime__type *)&RTYPE__slice_uint8_0, elem);
filenamea = a;
*(_QWORD *)filename_4 = 0x200000002LL;
fmt_Println(*(_slice_interface_ *)&filename_4[-4]);
os_Exit(0);
}
However, where does the user even interact with the program? We need to look at where the function takes user input since at the end of the day, it’s our input that will make a difference.
if ( wallet < inv.array[v11].price )
{
v35[0] = &RTYPE_string_0;
v35[1] = &main_statictmp_7;
ai.array = (interface_ *)v35;
*(_QWORD *)&ai.len = 0x100000001LL;
fmt_Println(ai);
v15 = wallet;
}
else
{
inv.array[v11].count = count - v9;
if ( *(_DWORD *)v10 >= inv.len )
runtime_panicindex();
v14 = *(_DWORD *)v10;
v15 = wallet - *(_DWORD *)_num * inv.array[v14].price;
if ( *(_DWORD *)v10 >= user.len )
runtime_panicindex();
user.array[v14].count += *(_DWORD *)_num;
if ( inv.len <= 2u )
runtime_panicindex();
if ( inv.array[2].count != 1 )
main_get_flag();
}
v13 = v15;
In this block, we notice our wallet is being updated with the price
of the object we’re buying multiplied by the amount we set in _num
. However, it doesn’t validate any negative inputs. Thus, we can just buy a negative value to increase the amount of money we have in our wallet!
scribbl@beepboop:~$ nc mercury.picoctf.net 11371
Welcome to the market!
=====================
You have 40 coins
Item Price Count
(0) Quiet Quiches 10 12
(1) Average Apple 15 8
(2) Fruitful Flag 100 1
(3) Sell an Item
(4) Exit
Choose an option:
0
How many do you want to buy?
-100000
You have 1000040 coins
Item Price Count
(0) Quiet Quiches 10 100012
(1) Average Apple 15 8
(2) Fruitful Flag 100 1
(3) Sell an Item
(4) Exit
Choose an option:
2
How many do you want to buy?
1
Flag is: [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 56 100 55 50 55 49 102 125]
>>> flag = "112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 98 56 100 55 50 55 49 102 125".split(" ")
>>> "".join(chr(int(c)) for c in flag)
'picoCTF{b4d_brogrammer_b8d7271f}'