For anyone who wants to start playing with memory reading and writing. (copy-pastable - should compile for 32bit and 64. ofc if you compile for 32 bit, don't try to access a 64 bit program- it won't work).

Not much of a tut., but here's a few notes.

1. You MUST be 'Attached' to the process before you can read or write to it's memory. The Attach() function expects a process ID and returns a boolean indicating success/fail.
-Behind the scenes Attach() calls OpenProcess() and tries to obtain a 'unique handle to the process' (which is required for all of the virtual memory functions: ReadProcessMemory, WriteProcessMemory, VirtualQueryEx, VirtualProtectEx, etc). You must get a handle from OpenProcess() before you can read/modify a process's memory.

2. Detach() will automatically be called when the MemoryManager object is disposed (thnx IDisposable). Not very important, but could be.

3. Most Importantly - You must already understand the vb.net language, and have a basic grasp on virtual memory.

usage:
Code:
Dim _targetID As Int32 = ****
Dim _memMgr As New MemoryManager()
..

If _memMgr.AttachToProcess(_targetID) Then
  MessageBox.Show("Attach Success!")
  ''After this point we can read/write
  Else
  MessageBox.Show("Attach Fail. Access Denied?")
End If
usage:
Code:
Dim _addr As IntPtr = New IntPtr(&H12345678)

If _memMgr.IsAttached() Then
  Dim xx as Int16 = _memMgr.ReadInt16(_addr)
  Dim yy as Int32 = _memMgr.ReadInt32(_addr)
  Dim zz as Int64 = _memMgr.ReadInt64(_addr)
  Dim ff as Single = _memMgr.ReadFloat(_addr)
  Dim str As String = _memMgr.ReadAsciiString(_addr,25)

  _memMgr.WriteInt32(_addr, 1000000) '' write 1,000,000
End If
--------------------------------------------------

a couple ways to find our target process' ID - based on Main Window Title
Code:
        For Each proc As Process In Process.GetProcesses()
            If proc.MainWindowTitle = "Calculator" Then
                ''This is our target process
                'use proc.Id
            End If
        Next
based on Process Name
Code:
        For Each proc As Process In Process.GetProcesses()
            If proc.ProcessName = "calc" Then
                ''This is our target process
                'use proc.Id
            End If
        Next
based on Process Name (again)
Code:
        Dim _procs() As Process = Process.GetProcessesByName("calc")
        If _procs.Length = 0 Then
            ''no results returned. Process not found.
        Else
            '' at least 1 process found.
            ''use _procs(0).Id
        End If
--------------------------------------------------

I broke the code into 2 parts - 1 is a few Windows APIs/structs/enums, and the other is the MemoryManager class.

WinApi class. Feel free to declare all of this inside the MemoryManager class if you'd like.
Code:
Public Class WinApi
#Region "Windows(R) Functions"
    Public Declare Sub GetSystemInfo Lib "kernel32" (ByRef lpSystemInfo As SystemInfo)
    Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As IntPtr) As Boolean
    Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAcess As UInt32, ByVal bInheritHandle As Boolean, ByVal dwProcessId As Int32) As IntPtr
    Public Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpBaseAddress As IntPtr, ByVal lpBuffer() As Byte, ByVal iSize As Int32, ByRef lpNumberOfBytesRead As Integer) As Boolean
    Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpbaseAddress As IntPtr, ByVal lpBuffer As Byte(), ByVal nSize As Int32, ByRef dwNumberOfBytesWritten As Int32) As Boolean

    Public Declare Function VirtualProtectEx Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByVal dwSize As Int32, ByVal dwNewProtect As UInt32, ByRef dwOldProtect As UInt32) As Boolean
    Public Declare Function VirtualQueryEx Lib "kernel32" (ByVal hProcess As IntPtr, ByVal lpAddress As IntPtr, ByRef lpMemoryBasicInformation As MemoryBasicInformation, ByVal dwLength As Int32) As UInt32

