Log in

View Full Version : Getting ReadProcessMemory to work


Eddie
July 20th, 2005, 08:36
Ok, I'm a newb in both using API calls and reverse engineering who is attempting to build his own patch. What I'm trying to do is a program to read a string from another program. This has to be really easy and I already got the memory address of the string.

The hardest part for me is that I use Ada to write my programs, which is a great language until you have to interact with other things such the API calls. It is very strict on variable types and it can be really hard to convert from one type to another if you don't have the functions to do so, but well, I thing I got over this.

I based my first testing program in this guide: http://www.woodmann.com/fravia/natz_mp2.htm

And simplifying this is what I do:

- Calling FindWindow to get the window handle from the program which I want to read from. Works fine.

- Getting the process ID with the GetWindowThreadProcessId function. Works fine.

- Open a thread to that process using the OpenProcess function. Either by using the PROCESS_VM_READ or PROCESS_ALL_ACCESS flags it works fine.

- Using the ReadProcessMemory with the following parameters:

hProcess: The handle provided by the previous function.
lpBaseAddress: 16#0C38DF8C# The Hex value that points at the start of the string.
lpBuffer: String which I'm saving the returning value in.
nSize: 8 bytes. The string is longer, but I just want to get this to work atm.
lpNumberOfBytesRead: Null. I don't really need to know how many bytes I am reading atm because the function is not even working for me yet.

Guess what I get... It returns the value 0! Ok, then I try to use the GetLastError and FormatMessage functions attempting to have an idea of what is not working. What I get in my win would mean "Invalid controller", I don't know the exact error message in english as I'm not running an english version of windows. I don't remember the error code but I think it was 6.

I think I said almost everything... Oh, I tried this only in Win XP Home with admin rights.

Thanks a lot to whoever who could help me.

Regards~

disavowed
July 20th, 2005, 08:42
lpNumberOfBytesRead can't be null. It has to be a pointer to a writable DWORD.

Eddie
July 20th, 2005, 09:15
Really? >,<
I didn't understand that from msdn:

Parameters

hProcess
[in] A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process.
lpBaseAddress
[in] A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the system verifies that all data in the base address and memory of the specified size is accessible for read access, and if it is not accessible the function fails.
lpBuffer
[out] A pointer to a buffer that receives the contents from the address space of the specified process.
nSize
[in] The number of bytes to be read from the specified process.
lpNumberOfBytesRead
[out] A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead is NULL, the parameter is ignored.


I'll give it a try. Thanks for the reply.

blabberer
July 20th, 2005, 11:47
no need it can be null
Code:

ReadProcessMemory(debproc,status,&dlah,4,NULL);

works for me
probably your # blah is throwing some errors
is it raw value ??
i mean how did you get it if it is raw value from file you need to convert it to
virtual offset
other than that try using virtualquery() and based on result try adding virtualprotect(page_read) before calling Readprocessmemory

Code:

some thing like this

pid = Plugingetvalue(VAL_PROCESSID);
debproc = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
ntdll_NtQueryInformationProcess(debproc,ProcessBasicInformation,&pbi,sizeof(pbi),NULL);
status = pbi.PebBaseAddress;
(byte *)status += 0x68;
VirtualQueryEx(debproc,status,&mbi,sizeof(mbi));
VirtualProtectEx(debproc,mbi.BaseAddress,mbi.RegionSize,PAGE_EXECUTE_READWRITE,&lpOld);
ReadProcessMemory(debproc,status,&dlah,4,NULL);
elah = (long)dlah;
elah |= 0x02;
WriteProcessMemory(debproc,status,&elah,4,NULL);

Eddie
July 20th, 2005, 17:21
I think I'll have to forget about using Ada on doing this and get a C++ developing envoirment... I'm tired of type conversions.

Theorically I have read access to the page, but I don't get it to work... >,<

nikolatesla20
July 20th, 2005, 20:18
Hi Eddie, the only thing I could think of is that lpString that you are passing. This must be a memory address..it could be that the string buffer you are passing is malfunctioning. Can't you walk thru the debugger and see what the value is right before and right after the readprocessmemory call?

