/*
* Copyright (C) 2010-2016 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.process.win32;
import com.jpexs.helpers.ProgressListener;
import com.jpexs.process.ProcessTools;
import com.sun.jna.Memory;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.BITMAP;
import com.sun.jna.platform.win32.BaseTSD;
import com.sun.jna.platform.win32.Gdi32;
import com.sun.jna.platform.win32.ICONINFO;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.MEMORY_BASIC_INFORMATION;
import com.sun.jna.platform.win32.PROCESSENTRY32;
import com.sun.jna.platform.win32.Psapi;
import com.sun.jna.platform.win32.SHFILEINFO;
import com.sun.jna.platform.win32.Shell32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.ptr.PointerByReference;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author JPEXS
*/
public class Win32ProcessTools extends ProcessTools {
private static long pointerToAddress(Pointer p) {
String s = p.toString();
s = s.replace("native@0x", "");
return Long.parseLong(s, 16);
}
public static List<MEMORY_BASIC_INFORMATION> getPageRanges(WinNT.HANDLE hOtherProcess) {
List<MEMORY_BASIC_INFORMATION> ret = new ArrayList<>();
MEMORY_BASIC_INFORMATION mbi;
WinBase.SYSTEM_INFO si = new WinBase.SYSTEM_INFO();
Kernel32.INSTANCE.GetSystemInfo(si);
Pointer lpMem = si.lpMinimumApplicationAddress;
while (pointerToAddress(lpMem) < pointerToAddress(si.lpMaximumApplicationAddress)) {
mbi = new MEMORY_BASIC_INFORMATION();
BaseTSD.SIZE_T t = Kernel32.INSTANCE.VirtualQueryEx(hOtherProcess, lpMem, mbi, new BaseTSD.SIZE_T(mbi.size()));
if (t.longValue() == 0) {
Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Cannot get page ranges. Last error:" + Kernel32.INSTANCE.GetLastError());
break;
}
ret.add(mbi);
lpMem = new Pointer(pointerToAddress(mbi.baseAddress) + mbi.regionSize.longValue());
}
return ret;
}
public static void printMemInfo(MEMORY_BASIC_INFORMATION mbi) {
System.out.println("Region size:" + mbi.regionSize);
String stateStr = "";
if ((mbi.state.intValue() & Kernel32.MEM_COMMIT) == Kernel32.MEM_COMMIT) {
stateStr += " commit";
}
if ((mbi.state.intValue() & Kernel32.MEM_FREE) == Kernel32.MEM_FREE) {
stateStr += " free";
}
if ((mbi.state.intValue() & Kernel32.MEM_RESERVE) == Kernel32.MEM_RESERVE) {
stateStr += " reserve";
}
stateStr = stateStr.trim();
String typeStr = "";
if ((mbi.type.intValue() & Kernel32.MEM_IMAGE) == Kernel32.MEM_IMAGE) {
typeStr += " image";
}
if ((mbi.type.intValue() & Kernel32.MEM_MAPPED) == Kernel32.MEM_MAPPED) {
typeStr += " mapped";
}
if ((mbi.type.intValue() & Kernel32.MEM_PRIVATE) == Kernel32.MEM_PRIVATE) {
typeStr += " private";
}
typeStr = typeStr.trim();
String protStr = "";
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE) == Kernel32.PAGE_EXECUTE) {
protStr += " execute";
}
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE_READ) == Kernel32.PAGE_EXECUTE_READ) {
protStr += " execute_read";
}
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_EXECUTE_READWRITE) == Kernel32.PAGE_EXECUTE_READWRITE) {
protStr += " execute_readwrite";
}
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_READONLY) == Kernel32.PAGE_READONLY) {
protStr += " readonly";
}
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_READWRITE) == Kernel32.PAGE_READWRITE) {
protStr += " readwrite";
}
if ((mbi.allocationProtect.intValue() & WinNT.PAGE_WRITECOPY) == Kernel32.PAGE_WRITECOPY) {
protStr += " writecopy";
}
protStr = protStr.trim();
System.out.println("State:" + stateStr);
System.out.println("Type:" + typeStr);
System.out.println("Protect:" + protStr);
System.out.println("baseAddress:" + mbi.baseAddress);
System.out.println("========================");
}
private static byte[] mergeArrays(byte[] one, byte[] two) {
byte[] combined = new byte[one.length + two.length];
System.arraycopy(one, 0, combined, 0, one.length);
System.arraycopy(two, 0, combined, one.length, two.length);
return combined;
}
public static Map<String, Character> getDriveMappings() {
Map<String, Character> ret = new HashMap<>();
for (char d = 'A'; d <= 'Z'; d++) {
char[] buf = new char[1024];
int len = Kernel32.INSTANCE.QueryDosDevice(d + ":", buf, buf.length).intValue();
String tar = new String(buf, 0, len);
tar = tar.trim();
if (!"".equals(tar)) {
ret.put(tar, d);
}
}
return ret;
}
public static String ntPathToWin32(String path) {
for (String dp : driveMappings.keySet()) {
if (path.startsWith(dp)) {
return driveMappings.get(dp) + ":" + path.substring(dp.length());
}
}
return path;
}
private static final Map<String, Character> driveMappings = getDriveMappings();
public static boolean drawIcon(BufferedImage ret, WinDef.HICON hIcon, int diFlags) {
WinDef.HDC hdcScreen = User32.INSTANCE.GetDC(null);
WinDef.HDC hdcMem = Gdi32.INSTANCE.CreateCompatibleDC(hdcScreen);
WinDef.HBITMAP bitmap = Gdi32.INSTANCE.CreateCompatibleBitmap(hdcScreen, ret.getWidth(), ret.getHeight());
WinNT.HANDLE hbmOld = Gdi32.INSTANCE.SelectObject(hdcMem, bitmap);
WinNT.HANDLE hBrush = Gdi32.INSTANCE.CreateSolidBrush(new WinDef.DWORD(0xffffff));
WinDef.RECT rect = new WinDef.RECT();
rect.left = 0;
rect.top = 0;
rect.right = ret.getWidth();
rect.bottom = ret.getHeight();
User32.INSTANCE.FillRect(hdcMem, rect, hBrush);
Gdi32.INSTANCE.DeleteObject(hBrush);
boolean ok = User32.INSTANCE.DrawIconEx(hdcMem, 0, 0, hIcon, ret.getWidth(), ret.getHeight(), new WinDef.UINT(0), new WinDef.HBRUSH(Pointer.NULL), diFlags);
if (!ok) {
return false;
}
for (int x = 0; x < ret.getWidth(); x++) {
for (int y = 0; y < ret.getHeight(); y++) {
int rgb = Gdi32.INSTANCE.GetPixel(hdcMem, x, y).intValue();
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = (rgb) & 0xff;
rgb = (b << 16) + (g << 8) + r;
ret.setRGB(x, y, rgb);
}
}
Gdi32.INSTANCE.SelectObject(hdcMem, hbmOld);
Gdi32.INSTANCE.DeleteObject(bitmap);
Gdi32.INSTANCE.DeleteDC(hdcMem);
User32.INSTANCE.ReleaseDC(null, hdcScreen);
return true;
}
private static void applyMask(BufferedImage image, BufferedImage mask) {
int width = image.getWidth();
int height = image.getHeight();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int masked = mask.getRGB(x, y);
int alpha = 255 - (masked & 0xff);
if (alpha != 0 && alpha != 255) {
System.out.println("a=" + alpha);
}
int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xff;
int g = (rgb >> 8) & 0xff;
int b = (rgb) & 0xff;
r = r * alpha / 255;
g = g * alpha / 255;
b = b * alpha / 255;
rgb = (r << 16) + (g << 8) + b;
rgb = (alpha << 24) | rgb;
image.setRGB(x, y, rgb);
}
}
}
public static BufferedImage iconToImage(WinDef.HICON hIcon) {
ICONINFO info = new ICONINFO();
boolean ok = User32.INSTANCE.GetIconInfo(hIcon, info);
if (!ok) {
return null;
}
BufferedImage ret = null;
BITMAP bmp = new BITMAP();
if (info.hbmColor != null) {
int nWrittenBytes = Gdi32.INSTANCE.GetObject(info.hbmColor, bmp.size(), bmp);
if (nWrittenBytes > 0) {
ret = new BufferedImage(bmp.bmWidth.intValue(), bmp.bmHeight.intValue(), BufferedImage.TYPE_INT_ARGB);
}
} else if (info.hbmMask != null) {
int nWrittenBytes = Gdi32.INSTANCE.GetObject(info.hbmMask, bmp.size(), bmp);
if (nWrittenBytes > 0) {
ret = new BufferedImage(bmp.bmWidth.intValue(), bmp.bmHeight.intValue() / 2, BufferedImage.TYPE_INT_ARGB);
}
}
if (ret == null) {
return ret;
}
if (info.hbmColor != null) {
Gdi32.INSTANCE.DeleteObject(info.hbmColor);
}
if (info.hbmMask != null) {
Gdi32.INSTANCE.DeleteObject(info.hbmMask);
}
drawIcon(ret, hIcon, User32.DI_NORMAL);
BufferedImage mask = new BufferedImage(ret.getWidth(), ret.getHeight(), BufferedImage.TYPE_INT_RGB);
drawIcon(mask, hIcon, User32.DI_MASK);
applyMask(ret, mask);
return ret;
}
public static BufferedImage getShellIcon(String path) {
SHFILEINFO fi = new SHFILEINFO();
BaseTSD.DWORD_PTR r = Shell32.INSTANCE.SHGetFileInfo(path, 0, fi, fi.size(), Shell32.SHGFI_ICON | Shell32.SHGFI_SMALLICON);
if (r.intValue() == 0) {
return null;
}
return iconToImage(fi.hIcon);
}
public static BufferedImage extractExeIcon(String fullExeFile, int index, boolean large) {
PointerByReference iconsLargeRef = new PointerByReference();
PointerByReference iconsSmallRef = new PointerByReference();
int cnt = Shell32.INSTANCE.ExtractIconEx(fullExeFile, index, iconsLargeRef, iconsSmallRef, new WinDef.UINT(1)).intValue();
if (cnt == 0) {
return null;
}
Pointer iconsLarge = iconsLargeRef.getPointer();
Pointer iconsSmall = iconsSmallRef.getPointer();
WinDef.HICON icon;
if (large) {
icon = new WinDef.HICON(iconsLarge.getPointer(0));
} else {
icon = new WinDef.HICON(iconsSmall.getPointer(0));
}
BufferedImage ic = iconToImage(icon);
User32.INSTANCE.DestroyIcon(icon);
return ic;
}
public static List<com.jpexs.process.Process> listProcesses() {
if (!privAdjusted) {
adjustPrivileges();
}
List<com.jpexs.process.Process> ret = new ArrayList<>();
WinNT.HANDLE hSnapShot = Kernel32.INSTANCE.CreateToolhelp32Snapshot(new WinDef.DWORD(Kernel32.TH32CS_SNAPPROCESS), new WinDef.DWORD(0));
PROCESSENTRY32 pe = new PROCESSENTRY32();
if (Kernel32.INSTANCE.Process32First(hSnapShot, pe)) {
do {
int i;
i = 0;
for (; i < pe.szExeFile.length; i++) {
if (pe.szExeFile[i] == 0) {
break;
}
}
String exeFile = new String(pe.szExeFile, 0, i);
char[] outputnames = new char[1024];
WinNT.HANDLE tempProcess = Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION, false, pe.th32ProcessID);
if (tempProcess == null) {
continue;
}
try {
int rn = Psapi.INSTANCE.GetProcessImageFileNameW(tempProcess, outputnames, 1024);
if (rn == 0) {
Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Can't get EXE path");
}
} catch (Exception | UnsatisfiedLinkError e) {
}
try {
int rn = Kernel32.INSTANCE.GetProcessImageFileNameW(tempProcess, outputnames, 1024);
if (rn == 0) {
Logger.getLogger(Win32ProcessTools.class.getName()).log(Level.SEVERE, "Can't get EXE path");
}
} catch (Exception | UnsatisfiedLinkError e) {
}
i = 0;
for (; i < outputnames.length; i++) {
if (outputnames[i] == 0) {
break;
}
}
String fullExeFile = new String(outputnames, 0, i);
fullExeFile = ntPathToWin32(fullExeFile);
ret.add(new Win32Process(fullExeFile, exeFile, getShellIcon(fullExeFile), pe.th32ProcessID));
} while (Kernel32.INSTANCE.Process32Next(hSnapShot, pe));
}
return ret;
}
private static boolean pageReadable(MEMORY_BASIC_INFORMATION mbi) {
int iUnReadable = 0;
iUnReadable |= (mbi.state.intValue() == Kernel32.MEM_FREE) ? 1 : 0;
iUnReadable |= (mbi.state.intValue() == Kernel32.MEM_RESERVE) ? 1 : 0;
iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_WRITECOPY);
iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_EXECUTE);
iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_GUARD);
iUnReadable |= (mbi.protect.intValue() & WinNT.PAGE_NOACCESS);
return iUnReadable == 0;
}
public static Map<Long, InputStream> findBytesInProcessMemory(ProgressListener progListener, WinDef.DWORD dwProcessID, byte[][] findBytesAll) {
int maxFindLen = 0;
for (int i = 0; i < findBytesAll.length; i++) {
if (findBytesAll[i].length > maxFindLen) {
maxFindLen = findBytesAll[i].length;
}
}
Map<Long, InputStream> ret = new HashMap<>();
WinNT.HANDLE hOtherProcess = Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_VM_READ | Kernel32.PROCESS_VM_WRITE | Kernel32.PROCESS_QUERY_INFORMATION | Kernel32.PROCESS_VM_OPERATION /*for VirtualProtectEx*/, false, dwProcessID);
List<MEMORY_BASIC_INFORMATION> pages = getPageRanges(hOtherProcess);
long totalMemLen = 0;
int progress = 0;
for (int pg = 0; pg < pages.size(); pg++) {
totalMemLen += pages.get(pg).regionSize.longValue();
}
long actualPos = 0;
List<Integer> guardedPages = new ArrayList<>();
for (int pg = 0; pg < pages.size(); pg++) {
MEMORY_BASIC_INFORMATION mbi = pages.get(pg);
if (pageReadable(mbi)) {
long addr = pointerToAddress(mbi.baseAddress);
int maxsize = mbi.regionSize.intValue();
long pos = 0;
long bufSize = 1024 * 512;
do {
NativeLongByReference bytesReadRef = new NativeLongByReference();
Memory buf = new Memory(bufSize);
boolean ok = Kernel32.INSTANCE.ReadProcessMemory(hOtherProcess, new Pointer(addr + pos), buf, new NativeLong(bufSize), bytesReadRef);
if (!ok) {
break;
}
if (bytesReadRef.getValue().longValue() == 0) {
break;
}
byte[] data = buf.getByteArray(0, bytesReadRef.getValue().intValue());
byte[] prevBytes = Arrays.copyOfRange(data, data.length - maxFindLen, data.length);
byte[] dataPlusPrev = mergeArrays(prevBytes, data);
loopi:
for (int i = 0; i < dataPlusPrev.length - maxFindLen; i++) {
loopk:
for (int k = 0; k < findBytesAll.length; k++) {
if (dataPlusPrev[i] == findBytesAll[k][0]) {
for (int p = 1; p < findBytesAll[k].length; p++) {
if (dataPlusPrev[i + p] != findBytesAll[k][p]) {
continue loopk;
}
}
long dataAddr = (long) (addr + pos - prevBytes.length + i);
ret.put(dataAddr, new ProcessMemoryInputStream(pages, hOtherProcess, pg, pos - prevBytes.length + i));
}
}
}
pos += bytesReadRef.getValue().longValue();
if (progListener != null) {
int newprogress = Math.round((actualPos + pos) * 100 / totalMemLen);
if (newprogress != progress) {
progress = newprogress;
progListener.progress(progress);
}
}
if (pos + bufSize >= maxsize) {
bufSize = maxsize - pos;
}
} while (bufSize > 0);
} else if (hasGuard(mbi)) {
if (unsetGuard(hOtherProcess, mbi)) {
guardedPages.add(pg);
pg--;
continue;
}
}
actualPos += mbi.regionSize.longValue();
int newprogress = Math.round(actualPos * 100 / totalMemLen);
if (newprogress != progress) {
progress = newprogress;
progListener.progress(progress);
}
}
//Set PAGE_GUARD back again
for (int pg : guardedPages) {
setGuard(hOtherProcess, pages.get(pg));
}
return ret;
}
private static boolean hasGuard(MEMORY_BASIC_INFORMATION mbi) {
return (mbi.protect.intValue() & WinNT.PAGE_GUARD) == WinNT.PAGE_GUARD;
}
private static boolean unsetGuard(HANDLE hOtherProcess, MEMORY_BASIC_INFORMATION mbi) {
if (!hasGuard(mbi)) {
return true;
}
int oldProt = mbi.protect.intValue();
int newProt = oldProt - WinNT.PAGE_GUARD;
IntByReference oldProtRef = new IntByReference();
boolean ok = Kernel32.INSTANCE.VirtualProtectEx(hOtherProcess, new WinDef.LPVOID(pointerToAddress(mbi.baseAddress)), mbi.regionSize, newProt, oldProtRef);
if (ok) {
mbi.protect = new NativeLong(newProt);
return true;
}
return false;
}
private static boolean setGuard(HANDLE hOtherProcess, MEMORY_BASIC_INFORMATION mbi) {
if (hasGuard(mbi)) {
return true;
}
int oldProt = mbi.protect.intValue();
int newProt = oldProt | WinNT.PAGE_GUARD;
IntByReference oldProtRef = new IntByReference();
boolean ok = Kernel32.INSTANCE.VirtualProtectEx(hOtherProcess, new WinDef.LPVOID(pointerToAddress(mbi.baseAddress)), mbi.regionSize, newProt, oldProtRef);
if (ok) {
mbi.protect = new NativeLong(newProt);
return true;
}
return false;
}
private static boolean privAdjusted = false;
public static boolean adjustPrivileges() {
WinNT.TOKEN_PRIVILEGES tp = new WinNT.TOKEN_PRIVILEGES(1);
WinNT.TOKEN_PRIVILEGES oldtp = new WinNT.TOKEN_PRIVILEGES(1);
WinNT.LUID luid = new WinNT.LUID();
WinNT.HANDLEByReference hTokenRef = new WinNT.HANDLEByReference();
if (!Advapi32.INSTANCE.OpenProcessToken(Kernel32.INSTANCE.GetCurrentProcess(), WinNT.TOKEN_ADJUST_PRIVILEGES | WinNT.TOKEN_QUERY, hTokenRef)) {
return false;
}
WinNT.HANDLE hToken = hTokenRef.getValue();
if (!Advapi32.INSTANCE.LookupPrivilegeValue(null, WinNT.SE_DEBUG_NAME, luid)) {
Kernel32.INSTANCE.CloseHandle(hToken);
return false;
}
tp.PrivilegeCount = new WinDef.DWORD(1);
tp.Privileges = new WinNT.LUID_AND_ATTRIBUTES[1];
tp.Privileges[0] = new WinNT.LUID_AND_ATTRIBUTES(luid, new WinDef.DWORD(WinNT.SE_PRIVILEGE_ENABLED));
IntByReference retSize = new IntByReference(0);
if (!Advapi32.INSTANCE.AdjustTokenPrivileges(hToken, false, tp, tp.size(), oldtp, retSize)) {
Kernel32.INSTANCE.CloseHandle(hToken);
return false;
}
Kernel32.INSTANCE.CloseHandle(hToken);
privAdjusted = true;
return true;
}
private static class ProcessMemoryInputStream extends InputStream {
private final List<MEMORY_BASIC_INFORMATION> pages;
private int currentPage = 0;
private long pagePos = 0;
private static final int BUFFER_SIZE = 1024 * 512;
private byte[] buf;
private int bufPos;
private final HANDLE hOtherProcess;
public ProcessMemoryInputStream(List<MEMORY_BASIC_INFORMATION> pages, HANDLE hOtherProcess, int currentPage, long pagePos) {
this.pages = pages;
this.hOtherProcess = hOtherProcess;
this.currentPage = currentPage;
this.pagePos = pagePos;
}
private boolean readNext() throws IOException {
MEMORY_BASIC_INFORMATION mbi = pages.get(currentPage);
if (!pageReadable(mbi)) {
if (hasGuard(mbi)) {
if (unsetGuard(hOtherProcess, mbi)) {
boolean ret = readNext();
setGuard(hOtherProcess, mbi);
return ret;
}
}
if (currentPage + 1 < pages.size()) {
pagePos = 0;
currentPage++;
return readNext();
}
return false;
}
long addr = pointerToAddress(mbi.baseAddress);
int maxsize = mbi.regionSize.intValue();
NativeLongByReference bytesReadRef = new NativeLongByReference();
Memory membuf = new Memory(BUFFER_SIZE);
NativeLong bufSize = new NativeLong(BUFFER_SIZE);
if (pagePos + bufSize.longValue() > maxsize) {
bufSize.setValue(maxsize - pagePos);
}
if (bufSize.longValue() == 0) {
if (currentPage + 1 < pages.size()) {
pagePos = 0;
currentPage++;
return readNext();
}
return false;
}
boolean ok = Kernel32.INSTANCE.ReadProcessMemory(hOtherProcess, new Pointer(addr + pagePos), membuf, bufSize, bytesReadRef);
if (!ok) {
throw new IOException("Cannot read memory");
}
if (bytesReadRef.getValue().longValue() == 0) {
return readNext();
}
pagePos += bytesReadRef.getValue().longValue();
buf = membuf.getByteArray(0, bytesReadRef.getValue().intValue());
return true;
}
@Override
public int read() throws IOException {
if ((buf == null) || (bufPos >= buf.length)) {
if (buf != null) {
bufPos = 0;
}
if (!readNext()) {
return -1;
}
}
return buf[bufPos++] & 0xff;
}
}
}