Akurat było sprawdzona jedna część czyli wczytanie i dekodowanie pliku.
A jest jeszcze druga cześć, która sprawdza hasło.
Edit Field, te pole edycji jakoś powinno czytać znaki i sprawdzać czy hasło poprawne, wydawać by się mogło, że to będzie jakieś onChangeEvent, czyli po każdym znaku jest sprawdzana poprawność hasła, ewentualnie po zatwierdzeniu enterem.
A tu ciekawe zaskoczenie.
SetTimer(0, 0, 1000, 0x1000faf70)
I potem w PostMessageBox od nt!KiUserCallbackMessage otrzymuje (NULL, WM_TIMER-x133, 0x1000faf70) (czyżby można było za pomocą postmessage robić remoteProcedureCall? używając WM_TIMER)
Trafia to do DispatchMessage, tam w user32.dll -> user32.dll
I niebezpośrednio trafiamy do funkcji 0x1000faf70, czyli do tej funkcji obsługującej timer.
Pierwsza funkcja, nic ciekawego iterujemy strukturę timerów, szukamy w niej z naszym id timera, jest i tak tylko jeden element, i w następnych 8 bajtach struktury jest adres następnej funkcji :)
void timer_proc_0x1000faf70
(undefined8 param_1,double param_2,undefined8 hwnd,undefined8 message,
longlong timer_id)
{
longlong *timer_struct;
undefined8 extraout_XMM0_Qa;
uint idx;
if ((DAT_10019e3e0 == 0) || (*(char *)(DAT_10019e3e0 + 0x89) == 0)) {
idx = *(uint *)(DAT_100280cf0 + 2);
do {
if ((int)idx < 1) {
return;
}
idx = idx - 1;
timer_struct = (longlong *)get_timer_struct(param_1,param_2,DAT_100280cf0,idx);
param_1 = extraout_XMM0_Qa;
} while (*timer_struct != timer_id);
(*(code *)timer_struct[1])(timer_struct[2]);
}
return;
}
Druga funkcja, jakiś check, nie mam pojęcia co sprawdza ciężko to wydedukować.
void timer_wrapper1_0x10016a630(longlong *param_1)
{
/* security check */
if ((*(char *)(param_1 + 0x14) != 0) && (*(int *)(param_1 + 0xc) != 0)) {
(**(code **)(*param_1 + 0x1c8))(param_1);
}
return;
}
Trzecia funkcja też niebezpośrednio wywołana też jakieś sprawdzenie, które nigdy się nie wykonuje jak poprzednie.
void timer_wrapper2_0x10016a720(longlong param_1)
{
/* security check */
if (*(longlong *)(param_1 + 0x90) != 0) {
(**(code **)(param_1 + 0x90))(*(undefined8 *)(param_1 + 0x98),param_1);
}
return;
}
No i teraz jesteśmy we właściwej funkcji, która odpowiada za wyświetlanie rozszyfrowanej bazy.
void show_hide_password_func_0x10002cff0(undefined8 param_1,undefined8 param_2)
{
int number_of_readed_lines;
int memo_field_length;
int memo_field_length2;
longlong b_check;
longlong offset;
longlong is_that_same;
longlong is_equal;
longlong user_input;
longlong p_str2;
longlong p_str;
uint i;
p_str = 0;
p_str2 = 0;
user_input = 0;
set_pointer(&p_str,0);
set_pointer(&p_str2,0);
number_of_readed_lines = (**(code **)(*database + 0x108))(database);
if (-1 < number_of_readed_lines + -1) {
/* i = -1 */
i = 0xffffffff;
do {
i = i + 1;
/* 0xf8 - *p_str = database.readLineNumber(i) */
(**(code **)(*database + 0xf8))(database,&p_str,(ulonglong)i);
b_check = check_if_contains_substring("Password:",p_str,1);
if (b_check != 0) {
offset = find_first_occurance(':',p_str,1);
get_substring_after_offset(&p_str,1,offset);
set_pointer(&p_str2,p_str);
/* (Object + 0x7b8) == edit_field */
getWindowTextWrap(*(longlong **)(Object + 0x7b8),&user_input);
is_that_same = strcmp(p_str2,user_input);
if (is_that_same == 0) {
/* (Object + 0x7c0) == memo_field */
memo_field_length =
(**(code **)(**(longlong **)(*(longlong *)(Object + 0x7c0) + 0x610) + 0x108))
(*(undefined8 *)(*(longlong *)(Object + 0x7c0) + 0x610));
if (memo_field_length == 0) {
getWindowTextWrap(*(longlong **)(Object + 0x7b8),&user_input);
if (user_input != 0) {
/* Set memo field as database content */
(**(code **)(**(longlong **)(*(longlong *)(Object + 0x7c0) + 0x610) + 0x160))
(*(undefined8 *)(*(longlong *)(Object + 0x7c0) + 0x610),database);
}
}
}
getWindowTextWrap(*(longlong **)(Object + 0x7b8),&user_input);
is_equal = strcmp(p_str2,user_input);
if (is_equal != 0) {
memo_field_length2 =
(**(code **)(**(longlong **)(*(longlong *)(Object + 0x7c0) + 0x610) + 0x108))
(*(undefined8 *)(*(longlong *)(Object + 0x7c0) + 0x610));
if (memo_field_length2 != 0) {
clear_memo_field(*(longlong **)(Object + 0x7c0));
}
}
}
} while ((int)i < number_of_readed_lines + -1);
}
__stack_check((longlong)&stack0xfffffffffffffff8);
return;
}
Funkcja pobiera ile jest linii w bazie, tej rozszyfrowanej.
Potem pobiera pokolej linie.
Sprawdza czy mają podciąg "Password:", jeśli nie ma continue, jeśli ma to wchodzimy w if.
Potem znajdujemy pozycję ':' w stringu i kopiujemy wszystkie litery za tym do nowego buffera.
Pobieramy tekst z edit_field czyli tego co wpisujemy tekst i porównujemy z tym nowym stringiem uzyskanym przed chwilą.
Jeśli równe pobieramy ilość znaków memo_field jak nie jest puste to wykonujemy daną funkcję, jeśli nie to opuszczamy.
Sprawdzamy czy użytkownik wprowadził jakieś dane, jeśli tak to wyświetlamy bazę, jeśli nie to nie? trochę dziwne, jeśli hasło jest poprawne to po co sprawdzać czy ktoś coś wprowadził XD
Pobieramy edit_field, sprawdzamy znowu hasło, jeśli niepoprawne.
To pobieramy ilość znaków w memo field i jak nie jest puste to
Czyścimy, jak jest to opuszamy.
I teraz żeby objeść te zabezpieczenia to musimy z patchować 2 rzeczy lub 3.
- Sprawdzanie czy hasło jest takie samo,
- Sprawdzanie czy user coś wprowadził? optionalne, gdyż można tam losowe rzeczy wpisać w edit field ręcznie.
- musimy zablokować czyszczenie memo, gdyż nawet jak będzie coś wpisane, to i tak zostanie wyczyszczone po tym więc nie zdążymy zobaczy.
A to patch w pythonie.
file = bytearray(open('Protector.exe', 'rb').read())
cmp = 0x2c507 # any password
for i in range(cmp, cmp+0x6):
file[i] = 0x90
inp = 0x2c560 # no input needed
file[inp] = 0x90
file[inp+1] = 0x90
memo = 0x2c5c3 # disable cleaning memo
file[memo] = 0xeb
open('Protector_patched.exe', 'wb').write(file)
Widzę, że można jeszcze ręcznie wywołać wpisywanie tej bazy, ale id timera się zmienia, a go by było trzeba wyłączyć syscallem killTimer gdyż jak go nie wyłączymy to i tak by potem po jednej sekundzie wyzerował memo.