«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
04-20 16:25
관리 메뉴

lancelot.com

[펌] 프로세스의 메모리안에 접근하고 읽고 쓰기. 본문

프로그래밍

[펌] 프로세스의 메모리안에 접근하고 읽고 쓰기.

lancelot50 2008. 8. 19. 13:47

데브피아에서 퍼왔습니다.

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7100&ref=7100
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7102&ref=7102
http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7107&ref=7107

안녕하세요. 이 부분에 특히 관심 많은 인간입니다(?). 아직 초짜지만..

이와 관련된 자료가 적어서 좀 어렵길래.. 저도 다시 한번 새로 기억내 보며 뭔가 빠뜨린 게 없나 살펴보고, 혹여나 여기에 관심있는 분들을 위해서 씁니다.

메모리에서 값을 찾는다는 것은 보통 자기 자신(프로그램)에게는 쓸모가 거의 없는 법이고, 다른 프로세스에서나 자주 쓰일 법한 기술이지요.

그런데 다들 알다시피 다른 프로세스의 주소공간을 침범할 수는 없습니다.
하지만 Windows에서는 Debug용으로 다른 프로세스의 메모리를 읽고 쓰는 기능을 하는 API 함수를 제공하고 있습니다.

ReadProcessMemory, WriteProcessMemory가 그 대표적인 예입니다.
저 두 API의 정체를 알려면 선언문을 보는 것이 기초이겠지요..

BOOL ReadProcessMemory( HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesRead );

BOOL WriteProcessMemory( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T* lpNumberOfBytesWritten );

모양이 memcpy같은 함수와 비슷하게 생겼습니다. 새로운 인자 몇개가 보이지요. 하나 하나 알아봅시다.


hProcess는 핸들값(OpenProcess를 쓰죠)인데, 메모리를 읽으려면 PROCESS_VM_READ를 가지고 있어야 하고요.. 쓸려면 PROCESS_VM_WRITE를 가지고 있어야 하는데.. 보통은 간편하게 PROCESS_ALL_ACCESS를 쓰죠.

lpBaseAddress는 읽을 메모리의 주소를 가르키는 포인터입니다. 저는 이거와 뒤의 lpBuffer때문에 무지 헷갈렸었는데..

알고보니 별거 아니더라고요. 저기다가 DWORD 변수의 포인터를 넣는건지.. 하여튼 헷갈리더군요.

그게 아니고, 저건 단순히 포인터라고 생각하시면 됩니다. 즉,

DWORD BaseAddress = 0x400000;
void *lpBaseAddress = (void *)BaseAddress;

이 말은 lpBaseAddress가 가르키는 곳을 BaseAddress (여기서는 0x400000)로 하라는 의미겠죠..

lpBuffer도 위와 마찬가지로 메모리에서 읽어낸 내용을 저장할 버퍼(변수)를 가르키는 포인터이지요..

예를 들어서..


int Buffer = 0;
void *lpBuffer = (void *)&Buffer;


위와 비슷하죠? &가 붙은게 다른건데 이게 무슨 의미인지 잘 아실겁니다. 위에것과 아래것 쉽게 헷갈리니까 대충 보지 마시구요..

nSize는 Buffer의 크기지요. 그냥 숫자를 넣어주심 되고요..
마지막 인자는 쓰거나 읽은 메모리의 크기를 반환합니다. 물론 lpBuffer처럼 포인터 형으로 넣어주셔야 나오겠죠..
리턴값은 성공하면 0이 아닌 값이 나오고, 실패하면 0이 나옵니다.


예)
HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); // 프로세스를 연다

DWORD buf = 0;
if (ReadProcessMemory(h, (LPVOID)0xA70000, &buf, sizeof(DWORD), NULL) != 0)
{ // 메모리를 읽는다
    buf++;

    WriteProcessMemory(h, (LPVOID)0xA70000, (LPCVOID)&buf, sizeof(DWORD), NULL) // 메모리에 쓴다
}

pid라는 프로세스 ID를 가진 프로세스의 메모리의 A70000의 값을 읽어서 그 값에 1을 더한 것을 거기다가 쓴 거죠.
별로 어려운 거 아닙니다.

이번에는 여기까지 하고요.. 다음에는 메모리를 어떤 식으로 찾아야 하는가에 대해서 알아보죠..


어려울 지도 모르겠지만 알고 나면 별로 어려운게 아닙니다.
어떻게 하는지는 저번에 설명을 했지요. 그럼 바로 코딩에 들어가봅시다 '▽'/






 

#define ARRAY_EACH_SIZE 100     // 배열 크기