#End Region
#Region "Structs"
    
    Public Structure SystemInfo
        Dim wProcessorArchitecture As Int16
        Dim wReserved As Int16
        Dim dwPageSize As Int32
        Dim lpMinimumApplicationAddress As IntPtr
        Dim lpMaximumApplicationAddress As IntPtr
        Dim dwActiveProcessorMask As Int32
        Dim dwNumberOfProcessors As Int32
        Dim dwProcessorType As Int32
        Dim dwAllocationGranularity As Int32
        Dim wProcessorLevel As Int16
        Dim wProcessorRevision As Int16
    End Structure
    Public Structure MemoryBasicInformation
        Dim BaseAddress As IntPtr
        Dim AllocationBase As IntPtr
        Dim AllocationProtect As UInt32
        Dim RegionSize As IntPtr
        Dim State As UInt32
        Dim Protect As UInt32
        Dim AllocationType As UInt32
    End Structure

#End Region
#Region "Enums"

    Public Enum MemoryAllocationProtection As UInt32
        PAGE_NOACCESS = &H1
        PAGE_READONLY = &H2
        PAGE_READWRITE = &H4
        PAGE_WRITECOPY = &H8
        PAGE_EXECUTE = &H10
        PAGE_EXECUTE_READ = &H20
        PAGE_EXECUTE_READWRITE = &H40
        PAGE_EXECUTE_WRITECOPY = &H80
        PAGE_GUARD = &H100
        PAGE_NOCACHE = &H200
        PAGE_WRITECOMBINE = &H400
    End Enum
    Public Enum MemoryAllocationType As UInt32
        MEM_IMAGE = &H1000000
        MEM_MAPPED = &H40000
        MEM_PRIVATE = &H20000
    End Enum
    Public Enum MemoryAllocationState As UInt32
        COMMIT = &H1000
        RESERVE = &H2000
        DECOMMIT = &H4000
        RELEASE = &H8000
        RESET = &H80000
        PHYSICAL = &H400000
        TOP_DOWN = &H100000
        WRITE_WATCH = &H200000
        LARGE_PAGES = &H20000000
    End Enum
    Public Enum ProcessAccess As UInt32
        ''Function descriptions taken from msdn.com
        ''' <summary>
        ''' Required to terminate a process using TerminateProcess.
        ''' </summary>
        PROCESS_TERMINATE = &H1
        ''' <summary>
        ''' Required to create a thread.
        ''' </summary>
        PROCESS_CREATE_THREAD = &H2
        ''' <summary>
        ''' Required to perform an operation on the address space of a process (see VirtualProtectEx and WriteProcessMemory).
        ''' </summary>
        PROCESS_VM_OPERATION = &H8
        ''' <summary>
        ''' Required to read memory in a process using ReadProcessMemory.
        ''' </summary>
        PROCESS_VM_READ = &H10
        ''' <summary>
        ''' Required to write to memory in a process using WriteProcessMemory.
        ''' </summary>
        PROCESS_VM_WRITE = &H20
        ''' <summary>
        ''' Required to duplicate a handle using DuplicateHandle.
        ''' </summary>
        PROCESS_DUP_HANDLE = &H40
        ''' <summary>
        ''' Required to create a process.
        ''' </summary>
        PROCESS_CREATE_PROCESS = &H80
        ''' <summary>
        ''' 	Required to set memory limits using SetProcessWorkingSetSize.
        ''' </summary>
        PROCESS_SET_QUOTA = &H100
        ''' <summary>
        ''' 	Required to set certain information about a process, such as its priority class (see SetPriorityClass).
        ''' </summary>
        PROCESS_SET_INFORMATION = &H200
        ''' <summary>
        ''' Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken).
        ''' </summary>
        PROCESS_QUERY_INFORMATION = &H400
        ''' <summary>
        ''' Required to suspend or resume a process.
        ''' </summary>
        PROCESS_SUSPEND_RESUME = &H800
        ''' <summary>
        ''' Required to retrieve certain information about a process (see GetExitCodeProcess, GetPriorityClass, IsProcessInJob, QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION. Windows Server 2003 and Windows XP:  This access right is not supported.
        ''' </summary>
        PROCESS_QUERY_LIMITED_INFORMATION = &H1000
        ''' <summary>
        ''' Required to wait for the process to terminate using the wait functions.
        ''' </summary>
        PROCESS_SYNCHRONIZE = &H100000
    End Enum

#End Region
End Class
MemoryManager Class
Code:
Public Class MemoryManager
    Implements IDisposable
#Region "Private"
    Private _isAttached As Boolean = False ''required for most non-shared functions.
    Private _targetProcessId As Int32 = 0
    Private _targetProcessHandle As IntPtr = IntPtr.Zero ''Returned from winapi.OpenProcess()
    Private _mainModuleBase As IntPtr = IntPtr.Zero
    Private _systemInfo As WinApi.SystemInfo '' Useful information about cpu and ram.  Stored to avoid excessive lookup.
    Private _mbiSize As Int32 = 0 '' SizeOf(WinAPI.MEMORY_BASIC_INFORMATION) in bytes. Stored to avoid excessive lookup.

    Public Sub New()
        ''Store these values in the class so they don't have to be calculated/looked up each time they are used.
        WinApi.GetSystemInfo(_systemInfo) '' Useful info about cpu and ram.
        _mbiSize = System.Runtime.InteropServices.Marshal.SizeOf(New WinApi.MemoryBasicInformation) ''size of the struct in bytes
    End Sub
#End Region

    Public Function AttachToProcess(ByVal processId As Int32) As Boolean
        If _isAttached Then
            Return False ''Already attached... True would be misleading because processId might not == _targetProcessId. 
        Else
            For Each proc As Process In Process.GetProcesses()
                If proc.Id = processId Then
                    _targetProcessHandle = WinApi.OpenProcess(WinApi.ProcessAccess.PROCESS_VM_WRITE Or WinApi.ProcessAccess.PROCESS_VM_READ Or WinApi.ProcessAccess.PROCESS_VM_OPERATION Or WinApi.ProcessAccess.PROCESS_QUERY_INFORMATION, False, processId)
                    If _targetProcessHandle = IntPtr.Zero Then
                        ''OpenProcess() failed. Problem with processId or OpenAccess level.
                        Return False
                    Else
                        ''OpenProcess() success. Permissions Granted.
                        _isAttached = True
                        _targetProcessId = processId
                        _mainModuleBase = proc.MainModule.BaseAddress
                        Return True
                    End If
                End If
            Next
            ''If falls through, processId not found
            MessageBox.Show("MemoryManager::AttachToProcess() Process Id not found: " & processId.ToString())
            Return False
        End If
    End Function
    Public Sub DetachFromProcess()
        If _isAttached Then
            If _targetProcessHandle <> IntPtr.Zero Then
                WinApi.CloseHandle(_targetProcessHandle) '' we are done with handle. OS can free/re-use it.
            End If
            _isAttached = False
            _targetProcessId = 0
            _targetProcessHandle = IntPtr.Zero
        End If
    End Sub
    Public ReadOnly Property IsAttached() As Boolean
        Get
            Return _isAttached
        End Get
    End Property
    Public ReadOnly Property MainModuleBase As IntPtr
        Get
            Return _mainModuleBase
        End Get
    End Property
#Region "Read"
    Public Function ReadByte(ByVal addr As IntPtr) As Byte
        Dim _byte(0) As Byte '' Awkward. ReadProcessMemory delcared to take a byte array instead of an IntPtr. Both are an address.
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _byte, 1, New Int32)
        Return _byte(0)
    End Function
    Public Function ReadInt16(ByVal addr As IntPtr) As Int16
        Dim _bytes(1) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 2, New Int32)
        Return BitConverter.ToInt16(_bytes, 0)
    End Function
    Public Function ReadInt32(ByVal addr As IntPtr) As Int32
        Dim _bytes(3) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
        Return BitConverter.ToInt32(_bytes, 0)
    End Function
    Public Function ReadInt64(ByVal addr As IntPtr) As Int64
        Dim _bytes(7) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
        Return BitConverter.ToInt64(_bytes, 0)
    End Function
    Public Function ReadUInt16(ByVal addr As IntPtr) As UInt16
        Dim _bytes(1) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 2, New Int32)
        Return BitConverter.ToUInt16(_bytes, 0)
    End Function
    Public Function ReadUInt32(ByVal addr As IntPtr) As UInt32
        Dim _bytes(3) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
        Return BitConverter.ToUInt32(_bytes, 0)
    End Function
    Public Function ReadUInt64(ByVal addr As IntPtr) As UInt64
        Dim _bytes(7) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
        Return BitConverter.ToUInt64(_bytes, 0)
    End Function
    Public Function ReadFloat(ByVal addr As IntPtr) As Single
        Dim _bytes(3) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 4, New Int32)
        Return BitConverter.ToSingle(_bytes, 0)
    End Function
    Public Function ReadDouble(ByVal addr As IntPtr) As Double
        Dim _bytes(7) As Byte
        If _isAttached Then WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, 8, New Int32)
        Return BitConverter.ToDouble(_bytes, 0)
    End Function
    ''' <summary>
    ''' Reads an Ascii string from the target process's ram.
    ''' </summary>
    ''' <param name="addr">Beginning address of the string.</param>
    ''' <param name="maxLength">Max length of string if unknown. Must be > 0</param>
    Public Function ReadAsciiString(ByVal addr As IntPtr, ByVal maxLength As Int32) As String
        If Not (_isAttached AndAlso (maxLength > 0)) Then
            Return String.Empty '' fail. not attached to any process (or maxLength = 0).

        End If
        Dim _bytes(maxLength - 1) As Byte
        Dim _bytesRead As Int32 = 0
        If WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, maxLength, _bytesRead) Then
            Return System.Text.Encoding.ASCII.GetString(_bytes, 0, _bytesRead)
        Else
            Return String.Empty ''rpm failed
        End If
    End Function
    ''' <summary>
    ''' Read a Unicode string from the target process's ram.
    ''' </summary>
    ''' <param name="addr">Beginning address of the string.</param>
    ''' <param name="maxLength">Max length of string if unknown. Must be > 0</param>
    Public Function ReadUnicodeString(ByVal addr As IntPtr, ByVal maxLength As Int32) As String
        If Not (_isAttached AndAlso (maxLength > 0)) Then
            Return String.Empty '' fail. not attached to any process (or maxLength = 0).
        End If
        maxLength = maxLength * 2 '' 2  bytes per character. TODO: "Unicode" ?
        Dim _bytes(maxLength - 1) As Byte
        Dim _bytesRead As Int32 = 0
        If WinApi.ReadProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, _bytesRead) Then
            Return System.Text.Encoding.Unicode.GetString(_bytes, 0, _bytesRead)
        Else
            Return String.Empty '' rpm failed.
        End If
    End Function
    ''' <summary>
    ''' Reads an array of bytes from the target process's ram into a pre-declared buffer.
    ''' </summary>
    ''' <param name="byteBuff">Must be large enough, will not be re-sized!</param>
    ''' <param name="size">Number of bytes to read.</param>
    ''' <param name="actualBytesRead">Byref. Returns the actual number of bytes read.</param>
    Public Function ReadBytes(ByVal addr As IntPtr, ByRef byteBuff() As Byte, ByVal size As Int32, ByRef actualBytesRead As Int32) As Boolean
        Return WinApi.ReadProcessMemory(_targetProcessHandle, addr, byteBuff, size, actualBytesRead)
    End Function
#End Region
#Region "Write"
    Public Function WriteByte(ByVal addr As IntPtr, ByVal aByte As Byte) As Boolean
        Dim _bts() As Byte = {aByte} '' Awkward. Winapi function is declared as array() instead of as IntPtr
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bts, 1, New Int32)
    End Function
    Public Function WriteInt16(ByVal addr As IntPtr, ByVal data As Int16) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, New Int32)
    End Function
    Public Function WriteUInt16(ByVal addr As IntPtr, ByVal data As UInt16) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 2, New Int32)
    End Function
    Public Function WriteInt32(ByVal addr As IntPtr, ByVal data As Int32) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, New Int32)
    End Function
    Public Function WriteInt64(ByVal addr As IntPtr, ByVal data As Int64) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
    End Function
    Public Function WriteUInt64(ByVal addr As IntPtr, ByVal data As UInt64) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
    End Function
    Public Function WriteFloat(ByVal addr As IntPtr, ByVal data As Single) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 4, New Int32)
    End Function
    Public Function WriteDouble(ByVal addr As IntPtr, ByVal data As Double) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, BitConverter.GetBytes(data), 8, New Int32)
    End Function
    Public Function WriteAsciiString(ByVal addr As IntPtr, ByVal str As String, ByRef actualBytesWritten As Int32) As Boolean
        Dim _bytes() As Byte = System.Text.Encoding.ASCII.GetBytes(str)
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, actualBytesWritten)
    End Function
    Public Function WriteUnicodeString(ByVal addr As IntPtr, ByVal str As String, ByRef actualBytesWritten As Int32) As Boolean
        Dim _bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(str)
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, _bytes, _bytes.Length, actualBytesWritten)
    End Function
    Public Function WriteBytes(ByVal addr As IntPtr, ByVal bytes() As Byte, ByRef actualBytesWritten As Int32) As Boolean
        Return WinApi.WriteProcessMemory(_targetProcessHandle, addr, bytes, bytes.Length, actualBytesWritten)
    End Function
#End Region
#Region "Hack"
    Public Function GetMemRegions() As WinApi.MemoryBasicInformation()
        Dim _rtnBlocks As New List(Of WinApi.MemoryBasicInformation)
        Dim _curBase As Int64 = _systemInfo.lpMinimumApplicationAddress.ToInt64
        Dim _curMbi As WinApi.MemoryBasicInformation

        Try
            Do
                If WinApi.VirtualQueryEx(_targetProcessHandle, New IntPtr(_curBase), _curMbi, _mbiSize) = 0 Then
                    Exit Do
                End If
                If _curMbi.State = WinApi.MemoryAllocationState.COMMIT Then 'this page actively being used by the target process
                    _rtnBlocks.Add(_curMbi)
                End If
                _curBase += _curMbi.RegionSize.ToInt64
            Loop While _curBase < _systemInfo.lpMaximumApplicationAddress.ToInt64
        Catch ex As Exception
            MessageBox.Show("MemoryManager::GetMemRegions() Threw an Exception. Please give this message to tech support -->" & Environment.NewLine _
                          & "Current memory region info:" & Environment.NewLine _
                          & ".baseAddress: 0x" & _curMbi.BaseAddress.ToString("X") & Environment.NewLine _
                          & ".size: " & _curMbi.RegionSize.ToString() & " bytes" & Environment.NewLine _
                          & ".protect: " & _curMbi.Protect & Environment.NewLine _
                          & ".type: " & _curMbi.AllocationType & Environment.NewLine _
                          & "state: " & _curMbi.State & Environment.NewLine & Environment.NewLine _
                          & "System error message (also important): " & Environment.NewLine _
                          & ex.Message & Environment.NewLine & "Thank you. ")
        End Try

        Return _rtnBlocks.ToArray()
    End Function
    ''TODO: FindBytes(), FindPattern() and more.
#End Region

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                DetachFromProcess()
            End If
        End If
        Me.disposedValue = True
    End Sub
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
It's a few lines of code. Feel free to ask questions. A generic function would turn 100 lines of code into 15, but maybe this is more clear for people who are new/haven't used generics. Comments appreciated.