Click here to Skip to main content
15,886,919 members
Articles / Desktop Programming / MFC
Article

Navigating the PEB

Rate me:
Please Sign up or sign in to vote.
4.72/5 (13 votes)
19 May 20054 min read 121.9K   1.1K   35   28
Obtaining another process' command-line arguments.

Introduction

This article is a brief, and somewhat rehashed, introduction to the steps involved in obtaining the command-line arguments of a process other than the current process. The two primary functions involved are OpenProcess() and ReadProcessMemory(). Another function that is used, although not required, is NtQueryInformationProcess().

When I first looked at this problem, I thought that I could just run the GetCommandLine() function in the target process using CreateRemoteThread(). That turned out to be a bit too involved.

Getting the list of processes

There are several ways of getting the list of running processes. One is via the Process32First()/Process32Next() pair. The other is with EnumProcesses() followed by GetModuleFileNameEx() to get the path of the first module in the process which is usually the executable. For my example, I'll use the former.

HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE != hProcessSnapshot)
{
    PROCESSENTRY32 ProcessEntry = {0};
    ProcessEntry.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hProcessSnapshot, &ProcessEntry) != FALSE)
    {
        do
        {
            // keep track of ProcessEntry.th32ProcessID here for later
        } while (Process32Next(hProcessSnapshot, &ProcessEntry) != FALSE);
    }

    CloseHandle(hProcessSnapshot);
}

Getting the PEB's starting address

Most of the literature that I read indicated that the starting address of the PEB was always located at memory address 0x7ffdf000. I did find one reference that indicated it to be a random address for Windows XP SP2. I did a very brief experiment on such a machine and found that the address was still located at 0x7ffdf000. That said, I went ahead and accounted for both by defaulting to 0x7ffdf000 but then possibly overriding that by calling NtQueryInformationProcess() like:

typedef LONG (WINAPI NTQIP)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
NTQIP *lpfnNtQueryInformationProcess;

PROCESS_BASIC_INFORMATION pbi;
pbi.PebBaseAddress = (_PEB *) 0x7ffdf000;

DWORD dwSize;

HMODULE hLibrary = GetModuleHandle(_T("ntdll.dll"));
if (NULL != hLibrary)
{
    lpfnNtQueryInformationProcess = (NTQIP *) GetProcAddress(hLibrary, 
                                         "NtQueryInformationProcess");
    if (NULL != lpfnNtQueryInformationProcess)
        (*lpfnNtQueryInformationProcess)(hProcess, 
             ProcessBasicInformation, &pbi, sizeof(pbi), &dwSize);
}

I found that you could also use ZwQueryInformationProcess() as it has the same signature. With the starting address known, we can now read the PEB. One question that should have popped into your head is how we can read each process' PEB by specifying the same address. I'm going to hazard a guess and say that the magic of this lies in the depths of ReadProcessMemory(). Given that it takes a handle to the process and the address of the PEB, it must internally map that virtual address into a physical address before doing the actual reading.

Enabling the Debug access privilege

I learned some interesting and useful information from this project regarding privileges. While a particular privilege might be added to a user or group account, that does not necessarily mean that the privilege has been enabled. On my development machine, I am a member of the Administrators group, thus I have lots and lots of privileges. One of these is the Debug privilege (i.e., SeDebugPrivilege). Consequently I did not run into any access-related issues when trying to open a process or read a process' memory. It was not until Toby Opferman brought to my attention that the Debug privilege should be enabled.

To explore this further, I went to my other development machine and tried the sample project while logged in using the local Guest account. Sure enough, when trying to open several of the processes, I was presented with an error 5 (access denied). To remedy this, I simply added the local Group account to the Debug policy. At this point, the Debug privilege has been added to and enabled for the Guest account. I'm not sure what would disable this privilege under normal circumstances, but to account for that situation, I used the following:

HANDLE hToken;
TOKEN_PRIVILEGES tokenPriv;
LUID luidDebug;

if (OpenProcessToken(GetCurrentProcess(), 
    TOKEN_ADJUST_PRIVILEGES, &hToken) != FALSE)
{
    if (LookupPrivilegeValue(_T(""), SE_DEBUG_NAME, 
                              &luidDebug) != FALSE)
    {
        tokenPriv.PrivilegeCount           = 1;
        tokenPriv.Privileges[0].Luid       = luidDebug;
        tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        AdjustTokenPrivileges(hToken, FALSE, 
                &tokenPriv, sizeof(tokenPriv), NULL, NULL);
    }
}

Navigating the PEB

This figure indicates that we must read the first 20 bytes of the PEB to get the address of the process' parameter information block. This is done with:

struct __PEB
{
    DWORD   dwFiller[4];
    DWORD   dwInfoBlockAddress;
} PEB;

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
                              PROCESS_VM_READ, FALSE, dwProcessID);