DWORD *FindMem(HANDLE hProc, unsigned int nFind, unsigned int nSize){


SYSTEM_INFO si; // 메모리 주소 최소값, 최대값을 출력
MEMORY_BASIC_INFORMATION mbi;   // 페이지 정보 출력
DWORD nMem = 0, i, j, result_ct = 0;        // 현재 메모리 주소 변수와 그 외 연산에 필요한 변수


BYTE *srcArray; // 메모리에서 찾아낼 것
BYTE *destArray;    // 메모리에서 읽어낸 것
DWORD *FindData = (DWORD *)malloc(ARRAY_EACH_SIZE*4);   // 찾아낸 것을 저장


이게 필요한 변수들입니다.
자 이제 찾을 준비를 해야죠~

메모리에서 찾아낼 것을 저장하는 포인터 변수에는 형식이 unsigned char 군요.
문자열의 경우 별 문제가 없으리라고 봅니다만, 여기서 찾을 것은 숫자(그 중에서 정수)인데...
이걸 어떻게 type-casting 할까요?

우선 숫자가 실제로 메모리상에 어떻게 저장되는지나 알아봅시다.

이건 CPU마다 다른데, 보통은 revserse hexa chain이라는 녀석을 씁니다.
1바이트씩 거꾸로 저장을 해놓는거죠. 예를 들어서, 0x0FFAA2 (1047202)라는 값이 메모리에는,
A2 FA 0F 로 저장이 됩니다.


이것을 함수로 옮겨 보았습니다.


BYTE *byteArray(unsigned int n, unsigned int size)
{
    BYTE *arr = new BYTE [size];
    int i;

    for (i=0; i<size; i++)
   {
        arr[i] = n % 256;
        n /= 256;
    }
    return arr;
}



이건 값 n을 받아서 BYTE 배열로 변환해 줍니다.


srcArray = byteArray(nFind, nSize);


이런 식이겠죠.

GetSystemInfo(&si);
nMem = (DWORD)si.lpMinimumApplicationAddress; //메모리 주소의 최소값을 구합니다.

이제 찾아 볼까요?

#define ARRAY_EACH_SIZE 100     // 배열 크기

BYTE *byteArray(unsigned int n, unsigned int size)
{
    BYTE *arr = new BYTE [size];
    int i;

    for (i=0; i<size; i++)
    {
        arr[i] = n % 256;
        n /= 256;
    }

    return arr;
}

PDWORD FindMem(HANDLE hProc, unsigned int nFind, unsigned int nSize)
{

    SYSTEM_INFO si; // 메모리 주소 최소값, 최대값을 출력
    MEMORY_BASIC_INFORMATION mbi;   // 페이지 정보 출력
    DWORD nMem = 0, i, j, result_ct = 0;        // 현재 메모리 주소 변수와 그 외 연산에 필요한 변수

    BYTE *srcArray; // 메모리에서 찾아낼 것
    BYTE *destArray;    // 메모리에서 읽어낸 것

    DWORD *FindData = (DWORD *)malloc(ARRAY_EACH_SIZE*4);   // 찾아낸 것을 저장

    srcArray = byteArray(nFind, nSize); // 변환

    GetSystemInfo(&si);

    nMem = (DWORD)si.lpMinimumApplicationAddress; //메모리 주소의 최소값을 구한다.

    do
    {

        if (VirtualQueryEx(hProc, (LPVOID)nMem, &mbi, sizeof(mbi)) == sizeof(mbi))
        {         // 페이지의 정보를 읽어낸다

            if (mbi.RegionSize > 0 && mbi.Type == MEM_PRIVATE && mbi.State == MEM_COMMIT)
            {      // 페이지가 사용가능한지 알아낸다

                destArray = new BYTE [mbi.RegionSize];      // 메모리를 읽을 준비를 한다

                if (ReadProcessMemory(hProc, mbi.BaseAddress, destArray, mbi.RegionSize, NULL) != 0){       // 메모리를 읽는다

                    for (i=0; i<(DWORD)mbi.RegionSize; i++)
                    {        // 읽은 메모리와 찾을 메모리를 비교한다
                       for (j=0; j<nSize; j++)
                      {
                            if (i+nSize+1 > mbi.RegionSize)
                                break;

                            if (destArray[i+j] != srcArray[j])
                            {
                                break;
                            }
                            else if (j == nSize-1)
                            {        // 값을 찾아냄
                                if (result_ct % ARRAY_EACH_SIZE == 0)   // 배열의 크기를 조절한다
                                    FindData = (DWORD *)realloc(FindData, nSize*ARRAY_EACH_SIZE*(result_ct / ARRAY_EACH_SIZE + 1));

                                FindData[result_ct] = (DWORD)mbi.BaseAddress + i;       // 배열에 주소를 저장한다
                                result_ct++;
                            }
                        }
                    }
                }
                delete destArray;       // 메모리를 읽었으니 해제
            }
            nMem = (DWORD)mbi.BaseAddress + (DWORD)mbi.RegionSize;  //현재 페이지 주소 계산
        }
    }while(nMem < (DWORD)si.lpMaximumApplicationAddress);       // 최대 주소를 넘어갔으면 루프에서 빠져나옴

    delete srcArray;    // 메모리 해제

    return FindData;    // 결과값 리턴

}




아래는 매우 간단한 예제입니다.


#include <stdio.h>
#include <windows.h>
#include <Tlhelp32.h>
#include <malloc.h>

#define ARRAY_EACH_SIZE 100     // 배열 크기


BYTE *byteArray(unsigned int n, unsigned int size){

    BYTE *arr = new BYTE [size];

    int i;

    for (i=0; i<size; i++)
    {
        arr[i] = n % 256;
        n /= 256;
    }

    return arr;
}


PDWORD FindMem(HANDLE hProc, unsigned int nFind, unsigned int nSize)
{
    SYSTEM_INFO si; // 메모리 주소 최소값, 최대값을 출력

    MEMORY_BASIC_INFORMATION mbi;   // 페이지 정보 출력

    DWORD nMem = 0, i, j, result_ct = 0;        // 현재 메모리 주소 변수와 그 외 연산에 필요한 변수

    BYTE *srcArray; // 메모리에서 찾아낼 것
    BYTE *destArray;    // 메모리에서 읽어낸 것

    DWORD *FindData = (DWORD *)malloc(ARRAY_EACH_SIZE*4);   // 찾아낸 것을 저장

    srcArray = byteArray(nFind, nSize); // 변환

    GetSystemInfo(&si);
    nMem = (DWORD)si.lpMinimumApplicationAddress; //메모리 주소의 최소값을 구한다.

    do{

        if (VirtualQueryEx(hProc, (LPVOID)nMem, &mbi, sizeof(mbi)) == sizeof(mbi))
        {         // 페이지의 정보를 읽어낸다

            if (mbi.RegionSize > 0 && mbi.Type == MEM_PRIVATE && mbi.State == MEM_COMMIT)
            {      // 페이지가 사용가능한지 알아낸다

                destArray = new BYTE [mbi.RegionSize];      // 메모리를 읽을 준비를 한다

                if (ReadProcessMemory(hProc, mbi.BaseAddress, destArray, mbi.RegionSize, NULL) != 0)
                {       // 메모리를 읽는다
                    for (i=0; i<(DWORD)mbi.RegionSize; i++)
                    {        // 읽은 메모리와 찾을 메모리를 비교한다
                        for (j=0; j<nSize; j++)
                        {
                            if (i+nSize+1 > mbi.RegionSize)
                                break;

                            if (destArray[i+j] != srcArray[j])
                            {
                                break;
                            }
                            else if (j == nSize-1)
                            {        // 값을 찾아냄

                                if (result_ct % ARRAY_EACH_SIZE == 0)   // 배열의 크기를 조절한다
                                    FindData = (DWORD *)realloc(FindData, nSize*ARRAY_EACH_SIZE*(result_ct / ARRAY_EACH_SIZE + 1));
                                FindData[result_ct] = (DWORD)mbi.BaseAddress + i;       // 배열에 주소를 저장한다
                                result_ct++;
                            }
                        }
                    }
                }
                delete destArray;       // 메모리를 읽었으니 해제

            }

            nMem = (DWORD)mbi.BaseAddress + (DWORD)mbi.RegionSize;  //현재 페이지 주소 계산
        }
    }while(nMem < (DWORD)si.lpMaximumApplicationAddress);       // 최대 주소를 넘어갔으면 루프에서 빠져나옴

    delete srcArray;    // 메모리 해제

    return FindData;    // 결과값 리턴
}



void main()
{
    PROCESSENTRY32 pe;
    DWORD pID = GetCurrentProcessId();
    HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pID); //프로세스 리스트를 뽑아낼 준비를 한다.

    pe.dwSize = sizeof(PROCESSENTRY32);
    Process32First(hSnapShot, &pe); // 프로세스 검색 시작

    printf("Process List: \n");

    while(Process32Next(hSnapShot, &pe))
    { // 프로세스 검색
        HANDLE k = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);

        if (k != 0)
            printf("    [%d]%s\n", pe.th32ProcessID, pe.szExeFile);

        CloseHandle(k);

    }

    CloseHandle(hSnapShot); // 프로세스 검색 끝


    DWORD nFind;
    DWORD nSize;

    printf("Select Process(Id): ");
    scanf("%d", &pID);

    printf("Value to Find: ");
    scanf("%d", &nFind);

    printf("Size of Value: ");
    scanf("%d", &nSize);

    HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID);
    DWORD *p = FindMem(h, nFind, nSize);

    for (int i=0; i<200; i++)
       printf("%d: 0x%X\n", i+1, p[i]);

    printf("And %d Addresses more..\n", _msize(p)-200);

    free(p); //delete p;
}


이걸로 끝입니다~
응용은 알아서들 하세요~ [.........