+ Reply to Thread
Results 1 to 5 of 5

Thread: Pirate from StartCTF или почему я не люблю цэтэфэ

  1. #1
    ARCHANGEL's Avatar

    Start Pirate from StartCTF или почему я не люблю цэтэфэ

    Wellcome, уважаемая аудитория.

    Не так давно у нас опять появились задания с очередного ещё одного цтф. Осё здесь ссылочка

    Для примера, возьмём заданьице полегче. Pirate. Из инструментов IDA Pro 7.0 + Visual Studio 2017 + WinHex (да-да, можно без него, но я вот так привык). Тут нам обещают антиотладку. Плагинов для сокрытия нет никаких, работаем.

    Загрузив файл, видим приглашение ввести флаг:

    Code:
    push    offset Str      ; "Enter the flag please: "
    mov     eax, ds:std::basic_ostream<char,std::char_traits<char>> std::cout
    push    eax             ; int
    call    sub_4014F0
    Ниже чтение флага и сравнение его длины с 35:

    Code:
    push    0Ah
    push    0
    push    24h
    lea     ecx, [ebp+Str]
    push    ecx
    mov     ecx, ds:std::basic_istream<char,std::char_traits<char>> std::cin
    call    ds:std::basic_istream<char,std::char_traits<char>>::getline(char *,__int64,char)
    lea     edx, [ebp+Str]
    push    edx             ; Str
    call    ds:strlen
    add     esp, 4
    cmp     eax, 23h
    jbe     short loc_401140
    Антиотладка
    1. GetTickCount
    2. CheckRemoteDebuggerPresent
    3. int 3
    4. int 1
    5. segment register mod + pushfd
    6. ud2

    Кратко по приколам - ничего нового. GetTickCount - делает замеры тиков для выявления трейса/остановки на брейкпоинте. CheckRemoteDebuggerPresent - вернёт по адресу второго параметра инфу, есть ли дебаггер или нет. int 1/int 3/ud2 сгенерят исключение без дебаггера. segment register mod + pushfd достанет установленный в 1 флаг трассировки, если была трассировка.

    Понатыкать-то этого всего понатыкали, а что оно защищает и как? Сердце этого всего - функция Encode:

    Code:
    push    ecx
    push    offset Transform
    lea     edx, [ebp+Str]
    push    edx
    call    Encode
    Первый вызов вообще наглядно показывает суть всей антиотладки:

    Code:
    mov     ecx, [ebp+pbDebuggerPresent]
    push    ecx
    push    offset Transform
    lea     edx, [ebp+Str]
    push    edx
    call    Encode
    Видите, как IDA дала имя переменной - pbDebuggerPresent. Если дебаггера нет - третий параметр всегда 0. Первый параметр - адрес нами введенного значения флага. Второй параметр - адрес таблицы Transform. Я её так назвал. Её мы сдампим WinHex`ом:

    Code:
    unsigned char Transform[256] = {
    	0x4E, 0x3E, 0x08, 0x2B, 0x0F, 0x08, 0x6D, 0x0E, 0x7C, 0x0A, 0x64, 0x64, 0x0D, 0x7D, 0x4D, 0x2A,
    	0x20, 0x06, 0x19, 0x3D, 0x62, 0x52, 0x6E, 0x0A, 0x38, 0x47, 0x3C, 0x6F, 0x73, 0x1D, 0x06, 0x04,
    	0x4A, 0x5A, 0x01, 0x24, 0x66, 0x18, 0x0D, 0x04, 0x51, 0x59, 0x45, 0x3A, 0x58, 0x3B, 0x46, 0x79,
    	0x42, 0x10, 0x44, 0x1E, 0x11, 0x60, 0x29, 0x39, 0x45, 0x7A, 0x5D, 0x16, 0x80, 0x5A, 0x5D, 0x2F,
    	0x80, 0x18, 0x1F, 0x32, 0x15, 0x73, 0x66, 0x1C, 0x36, 0x42, 0x49, 0x38, 0x41, 0x7E, 0x58, 0x41,
    	0x04, 0x63, 0x5D, 0x13, 0x43, 0x56, 0x7F, 0x04, 0x12, 0x5D, 0x47, 0x51, 0x35, 0x1A, 0x6D, 0x1D,
    	0x0D, 0x3D, 0x65, 0x47, 0x18, 0x38, 0x4F, 0x30, 0x55, 0x27, 0x6B, 0x24, 0x5A, 0x5C, 0x24, 0x73,
    	0x27, 0x00, 0x45, 0x19, 0x7D, 0x76, 0x56, 0x35, 0x5B, 0x34, 0x41, 0x0E, 0x69, 0x0E, 0x44, 0x57,
    	0x6E, 0x47, 0x28, 0x77, 0x7B, 0x4C, 0x32, 0x55, 0x58, 0x41, 0x00, 0x00, 0x5A, 0x42, 0x49, 0x5A,
    	0x2F, 0x55, 0x58, 0x58, 0x50, 0x3D, 0x70, 0x1A, 0x36, 0x50, 0x70, 0x17, 0x7D, 0x70, 0x27, 0x55,
    	0x37, 0x58, 0x1A, 0x7C, 0x50, 0x55, 0x58, 0x50, 0x14, 0x70, 0x1A, 0x09, 0x2F, 0x70, 0x6C, 0x00,
    	0x00, 0x00, 0x00, 0x00, 0x17, 0x4C, 0xF7, 0x59, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
    	0x4E, 0x00, 0x00, 0x00, 0x44, 0x33, 0x00, 0x00, 0x44, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    	0x17, 0x4C, 0xF7, 0x59, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
    	0x94, 0x33, 0x00, 0x00, 0x94, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x4C, 0xF7, 0x59,
    	0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x64, 0x02, 0x00, 0x00, 0xA8, 0x33, 0x00, 0x00
    };
    Да, её длина - 256 байт. Откуда взялся её размер? Из таких строк кода внутри неё:

    Code:
    mov     ecx, [ebp+arg_0]
    add     ecx, [ebp+CurrentSymbol]
    movsx   edx, byte ptr [ecx]
    Из-за byte ptr конкретно. Далее особо не заморачиваемся и рипаем ассемблерный код этой функции. Я добавил в неё 4-й параметр - длину. Получилось как-то так:

    Code:
    void Encode(unsigned char* buffer, unsigned char* table,unsigned int len, unsigned int xorvalue)
    {
    	unsigned int var_4;
    
    	__asm
    	{
    		mov var_4, 0
    		jmp     short loc_401076
    
    		loc_40106D :
    		mov     eax, var_4
    		add     eax, 1
    		mov var_4, eax
    
    		loc_401076 :
    		mov eax, len
    		cmp var_4, eax
    		jnb     short loc_4010A5		
    		mov     ecx, buffer
    		add     ecx, var_4
    		movsx   edx, byte ptr[ecx]
    		mov     eax, table
    		movsx   ecx, byte ptr[eax + edx + 0Ah]
    		xor ecx, xorvalue
    		mov     edx, buffer
    		add     edx, var_4
    		movsx   eax, byte ptr[edx]
    		xor eax, ecx
    		mov     ecx, buffer
    		add     ecx, var_4
    		mov [ecx], al
    		jmp     short loc_40106D
    
    		loc_4010A5 :
    	}
    }
    Что в самом pirate проверяется? Наш введенный флаг дважды пропускается через Encode, потом полученное кодированное значение сравнивается с захардкоженным значением из бинарника. Дампим это значение тоже ВинХексом:

    Code:
    unsigned char Encryptedflag[35] = {
    	0x5A, 0x42, 0x49, 0x5A, 0x2F, 0x55, 0x58, 0x58, 0x50, 0x3D, 0x70, 0x1A, 0x36, 0x50, 0x70, 0x17,
    	0x7D, 0x70, 0x27, 0x55, 0x37, 0x58, 0x1A, 0x7C, 0x50, 0x55, 0x58, 0x50, 0x14, 0x70, 0x1A, 0x09,
    	0x2F, 0x70, 0x6C
    };
    Где сравнение в коде? Вот:

    Code:
    mov     ecx, [ebp+var_50]
    movsx   edx, ds:byte_4031FC[ecx]
    mov     eax, [ebp+var_50]
    movsx   ecx, [ebp+eax+Str]
    cmp     edx, ecx
    jz      short loc_40128A
    byte_4031FC - начало закодированного флага.

    Как раскодировать? При кодировании каждый байт пропускается через Encode, получается как-то так: Encode(Encode(x)) = y. Причём есть соответсвие между х и у, но оно неоднозначное. Что это значит?

    Code:
    Encode(Encode(x)) = y
    Encode(Encode(z)) = y
    z != x
    Как будет работать генератор флагов? Строить таблицу соответствий, искать закодированный символ в таблице, его индекс - первоначальное значение.

    Строим таблицу для поиска:

    Code:
    for (int i = 0; i < 256; i++)
    		SwitchTable[i] = (unsigned char)(i & 0xFF);
    	Encode(SwitchTable, Transform, 0x100, 0);
    	Encode(SwitchTable, Transform, 0x100, 0);

    Находим все варианты оригинальным символов по закодированным:

    Code:
    std::vector<std::vector<unsigned int>> brute;
    	try
    	{
    		for (int i = 0; i < sizeof(Encryptedflag); i++)
    		{			
    			auto number = get_number_by_symbol(Encryptedflag[i]);			
    			brute.push_back(number);
    		}
    	}
    	catch (const std::string& error)
    	{
    		std::cout << error << std::endl;
    	}
    Как ищутся символы:

    Code:
    std::vector<unsigned int> get_number_by_symbol(unsigned char sym)
    {
    	std::vector<unsigned int> result;
    	for (unsigned int i = 0; i < sizeof(SwitchTable); i++)
    	{
    		if (SwitchTable[i] == sym)
    		{
    			if (isprint(i))
    				result.push_back(i);
    		}
    	}
    	if (!result.size())
    		throw std::string("Not found ") + (char) sym;
    
    	return result;
    }
    Обратите внимание на isprint - это ж цэтэфэ, могут попросить ввести флаг и не принимать на вход непечатаемые символы.

    Даже при таком раскладе получается куча вариантов - как перебрать их все? Есть черезжопный брутфорс, который я бы использовал только (АХТУНГ!) в цэтэфэ. Суть в том, что нужно выполнить генерацию кода переборщика скриптом. Вот две функции генерации:

    Code:
    def gen(x,y):
    	if x == y:
    		return
    	print("for (auto val_" + str(x) + " : brute[" + str(x) + "])")
    	print("{")
    	gen(x + 1,y)
    	print("}")
    
    def out(x,y):
    	result = ''
    	for a in range(x,y):
    		result += " << (char) val_" + str(a) + " "
    	return result
    Что это за фигня и куда её лепить? Ну вот представьте, что вам нужно перебрать все значения в векторе. Вы делаете что-то вроде:

    Code:
    for (auto val_0 : brute[0])
    {
      std::cout << (char) val_0 << std::endl;
    }
    Если таких векторов два - делаете вложенный цикл, три - ещё один вложенный цикл. Здесь генерируется 35 вложенных циклов.

    Code:
    for (auto val_0 : brute[0])
    	{
    		for (auto val_1 : brute[1])
    		{
    			for (auto val_2 : brute[2])
    			{
    				for (auto val_3 : brute[3])
    				{
    					for (auto val_4 : brute[4])
    					{
    						for (auto val_5 : brute[5])
    						{
    							for (auto val_6 : brute[6])
    							{
    								for (auto val_7 : brute[7])
    								{
    									for (auto val_8 : brute[8])
    									{
    										for (auto val_9 : brute[9])
    										{
    											for (auto val_10 : brute[10])
    											{
    												for (auto val_11 : brute[11])
    												{
    													for (auto val_12 : brute[12])
    													{
    														for (auto val_13 : brute[13])
    														{
    															for (auto val_14 : brute[14])
    															{
    																for (auto val_15 : brute[15])
    																{
    																	for (auto val_16 : brute[16])
    																	{
    																		for (auto val_17 : brute[17])
    																		{
    																			for (auto val_18 : brute[18])
    																			{
    																				for (auto val_19 : brute[19])
    																				{
    																					for (auto val_20 : brute[20])
    																					{
    																						for (auto val_21 : brute[21])
    																						{
    																							for (auto val_22 : brute[22])
    																							{
    																								for (auto val_23 : brute[23])
    																								{
    																									for (auto val_24 : brute[24])
    																									{
    																										for (auto val_25 : brute[25])
    																										{
    																											for (auto val_26 : brute[26])
    																											{
    																												for (auto val_27 : brute[27])
    																												{
    																													for (auto val_28 : brute[28])
    																													{
    																														for (auto val_29 : brute[29])
    																														{
    																															for (auto val_30 : brute[30])
    																															{
    																																for (auto val_31 : brute[31])
    																																{
    																																	for (auto val_32 : brute[32])
    																																	{
    																																		for (auto val_33 : brute[33])
    																																		{
    																																			for (auto val_34 : brute[34])
    																																			{
    																																				std::cout << (char)val_0 << (char)val_1 << (char)val_2 << (char)val_3 << (char)val_4 << (char)val_5 << (char)val_6 << (char)val_7 << (char)val_8 << (char)val_9 << (char)val_10 << (char)val_11 << (char)val_12 << (char)val_13 << (char)val_14 << (char)val_15 << (char)val_16 << (char)val_17 << (char)val_18 << (char)val_19 << (char)val_20 << (char)val_21 << (char)val_22 << (char)val_23 << (char)val_24 << (char)val_25 << (char)val_26 << (char)val_27 << (char)val_28 << (char)val_29 << (char)val_30 << (char)val_31 << (char)val_32 << (char)val_33 << (char)val_34 << std::endl;
    																																			}
    																																		}
    																																	}
    																																}
    																															}
    																														}
    																													}
    																												}
    																											}
    																										}
    																									}
    																								}
    																							}
    																						}
    																					}
    																				}
    																			}
    																		}
    																	}
    																}
    															}
    														}
    													}
    												}
    											}
    										}
    									}
    								}
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    Да простят меня за эту жесть. Но в цэтэфэ вполне могут попросить какой-то конкретный флаг, где есть имя автора, кличка его кота, адрес проживания - что угодно. Потому генерируем всё в файл:

    Code:
    .\PirateFlagGenerator.exe > log.txt
    После того, как файл стал более 400 Мб, я невзлюбил цэтэфэ и завершил эту жуть.

    Протестил парочку значений из файла:

    Code:
    PS C:\Reverse\pirate> .\pirate.exe
    Enter the flag please: M<EM':]0_m4ny_3xc4p78]n5_:0_h4nd'4}
    YES!
    PS C:\Reverse\pirate> .\pirate.exe
    Enter the flag please: M<EM'7]0_m3ny_4xc4b78]n5_:0_h3nd'4}
    YES!
    Результаты

    Особо ничему не научились. Ида про теперь офигенно разворачивает Seh обработчики, что показывает, куда перейдёт поток выполнения при исключении и нехило тащит данный таск. Если это цэтэфэ для школьников, то школьники нехило залевелапились с момента моего обучения в школе. Я в школе чайник поднимал двумя руками, пустой, без крышки. А тут такое надо решать. Горжусь школами, ну а чё?
    Добрым быть просто - достаточно обратить свой гнев на негодяев...

  2. #2

    Default Re: Pirate from StartCTF или почему я не люблю цэтэфэ

    Да, коллизии есть и в более сложных заданиях на CTF. И это, своего рода, бич. Особенно если речь идет о решении на время.
    Пост выше несколько утрирует ситуацию, которая имеет место быть. Соглашусь лишь с тем, что задания имеют отдаленное отношение к реверсу реальных приложений, но владения инструментарием все же требуют. На современных СTF (уровень сложности которых постоянно растет) зачастую остаются задания не решенные в течении отведенного времени, даже при участии маститых команд. И это при том, что азиаты решают ВСЕ с учетом того что команды в полсотни тел. Тут мы имеем задание школьного уровня и по сложности и по качеству. Что касается тезиса вынесенного в титлы, то "на вкус и цвет все фломастеры разные" и кто-то присыщен реверсом в каждодневной деятельности по месту работы, а для кого-то это наоборот хобби и смена рода деятельности.

    Что касается рассматриваемого задания, то не обязательно генерить выхлоп в 400+ Мб. :)

    Code:
    master = [0x5a, 0x42, 0x49, 0x5a, 0x2f, 0x55, 0x58, 0x58, 0x50, 0x3d, 
              0x70, 0x1a, 0x36, 0x50, 0x70, 0x17, 0x7d, 0x70, 0x27, 0x55, 
              0x37, 0x58, 0x1a, 0x7c, 0x50, 0x55, 0x58, 0x50, 0x14, 0x70, 
              0x1a, 0x9, 0x2f, 0x70, 0x6c]
    
    data   = [0x64, 0x64, 0x0d, 0x7d, 0x4d, 0x2a, 0x20, 0x06, 0x19, 0x3d, 
              0x62, 0x52, 0x6e, 0x0a, 0x38, 0x47, 0x3c, 0x6f, 0x73, 0x1d, 
              0x06, 0x04, 0x4a, 0x5a, 0x01, 0x24, 0x66, 0x18, 0x0d, 0x04, 
              0x51, 0x59, 0x45, 0x3a, 0x58, 0x3b, 0x46, 0x79, 0x42, 0x10, 
              0x44, 0x1e, 0x11, 0x60, 0x29, 0x39, 0x45, 0x7a, 0x5d, 0x16, 
              0x80, 0x5a, 0x5d, 0x2f, 0x80, 0x18, 0x1f, 0x32, 0x15, 0x73, 
              0x66, 0x1c, 0x36, 0x42, 0x49, 0x38, 0x41, 0x7e, 0x58, 0x41, 
              0x04, 0x63, 0x5d, 0x13, 0x43, 0x56, 0x7f, 0x04, 0x12, 0x5d, 
              0x47, 0x51, 0x35, 0x1a, 0x6d, 0x1d, 0x0d, 0x3d, 0x65, 0x47, 
              0x18, 0x38, 0x4f, 0x30, 0x55, 0x27, 0x6b, 0x24, 0x5a, 0x5c, 
              0x24, 0x73, 0x27, 0x00, 0x45, 0x19, 0x7d, 0x76, 0x56, 0x35, 
              0x5b, 0x34, 0x41, 0x0e, 0x69, 0x0e, 0x44, 0x57, 0x6e, 0x47, 
              0x28, 0x77, 0x7b, 0x4c, 0x32, 0x55, 0x58, 0x41] 
    
    o = ''
    for i in range(35):
       for j in range(32,128):
         k = j^data[j]
         if k < 128:
           if k^data[k] == master[i]:
             o += chr(j)
       o += ' '
    print o
    При этом генерится строка с учетом всех коллизий
    Code:
    MS <I E MS '),l{ 7: 0] 0] _ m 34 n y _ 34 Wx c 34 bp 7: 18 0] n 5 _ 7: 0] _ h 34 n d '),l{ 34 }
    из которой легко лепится "осмысленный" флаг MIEM{700_m4ny_3xc3p710n5_70_h4ndl3}

  3. Пользователь сказал cпасибо:
    Darwin (22-11-2017)
  4. #3
    ARCHANGEL's Avatar

    Default Re: Pirate from StartCTF или почему я не люблю цэтэфэ

    3xc3p710n5_70_h4ndl3 - сорри, но осмысленным я бы его не назвал.
    Добрым быть просто - достаточно обратить свой гнев на негодяев...

  5. #4

    Default Re: Pirate from StartCTF или почему я не люблю цэтэфэ

    Потому осмысленный и взято в кавычки.

  6. #5
    ARCHANGEL's Avatar

    Default Re: Pirate from StartCTF или почему я не люблю цэтэфэ

    Ок, до меня пока дойдёт ...
    Добрым быть просто - достаточно обратить свой гнев на негодяев...

+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:20
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org