Click here to Skip to main content
Licence 
First Posted 19 May 2005
Views 68,929
Bookmarked 29 times

Navigating the PEB

By | 19 May 2005 | Article
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

About the Author

DavidCrow

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

Member


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


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
AnswerOvercoming ERROR_PARTIAL_COPY Pinmemberi.Wahn6:28 5 May '10  
GeneralEnvironment variable of a process other than the current process Pinmembercsavie22:25 28 Jun '07  
QuestiongetModuleHandle? PinmemberMaliq Epstein4:20 21 Nov '06  
AnswerRe: getModuleHandle? PinmemberDavidCrow5:22 21 Nov '06  
QuestionAnyone would tell me? PinmemberJoshdai21:23 5 Mar '06  
AnswerRe: Anyone would tell me? PinmemberDavidCrow2:31 6 Mar '06  
GeneralRe: Anyone would tell me? PinmemberJoshdai4:18 6 Mar '06  
GeneralRe: Anyone would tell me? PinmemberDavidCrow5:51 6 Mar '06  
GeneralRe: Anyone would tell me? PinmemberRaymond Menard16:35 20 Mar '06  
GeneralRe: Anyone would tell me? PinmemberDavidCrow4:02 27 Mar '06  
GeneralThanks a lot..Great work..! PinmemberNagareshwar0:14 7 Sep '05  
GeneralPPEB_LDR_DATA ... PinmemberOrkblutt11:17 19 Jul '05  
GeneralRe: PPEB_LDR_DATA ... PinmemberDavidCrow8:56 25 Jul '05  
GeneralRe: PPEB_LDR_DATA ... PinmemberOrkblutt10:19 25 Jul '05  
Generalactual PEB structure Pinmemberakcom4:11 21 May '05  
GeneralRe: actual PEB structure PinmemberDavidCrow1:58 23 May '05  
GeneralNot bad Pinmemberrocky_pulley16:37 19 May '05  
GeneralRe: Not bad PinmemberAlexms10:43 24 May '05  
GeneralQuickView PinmemberToby Opferman13:29 19 May '05  
GeneralRe: QuickView PinmemberToby Opferman13:44 19 May '05  
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 PinmemberDavidCrow4:52 20 May '05  
GeneralRe: QuickView PinmemberToby Opferman11:01 20 May '05  
GeneralRe: QuickView PinmemberDavidCrow2:10 23 May '05  
GeneralRe: QuickView PinmemberToby Opferman16:39 23 May '05  
GeneralRe: QuickView PinmemberToby Opferman16:44 23 May '05  

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

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120528.1 | Last Updated 19 May 2005
Article Copyright 2005 by DavidCrow
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid