B1g_Mac
Problem
Here's a zip file. You can also find the file in /problems/b1g-mac_0_ac4b0dbedcd3b0f0097a5f056e04f97a.
Solution
Unzip
b1g_mac.zip
withunzip b1g_mac.zip
to get a folder calledtest
(with 18 images inside) and amain.exe
executable file.Running the
main.exe
file producesNo flag found, please make sure this is run on the server
, which is an interesting error message because this file cannot be run on the shell server since it is a Windows executable and the shell server runs Linux.Creating a fake flag file with
echo picoCTF{fake_flag} > flag.txt
and rerunning the program results in the following:Work is done! Wait for 5 seconds to exit.
Since something happened lets see if the files are any different by re-extracting the test directory from the zip file and comparing it to the contents of the current test directory:
diff -r test/ test_original/
. Nothing appears to have changed. Lets reset thetest
directory and delete the "new" folder in case it actually changed something.Reverse the binary file using Ghidra (cheat sheet). Open it and in the symbol tree click on main. The decompiled main function will show on the left.
int __cdecl _main(int _Argc,char **_Argv,char **_Env) { undefined4 local_60; undefined local_5a [50]; undefined4 local_28; undefined4 local_24; undefined4 local_20; size_t local_1c; FILE *local_18; int local_14; ___main(); _isOver = 0; local_28 = 0x65742f2e; local_24 = 0x7473; local_20 = 0; _folderName = &local_28; local_14 = 0; _pLevel = 0; local_18 = _fopen("flag.txt","r"); if (local_18 == (FILE *)0x0) { _puts("No flag found, please make sure this is run on the server"); } local_1c = _fread(local_5a,1,0x12,local_18); if ((int)local_1c < 1) { /* WARNING: Subroutine does not return */ _exit(0); } _flag = local_5a; _flag_size = 0x12; local_60 = 0; _flag_index = &local_60; _puts("Work is done!"); _listdir(local_14,_folderName); _puts("Wait for 5 seconds to exit."); _sleep(5); return 2; }
The program opens the flag file, reads 18 characters, initializes some globals and then calls
_listdir
(after printing"Work is done!"
which is strange because nothing much is done before that message is printed)._listdir
function:void __cdecl _listdir(int param_1,undefined4 param_2) { int iVar1; BOOL BVar2; CHAR local_958 [2048]; _WIN32_FIND_DATAA local_158; HANDLE local_18; bool local_11; int local_10; local_18 = (HANDLE)0x0; _sprintf(local_958,"%s\\*.*",param_2); local_18 = FindFirstFileA(local_958,(LPWIN32_FIND_DATAA)&local_158); if (local_18 == (HANDLE)0xffffffff) { _printf("Path not found: [%s]\n",param_2); } else { local_10 = 1; local_11 = true; while (local_11 != false) { iVar1 = _strcmp(local_158.cFileName,"."); if ((iVar1 != 0) && (iVar1 = _strcmp(local_158.cFileName,".."), iVar1 != 0)) { _sprintf(local_958,"%s\\%s",param_2,local_158.cFileName); if ((local_158.dwFileAttributes & 0x10) == 0) { if (local_10 == 1) { if (param_1 == 0) { _hideInFile(local_958); } else { if (param_1 == 1) { _decodeBytes(local_958); } } } local_10 = 1 - local_10; } else { _printf("Folder: %s\n",local_958); _listdir(param_1,local_958); } } if (_isOver != '\0') break; BVar2 = FindNextFileA(local_18,(LPWIN32_FIND_DATAA)&local_158); local_11 = BVar2 != 0; } FindClose(local_18); } return; }
_listdir
is a recursive function which iterates over files in the folder (it was called with the folder"./test"
by the main function) and, ifparam_1
if 0, it calls _hideInFile for every other file (only the'Copy'
files). Ifparam_1
is set to 1,_decodeBytes
is called on the file instead.Right click
_listdir
and go to "References > Find references to _listdir". One of the three options is an uncalled function labeled_decode
:void UndefinedFunction_00401afe(void) { undefined4 uStack40; undefined4 uStack36; undefined4 uStack32; undefined4 uStack28; undefined4 uStack24; undefined4 uStack20; undefined4 uStack16; uStack36 = 0x616c6f68; uStack32 = 0x202020; uStack28 = 0; uStack24 = 0; uStack20 = 0; _buff_size = 0x12; uStack40 = 0; uStack16 = 1; _buff = &uStack36; _buff_index = &uStack40; _listdir(1,_folderName); _printf("value of DECODE %s \n",_buff); _puts("Wait for 5 seconds to exit."); _sleep(5); /* WARNING: Subroutine does not return */ _exit(0); }
The
_decode
function uses the same_folderName
global variable as the main function. We can call_decode_
using the debugger after_folderName
gets initialized in the main function.Get location in main right after initializing
_folderName
:0x00401bda
00401bc1 c7 44 24 MOV dword ptr [ESP + local_20],0x0 50 00 00 00 00 00401bc9 8d 44 24 48 LEA EAX=>local_28,[ESP + 0x48] 00401bcd a3 34 54 MOV [_folderName],EAX = ?? 40 00 00401bd2 c7 44 24 MOV dword ptr [ESP + local_14],0x0 5c 00 00 00 00 00401bda c7 05 48 MOV dword ptr [_pLevel],0x31 = ?? 54 40 00 31 00 00 00
Get address of
_decode
:0x00401afe
LAB_00401afc XREF[1]: 004019bd(j) 00401afc c9 LEAVE 00401afd c3 RET _decode 00401afe 55 PUSH EBP 00401aff 89 e5 MOV EBP,ESP 00401b01 83 ec 38 SUB ESP,0x38 00401b04 c7 45 e0 MOV dword ptr [EBP + -0x20],0x616c6f68 68 6f 6c 61 00401b0b c7 45 e4 MOV dword ptr [EBP + -0x1c],0x202020 20 20 20 00
Debugger and Get Flag 1. Linux (
winedbg
- did not work): Open in GDB withwinedbg main.exe
then set a breakpoint at0x00401bda
withbreak *0x00401bda
(the*
means "address" instead of function) thennext
thenset $eip = 0x00401afe
thennext
and get the flag. 2. Windows (x32dbg
- Successful): It is important that the file times in the zip are not changed. On Windows, use7-zip
to open and extract these files without modifying their file times. The built-in to Windows extraction option does not work. Runx96dbg
from x64dbg and selectx32dbg
. Then openmain.exe
inx32dbg
and runSetBPX 00401bda
to set a breakpoint at0x00401bda
. Next, click through "run" until this breakpoint is reached. When the breakpoint is hit, runEIP=00401afe
to change theEIP
to_decode
. "Run" one more time and get the flag.value of DECODE picoCTF{M4cTim35!} Wait for 5 seconds to exit.
_hideInFile
function:void __cdecl _hideInFile(LPCSTR param_1) { BOOL BVar1; FILETIME local_2c; FILETIME local_24; FILETIME local_1c; char local_12; char local_11; HANDLE local_10; local_10 = CreateFileA(param_1,0x100,0,(LPSECURITY_ATTRIBUTES)0x0,3,0,(HANDLE)0x0); _DoNotUpdateLastAccessTime(local_10); if (local_10 == (HANDLE)0xffffffff) { _printf("Error:INVALID_HANDLED_VALUE"); } else { BVar1 = GetFileTime(local_10,(LPFILETIME)&local_1c,(LPFILETIME)&local_24,(LPFILETIME)&local_2c); if (BVar1 == 0) { _printf("Error: C-GFT-01"); } else { local_11 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; local_12 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; _encodeBytes(local_11,local_12,(uint *)&local_2c); if (0 < _pLevel) { local_11 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; local_12 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; _encodeBytes(local_11,local_12,(uint *)&local_1c); } if (_pLevel == 2) { local_11 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; local_12 = *(char *)(*_flag_index + _flag); *_flag_index = *_flag_index + 1; _encodeBytes(local_11,local_12,(uint *)&local_24); } BVar1 = SetFileTime(local_10,&local_1c,&local_24,&local_2c); if (BVar1 == 0) { _printf("Error: C-SFT-01"); } else { if (_flag_size <= *_flag_index) { _isOver = 1; } CloseHandle(local_10); } } } return; }
We can see that the function is using
GetFileTime
andSetFileTime
, and this also explains the challenge name (MAC stands for "Modification, Access, Creation"). The Windows user interface shows us the date and H:M:S, but NTFS file systems have a resolution of 100 Nanoseconds for these fields (TheFILETIME
structure represents the time in 100-nanosecond intervals since January 1, 1601). More info about how the flag was encoded and how to write a script to decode it: Dvd848 (Archive) and AMACB (Archive)
Flag
picoCTF{M4cTim35!}
Last updated
Was this helpful?