ReadProcessMemory(hProcess, pbi.PebBaseAddress, &PEB, sizeof(PEB), &dwSize);

Although probably insignificant, I found that the address of the parameter information block was at address 0x20000 for all but some of the "special" system processes. This figure indicates that we must read the first 72 bytes of this block to get the address of the buffer that contains the command-line arguments. This is done with:

struct __INFOBLOCK
{
    DWORD   dwFiller[16];
    WORD    wLength;
    WORD    wMaxLength;
    DWORD   dwCmdLineAddress;
} Block;

ReadProcessMemory(hProcess, (LPVOID) PEB.dwInfoBlockAddress, 
                             &Block, sizeof(Block), &dwSize);

At this point, we know the address of the buffer containing the command-line arguments as well as the length of that buffer. Since the buffer is Unicode, we'll need to use a wide character type to hold the contents. We can allocate memory on the stack and set its size big enough (1K would probably suffice), or we can allocate memory on the heap by using the value contained in the length field preceding the buffer. I found that the difference between the two length fields was consistently 2 bytes. That would account for the '\0' character.

TCHAR *pszCmdLine = new TCHAR[Block.wMaxLength];

ReadProcessMemory(hProcess, (LPVOID) Block.dwCmdLineAddress, 
                  pszCmdLine, Block.wMaxLength, &dwSize);

Guess what? We now have the process' command-line arguments tucked nicely away in the pszCmdLine variable.

Don't forget to free up the memory when you are done with it.

Alternative approach

Almost halfway through this article, Christophe indicates that there are three ways of obtaining a process' command-line arguments. I dismissed the third option of capturing the output from Tlist because it just feels so cheesy. The second option seemed to be fairly straightforward but I am not familiar enough with all of the possible ramifications (e.g., authority) that could arise from injecting code into another's address space. Thus I opted to read through the PEB to get at the desired information.

I did find, however, that I could not access some of the processes running on my machine. Two errors that I frequently saw were ERROR_PARTIAL_COPY and ERROR_INVALID_PARAMETER. Getting access to and debugging a process is out of scope and not the intent of this article. I leave the task of accounting for those to the interested reader.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Pinnacle Business Systems
United States United States

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.

HTTP 404 - File not found
Internet Information Services

Comments and Discussions

 
AnswerOvercoming ERROR_PARTIAL_COPY Pin
i.Wahn5-May-10 6:28
i.Wahn5-May-10 6:28 
GeneralEnvironment variable of a process other than the current process Pin
csavie28-Jun-07 22:25
csavie28-Jun-07 22:25 
QuestiongetModuleHandle? Pin
Maliq Epstein21-Nov-06 4:20
Maliq Epstein21-Nov-06 4:20 
AnswerRe: getModuleHandle? Pin
David Crow21-Nov-06 5:22
David Crow21-Nov-06 5:22 
QuestionAnyone would tell me? Pin
Joshdai5-Mar-06 21:23
Joshdai5-Mar-06 21:23 
AnswerRe: Anyone would tell me? Pin
David Crow6-Mar-06 2:31
David Crow6-Mar-06 2:31 
GeneralRe: Anyone would tell me? Pin
Joshdai6-Mar-06 4:18
Joshdai6-Mar-06 4:18 
GeneralRe: Anyone would tell me? Pin
David Crow6-Mar-06 5:51
David Crow6-Mar-06 5:51 
GeneralRe: Anyone would tell me? Pin
Raymond Menard20-Mar-06 16:35
Raymond Menard20-Mar-06 16:35 
GeneralRe: Anyone would tell me? Pin
David Crow27-Mar-06 4:02
David Crow27-Mar-06 4:02 
GeneralThanks a lot..Great work..! Pin
Nagareshwar7-Sep-05 0:14
Nagareshwar7-Sep-05 0:14 
GeneralPPEB_LDR_DATA ... Pin
Orkblutt19-Jul-05 11:17
Orkblutt19-Jul-05 11:17 
GeneralRe: PPEB_LDR_DATA ... Pin
David Crow25-Jul-05 8:56
David Crow25-Jul-05 8:56 
GeneralRe: PPEB_LDR_DATA ... Pin
Orkblutt25-Jul-05 10:19
Orkblutt25-Jul-05 10:19 
Generalactual PEB structure Pin
akcom21-May-05 4:11
akcom21-May-05 4:11 
GeneralRe: actual PEB structure Pin
David Crow23-May-05 1:58
David Crow23-May-05 1:58 
GeneralNot bad Pin
rocky_pulley19-May-05 16:37
rocky_pulley19-May-05 16:37 
GeneralRe: Not bad Pin
Alexms24-May-05 10:43
Alexms24-May-05 10:43 
GeneralQuickView Pin
Toby Opferman19-May-05 13:29
Toby Opferman19-May-05 13:29 
GeneralRe: QuickView Pin
Toby Opferman19-May-05 13:44
Toby Opferman19-May-05 13:44 
Actually I downloaded this project to see. Yes you don't set the correct privledges in order to be able to open a handle with these specific rights on some processes. (The following debug code from your application).

Breakpoint 2 hit
eax=00000000 ebx=00000001 ecx=7c90fb71 edx=00000015 esi=0012fe14 edi=77d4b8ba
eip=00401428 esp=0012f5d0 ebp=0012f660 iopl=0 nv up ei pl zr na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
CmdLineTest+0x1428:
00401428 8bf0 mov esi,eax
0:000> !gle
LastErrorValue: (Win32) 0x5 (5) - Access is denied.
LastStatusValue: (NTSTATUS) 0xc0000022 - {Access Denied} A process has requeste
d access to an object, but has not been granted those access rights.
0:000>

I do set the privledge information in Quick View and if you are interested here is the code.


pszDebugPriv db "SeDebugPrivilege", 0


LEA EAX, [ghToken]
PUSH EAX
PUSH TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY
CALL GetCurrentProcess
PUSH EAX
CALL OpenProcessToken

TEST EAX, EAX
JZ SHORT @TsysHelp_SkipThisShit ; Cannot Get Process Token, f*** it.

PUSH 1
PUSH OFFSET pszDebugPriv
PUSH [ghToken]
CALL SetPriv_SetPrivilege ; Setting Debug Privleges.


@TsysHelp_SkipThisShit:



;
; The Set Privledge Function
;
SetPriv_SetPrivilege PROC hToken:DWORD, pszPriv:DWORD, bEnable:DWORD
ASSUME FS:nothing
LOCAL iluid :LUID
LOCAL tokenp :TOKEN_PRIVILEGES
LOCAL tpPrevious :TOKEN_PRIVILEGES
LOCAL cbPrevious :DWORD

LEA EAX, [iluid]

PUSH EAX
PUSH [pszPriv]
PUSH 0
CALL LookupPrivilegeValue

TEST EAX, EAX
JZ @SetPriv_Exit

MOV [tokenp.PrivilegeCount], 1

LEA EDI, [tokenp.Privileges.Luid]
LEA ESI, [iluid]
MOV ECX, size LUID

REP MOVSB

MOV [tokenp.Privileges.Attributes], 0

MOV [cbPrevious], size TOKEN_PRIVILEGES

LEA EAX, [cbPrevious]
PUSH EAX
LEA EAX, [tpPrevious]
PUSH EAX
PUSH size TOKEN_PRIVILEGES
LEA EAX, [tokenp]
PUSH EAX
PUSH 0
PUSH [hToken]
CALL AdjustTokenPrivileges

MOV EAX, FS:[34h]

TEST EAX, EAX
MOV EAX, 0 ; MOV does not set flags
JNZ @SetPriv_Exit

MOV [tpPrevious.PrivilegeCount], 1
LEA EDI, [tpPrevious.Privileges.Luid]
LEA ESI, [iluid]
MOV ECX, size LUID

REP MOVSB

MOV EAX, [bEnable]
TEST EAX, EAX
JZ @SetPriv_Disable

@SetPriv_Enable:
MOV EAX, SE_PRIVILEGE_ENABLED
OR [tpPrevious.Privileges.Attributes], EAX
JMP @SetPriv_SetThem

@SetPriv_Disable:
MOV EAX, [tpPrevious.Privileges.Attributes]
AND EAX, SE_PRIVILEGE_ENABLED
XOR [tpPrevious.Privileges.Attributes], EAX

@SetPriv_SetThem:
PUSH 0
PUSH 0
PUSH [cbPrevious]
LEA EAX, [tpPrevious]
PUSH EAX
PUSH 0
PUSH [hToken]
CALL AdjustTokenPrivileges

MOV EAX, FS:[34h]
TEST EAX, EAX
MOV EAX, 0 ; MOV does not set flags
JNZ @SetPriv_Exit

MOV EAX, 1

@SetPriv_Exit:
RET
SetPriv_SetPrivilege ENDP




GeneralRe: QuickView Pin
David Crow20-May-05 4:52
David Crow20-May-05 4:52 
GeneralRe: QuickView Pin
Toby Opferman20-May-05 11:01
Toby Opferman20-May-05 11:01 
GeneralRe: QuickView Pin
David Crow23-May-05 2:10
David Crow23-May-05 2:10 
GeneralRe: QuickView Pin
Toby Opferman23-May-05 16:39
Toby Opferman23-May-05 16:39 
GeneralRe: QuickView Pin
Toby Opferman23-May-05 16:44
Toby Opferman23-May-05 16:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.