Ada is kind of like Python in the sense that everything is an object I believe...so a "pointer to a string" might have to be handled a special way. VB is the same way, it does strings stuff behind the scenes. For example, the string's length is actually the first characters in the string in VB. But VB sees this when you call API's and gives the correct address (2 bytes into the string). Maybe Ada is doing something similar to you.

-niko

Eddie
July 21st, 2005, 15:12
Hi niko,
My head hurts because of not getting this to work...

Here is an example of how I define the buffer and its pointer. Ada is very transparent in its way of working, it doesn't have hidden characters in the strings, so you have to add the markers for yourself .

Code:

type buffer_type is new string(1..8);
type buffer_pointer_type is access all buffer_type;
Buff: aliased buffer_type;
Buff_pointer: constant buffer_pointer_type:= Buff'access;

function buffer_pointer_type_to_address is new
Ada.unchecked_conversion
(source => buffer_pointer_type,
target => System.Address);


I checked if the buffer pointer address value was correct with the debugger and it was, so the pointers that I'm using really contain the memory addresses...

I'm messed up...

Thanks to everyone for your help.

blabberer
July 23rd, 2005, 03:27
does it produce 32 bit pe executable loadable in ollydbg if yes post a prototype non working compiled binary here let me take a look at it

Admiral
July 23rd, 2005, 10:40
Things that come to mind (including some reiterations of other posts):

I'm fairly sure lpNumberOfBytesRead can be NULL. I had it working so yesterday. However, VirtualProtectEx's equivalent parameter can't be NULL. Perhaps that's where the confusion comes in.

You'll want to make sure you send the base address by value. I know Visual Basic tends to create a new variable equal to that base address and passes it by reference. Hence in order to get the desired stack construction, it requires an explicit ByVal in the function call:

ReadProcessMemory(hProcess, ByVal &H77D40000, ByVal Buffer, 7, NumBytes)
as opposed to
ReadProcessMemory(hProcess, &H77D40000, ByVal Buffer, 7, NumBytes)

It's quite possible that Ada uses its own array and string classes, which may clash with the lpAddress required by ReadProcessMemory. Try using a byte array (passing the first entry by reference) first. If that doesn't work, see if passing a string is any better. Of course, the whole process will be easier to oversee if you monitor the stack at the point of the call. If you have OllyDbg, use it. If you don't, get it.

Good luck
Admiral

Eddie
July 23rd, 2005, 20:59
First of all, thanks to everyone for your replies, I really appreciate it.

I tried to do this in C# but I never used anything but Ada before so it took me a while to realize how to do some things and some other things should be done better, but well, I think I got close to my Ada version, the funny thing is that I'm getting the same error: #6: Invalid controller.

Ok, so if you are willing to help (thanks a lot again) I'll post this file, Read.zip.

It contains the following:

readme.txt - Exactly what I'm writing here.
ReadAda/read.htm - The formatted sourcecode written in Ada (highly comented to be easy to understand even not familiarized with the language). Html format.
ReadAda/read.rtf - The formatted sourcecode written in Ada (highly comented to be easy to understand even not familiarized with the language). Rich text format.
ReadAda/read.exe - The executable binary file compiled with the Ada GNAT compiler.
ReadAda/read.adb - The original sourcecode file(highly comented to be easy to understand even not familiarized with the language).
ReadAdaUncomented/read.htm - The formatted sourcecode written in Ada (not comented to have a clean version). Html format.
ReadAdaUncomented/read.rtf - The formatted sourcecode written in Ada (not comented to have a clean version). Rich text format.
ReadAdaUncomented/read.exe - The executable binary file compiled with the Ada GNAT compiler.
ReadAdaUncomented/read.adb - The original sourcecode file (not comented to have a clean version).
ReadC#/Read.htm - The formatted sourcecode written in C#. Html format.
ReadC#/Read.rtf - The formatted sourcecode written in C#. Rich text format.
ReadC#/Read.exe - The executable binary file compiled with the Microsoft Visual C# compiler.
ReadC#/Program.cs - The original sourcecode file.


Notes:
The coment flag in Ada is --, in C# is comonly //.
Ada is not case sensitive, C# is.
The executables built with the Ada compiler are large, I don't really know why but I think that it is because it adds lots of error handling functions.
You may need Microsoft .NET Framework to run the C# executable.


Should I learn assembly language? >_<

Kind regards,

Eddie~

blabberer
July 24th, 2005, 07:47
Code:

0244FA7C 00260262 |hWnd = 00260262 ('Iczelion's tutorial no.2',class='#32770')
0244FA80 00000000 \pProcessID = NULL <-- you should probably fill this with an address and try

as from help

it says

pdwProcessId

Points to a 32-bit value that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId copies the identifier of the process to the 32-bit value; otherwise, it does not.

Code:

the return value for me atm is is 588

Unnamed window, item 6
Process=00000584
Name=msgbox
Window=Iczelion's tutorial no.2
Path=D:\borland\tut02\msgbox.exe


D:\borland\windbg>tlist -p msgbox.exe
1412 == 584 not 588





tlist -1416 retns no info
its neither on takmanger
not on tlsit dunno from where it returns this value
probably some thread identifier

may be its because i was running this one some too much crapped non admin account dunno have to investigate

or probably this function is crap
may be it returns explorers pid saying explorers created it

as the return values help is kinda ambigous to understand for me at the moment
from help
The return value is the identifier of the thread that created the window.
lets look at it in detail now to find what the heck is 588

ok its the thread identifier not process identifier

take a look at tlist -v
Code:


NumberOfThreads: 1
1416 Win32StartAddr:0x00401000 LastErr:0x00000000 State:Waiting


D:\borland\windbg>tlist 1412
1412 msgbox.exe Iczelion's tutorial no.2
CWD: D:\borland\tut02\
CmdLine: "D:\borland\tut02\msgbox.exe"
VirtualSize: 17596 KB PeakVirtualSize: 17596 KB
WorkingSetSize: 1852 KB PeakWorkingSetSize: 1852 KB
NumberOfThreads: 1
1416 Win32StartAddr:0x00401000 LastErr:0x00000000 State:Waiting
0x00400000 D:\borland\tut02\msgbox.exe
5.0.2195.6899 shp 0x77F80000 C:\WINNT\system32\ntdll.dll
5.0.2195.6946 shp 0x7C570000 C:\WINNT\system32\KERNEL32.dll
5.0.2195.7032 shp 0x77E10000 C:\WINNT\system32\USER32.dll
5.0.2195.6945 shp 0x77F40000 C:\WINNT\system32\GDI32.dll
5.1.2409.7 shp 0x60000000 C:\WINNT\system32\MSCTF.dll
5.0.2195.6876 shp 0x7C2D0000 C:\WINNT\system32\ADVAPI32.dll
5.0.2195.6904 shp 0x77D30000 C:\WINNT\system32\RPCRT4.DLL
2.40.4522.0 shp 0x779B0000 C:\WINNT\system32\OLEAUT32.DLL
5.0.2195.7021 shp 0x77A50000 C:\WINNT\system32\ole32.dll
9.50.0.0 shp 0x10000000 C:\PROGRA~1\Logitech\MOUSEW~1\SYSTEM\LgMousHk.d
ll


ok one doubt out of way to get pid you should probably provide an address

ok ill mcgyver some thing without recompiling

Code:

0244FA7C 00260262 |hWnd = 00260262 ('Iczelion's tutorial no.2',class='#32770')
0244FA80 00424E90 \pProcessID = read.00424E90

ok that works

00424E90 84 05 00 00 „..

see 584 there


ok spleunking continues

Code:

your original open process
0244FA78 001F0FFF |Access = PROCESS_ALL_ACCESS
0244FA7C 00000000 |Inheritable = FALSE
0244FA80 00000588 \ProcessId = 588

spleunked

0244FA78 001F0FFF |Access = PROCESS_ALL_ACCESS
0244FA7C 00000000 |Inheritable = FALSE
0244FA80 00000584 \ProcessId = 584


Write the window name from the program to identify and press ENTER:
Iczelion's tutorial no.2
The window "Iczelion's tutorial no.2" was found.
Access to the process granted.

as i said in the first post your address is probably wrong
atleast it is wrong for the window i am using

Code:

|hProcess = 00000048
|pBaseAddress = 18D20E0 <----- what the heck is this from where did you get it did you verify if it exists are you sure about this address ??????????????
|Buffer = 0244FDC4
|BytesToRead = 6
\pBytesRead = NULL


lets just press on without spleuking and see what happenss


opps it errs
ERROR_PARTIAL_COPY (0000012B)
Code:


ok restart and spleunk

|hProcess = 00000048
|pBaseAddress = 403000
|Buffer = 0244FDC4
|BytesToRead = 6
\pBytesRead = NULL


before execution
0244FDC4 00 00 00 00 21 21 F8 77 24 00 00 00 FC FD 44 02 ....!!øw$...üýD

after execution
0244FDC4 49 63 7A 65 6C 69 F8 77 24 00 00 00 FC FD 44 02 Iczeliøw$...üýD


do you see it has read all the 6 bytes "Iczeli"

not satisified ok we will fetch a big string

now spleunked to fetch 40 bytes
Code:

|hProcess = 00000048
|pBaseAddress = 403000
|Buffer = 0244FDC4
|BytesToRead = 40 (64.)
\pBytesRead = NULL


before execution

0244FDC4 00 00 00 00 21 21 F8 77 24 00 00 00 FC FD 44 02 ....!!øw$...üýD
0244FDD4 FC FD 44 02 24 00 00 00 07 00 00 00 00 00 00 00 üýD$..........
0244FDE4 9C FE 44 02 4D AB 5A 7C 00 00 00 00 00 00 00 00 œþDM«Z|........
0244FDF4 23 02 02 00 0C 00 00 00 1C 00 34 00 02 00 00 00 #......4....

after execution

0244FDC4 49 63 7A 65 6C 69 6F 6E 27 73 20 74 75 74 6F 72 Iczelion's tutor
0244FDD4 69 61 6C 20 6E 6F 2E 32 00 57 69 6E 33 32 20 41 ial no.2.Win32 A
0244FDE4 73 73 65 6D 62 6C 79 20 69 73 20 47 72 65 61 74 ssembly is Great
0244FDF4 21 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 !...............

hoooooray pat myself on my back

opps my paste some how didnt get th final result
Quote:

Buffer address: 16#244FDC4#
Buffer value: 0 0 0 0 33 33
Target address: 16#18D20E0#
Write the window name from the program to identify and press ENTER:
Iczelion's tutorial no.2
The window "Iczelion's tutorial no.2" was found.
Access to the process granted.
The process memory was read successfuly.
Buffer address: 16#244FDC4#
Buffer value: 73 99 122 101 108 105
Target address: 16#18D20E0#
Program finished. Press any key to exit.

Eddie
July 24th, 2005, 12:48
OMFG
Thank you!
Thank you!!
THANK YOU!!!

I realized what I did wrong and also realized how ignorant I am in API calls... I didn't realize by myself that the problem wasn't in ReadProcessMemory, it was in the previous functions...

Quote:
0244FA80 00000000 \pProcessID = NULL <-- you should probably fill this with an address and try


That was it... the two null value in GetWindowThreadProcessId and a wrong parameter in OpenProcess... I was calling it like this...

Code:
AccessHandle := Win32.Winbase.OpenProcess(
Win32.Winnt.PROCESS_ALL_ACCESS,
Win32.FALSE,
ProcHandle);


Where ProcHandle was the value returned by GetWindowThreadProcessId and I should had used the ProcID provided also in that function...

I'm feeling silly because I don't get what is different from the dwProcessId and the process handle that GetWindowThreadProcessId returns.

Sorry for having you wasting your time >_<

Thank you again!



Regards,

Eddie~

Kayaker
July 24th, 2005, 22:11
See, that's what's great about this community. If someone asks a reasonable question in a reasonable manner and shows they're doing the work, not just looking for a crack request, people fall all over themselves to try to help. That's the way it should be. It seems like a simple equation...

I don't think anyone wasted their time Eddie, these are the kind of detailed posts people can learn from. Nice to see.

Cheers,
Kayaker

blabberer
July 25th, 2005, 05:25
well it wasnt time wasted i ve never used the
GetWindowProcess whatever api so i wont forget how to use it if ever i need to use it now

any way i suggest you to get ollydbg and play with it debugging the program
any program it just is an amazingly wonderful tool and saves you lot of troubles like this

any way for me it was just few clicks to find this out
ctrl+n on the module
set log breakpoint log arguments to always on all those apis from findWindowOnwards till ReadProcessMemory and just looked through them to
understand it may be took three minutes flat

Code:


Log data
Address Message
0040B133 CALL to FindWindowA
Class = 0
Title = "Iczelion's tutorial no.2"
0040EAE0 CALL to FindWindowA from read.0040B133
Class = 0
Title = "Iczelion's tutorial no.2"
0040B32D CALL to GetWindowThreadProcessId
hWnd = 000804B0 ('Iczelion's tutorial no.2',class='#32770')
pProcessID = NULL
0040EAA8 CALL to GetWindowThreadProcessId from read.0040B32D
hWnd = 000804B0 ('Iczelion's tutorial no.2',class='#32770')
pProcessID = NULL
0040B348 CALL to OpenProcess
Access = PROCESS_ALL_ACCESS
Inheritable = FALSE
ProcessId = 3BC <--- when see this just hit file attach and see whats the actual pid and does it match nope so investigate why ??
004239AC CALL to OpenProcess from read.0040B348
Access = PROCESS_ALL_ACCESS
Inheritable = FALSE
ProcessId = 3BC open process has all required params so why did it fail ?????
0040B3B0 CALL to ReadProcessMemory
hProcess = NULL
pBaseAddress = 18D20E0
Buffer = 0244FDC4
BytesToRead = 6
pBytesRead = NULL
004239B4 CALL to ReadProcessMemory from read.0040B3B0
hProcess = NULL <--- cannot be null so open process failed
pBaseAddress = 18D20E0
Buffer = 0244FDC4
BytesToRead = 6
pBytesRead = NULL



writing it up in step by step actually took more than 30 minutes

thats it

laola
July 26th, 2005, 11:23
Just adding my two cents after the show: Error code 6 means "invalid handle". Why it was invalid has been explained above.

BTW, you don't have to use C#, an older Visual C++ 6 will be sufficient for almost any case. Just make sure to apply the latest processor pack and service pack, otherwise you may end up in funny messes

For doing small experiments, you can just do the following:

Fire up Visual C++ (Visual Studio, whatever), use the wizard to create a new project. Select the MFC category, and select creating a new MFC application. Configure it to be dialog-based and leave all the other settings alone. The wizard will now create a new application project which will compile fine, involving 2 classes (CMyProg and CMyProgDlg). Open up the resource view, select the pre-created dialog and double-click the OK button. The class wizard will create a new member function for you which will execute when you push the OK button.
Now write your code inside the OnOK-Function that has been created. (Make sure to put all of your code before the Call to ::OnOK, this call ends the application).

Finally, hit F5 to compile and run. Default will be debug mode so you can step through your code easily and see what's happening.

For lazy people like me (otherwise I'd just write up everything in asm), this is a very nice and easy testbed

If you have any questions about this, just let me know.

Eddie
July 26th, 2005, 17:57
Honestly... I allways have programmed in Ada and I wanted to get a little in touch with C (and from its variants I choosed C# because it would be the most Ada like variant in terms of restriction and redundance).

I didn't like the experience. Why? Because I got used Ada, a very restrictive language (in variable types) and redundant enough that the compiler easily detects every error you have done. In C I found out that it was very easy for the compiler to not notice lots of errors that the Ada compiler would.

Well, everything has advantages and disadvantages, I would have to choose between a powerful language or a language that would make me easier to interact with other things or build a GUI. When I only did things in Ada I though I would like the advantages from C and that I would not lose much, but when I tried it I missed my Ada compiler.

So here I am, using Ada with an outdated Win32 API binding (designed for WinNT 4.0 lol), but that slowly I'm building everything I'm needing to.

Well, I think that the best choice depends of what your needs are, so if you need to work a lot with graphical interfaces in your programs then the best choice is C or Basic because of the microsoft envoirements. If you need a lot things that require reverse enginereeng the best choice may be getting in touch with ASM.

It's funny how the topic from a choice can change...

Kind regards and thanks again for helping me,

Eddie~