我正在尝试以编程方式更改外部可执行文件的图标。我用谷歌搜索,并使用C ++找到有关此问题的大量信息。基本上,我需要使用BeginUpdateResource,UpdateResource和EndUpdateResource。问题是 - 我不知道在C#中传递给UpdateResource的内容。
这是我到目前为止的代码:
class IconChanger
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage,
IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public enum ICResult
{
Success,
FailBegin,
FailUpdate,
FailEnd
}
public ICResult ChangeIcon(string exeFilePath, byte[] iconData)
{
// Load executable
IntPtr handleExe = BeginUpdateResource(exeFilePath, false);
if (handleExe == null)
return ICResult.FailBegin;
// Get language identifier
CultureInfo currentCulture = CultureInfo.CurrentCulture;
int pid = ((ushort)currentCulture.LCID) & 0x3ff;
int sid = ((ushort)currentCulture.LCID) >> 10;
ushort languageID = (ushort)((((ushort)pid) << 10) | ((ushort)sid));
// Get pointer to data
GCHandle iconHandle = GCHandle.Alloc(iconData, GCHandleType.Pinned);
// Replace the icon
if (UpdateResource(handleExe, "#3", "#1", languageID, iconHandle.AddrOfPinnedObject(), (uint)iconData.Length))
{
if (EndUpdateResource(handleExe, false))
return ICResult.Success;
else
return ICResult.FailEnd;
}
else
return ICResult.FailUpdate;
}
}
关于lpType - 在C ++中,传递RT_ICON(或RT_GROUP_ICON)。我应该在C#中传递什么价值? lpName参数也是同样的问题。 我不确定语言标识符(我在Internet上发现了这个),因为我无法测试它。 我也不确定我是否提供了合适的图标数据。目前,iconData包含来自.ico文件的字节。
有人能指出我正确的方向吗?
非常感谢。
答案 0 :(得分:7)
只是一些指示,这很难做对。通过说谎lpType参数传递RT_ICON。将它从字符串更改为IntPtr并传递(IntPtr)3。
lpData参数非常棘手。您需要按资源编译器(rc.exe)编译的方式传递数据。我不知道它是否会破坏.ico文件的原始数据。唯一合理的尝试是将带有FileStream的.ico文件中的数据读入byte [],您似乎已经这样做了。我认为该功能实际上是为了将资源从一个二进制映像复制到另一个二进制映像。你的方法工作的几率不是零。
你也忽略了另一个潜在的问题,程序图标的资源ID不一定是1.通常不是,100往往是一个受欢迎的选择,但任何事情都会发生。 EnumResourceNames需要使其可靠。规则是编号最小的ID设置文件的图标。我真的不确定这是否真的意味着资源编译器首先放置最小的数字,这可能是API可能没有的。
一个非常小的故障模式是UpdateResource只能更新编号的资源项,而不是名称的资源项。使用名称而不是数字并不罕见,但绝大多数图像都使用数字作为图标。
当然,如果没有UAC清单,这种情况的可能性为零。您正在攻击通常没有写入权限的文件。
答案 1 :(得分:3)
我设法使用ResourceHacker和这个帖子作为示例在纯C#中工作。只需使用常规.ico作为输入。在ResourceHacker(http://www.angusj.com/resourcehacker/)中,您将看到图标标识符(在我的情况下为1)和语言标识符(在我的情况下为1043):
我使用了这段代码:
internal class IconChanger
{
#region IconReader
public class Icons : List<Icon>
{
public byte[] ToGroupData(int startindex = 1)
{
using (var ms = new MemoryStream())
using (var writer = new BinaryWriter(ms))
{
var i = 0;
writer.Write((ushort)0); //reserved, must be 0
writer.Write((ushort)1); // type is 1 for icons
writer.Write((ushort)this.Count); // number of icons in structure(1)
foreach (var icon in this)
{
writer.Write(icon.Width);
writer.Write(icon.Height);
writer.Write(icon.Colors);
writer.Write((byte)0); // reserved, must be 0
writer.Write(icon.ColorPlanes);
writer.Write(icon.BitsPerPixel);
writer.Write(icon.Size);
writer.Write((ushort)(startindex + i));
i++;
}
ms.Position = 0;
return ms.ToArray();
}
}
}
public class Icon
{
public byte Width { get; set; }
public byte Height { get; set; }
public byte Colors { get; set; }
public uint Size { get; set; }
public uint Offset { get; set; }
public ushort ColorPlanes { get; set; }
public ushort BitsPerPixel { get; set; }
public byte[] Data { get; set; }
}
public class IconReader
{
public Icons Icons = new Icons();
public IconReader(Stream input)
{
using (BinaryReader reader = new BinaryReader(input))
{
reader.ReadUInt16(); // ignore. Should be 0
var type = reader.ReadUInt16();
if (type != 1)
{
throw new Exception("Invalid type. The stream is not an icon file");
}
var num_of_images = reader.ReadUInt16();
for (var i = 0; i < num_of_images; i++)
{
var width = reader.ReadByte();
var height = reader.ReadByte();
var colors = reader.ReadByte();
reader.ReadByte(); // ignore. Should be 0
var color_planes = reader.ReadUInt16(); // should be 0 or 1
var bits_per_pixel = reader.ReadUInt16();
var size = reader.ReadUInt32();
var offset = reader.ReadUInt32();
this.Icons.Add(new Icon()
{
Colors = colors,
Height = height,
Width = width,
Offset = offset,
Size = size,
ColorPlanes = color_planes,
BitsPerPixel = bits_per_pixel
});
}
// now get the Data
foreach (var icon in Icons)
{
if (reader.BaseStream.Position < icon.Offset)
{
var dummy_bytes_to_read = (int)(icon.Offset - reader.BaseStream.Position);
reader.ReadBytes(dummy_bytes_to_read);
}
var data = reader.ReadBytes((int)icon.Size);
icon.Data = data;
}
}
}
}
#endregion
[DllImport("kernel32.dll", SetLastError = true)]
static extern int UpdateResource(IntPtr hUpdate, uint lpType, ushort lpName, ushort wLanguage, byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, [MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public enum ICResult
{
Success,
FailBegin,
FailUpdate,
FailEnd
}
const uint RT_ICON = 3;
const uint RT_GROUP_ICON = 14;
public ICResult ChangeIcon(string exeFilePath, string iconFilePath)
{
using (FileStream fs = new FileStream(iconFilePath, FileMode.Open, FileAccess.Read))
{
var reader = new IconReader(fs);
var iconChanger = new IconChanger();
return iconChanger.ChangeIcon(exeFilePath, reader.Icons);
}
}
public ICResult ChangeIcon(string exeFilePath, Icons icons)
{
// Load executable
IntPtr handleExe = BeginUpdateResource(exeFilePath, false);
if (handleExe == null) return ICResult.FailBegin;
ushort startindex = 1;
ushort index = startindex;
ICResult result = ICResult.Success;
var ret = 1;
foreach (var icon in icons)
{
// Replace the icon
// todo :Improve the return value handling of UpdateResource
ret = UpdateResource(handleExe, RT_ICON, index, 0, icon.Data, icon.Size);
index++;
}
var groupdata = icons.ToGroupData();
// todo :Improve the return value handling of UpdateResource
ret = UpdateResource(handleExe, RT_GROUP_ICON, startindex, 0, groupdata, (uint)groupdata.Length);
if (ret == 1)
{
if (EndUpdateResource(handleExe, false))
result = ICResult.Success;
else
result = ICResult.FailEnd;
}
else
result = ICResult.FailUpdate;
return result;
}
}
答案 2 :(得分:2)
这是适合我的解决方案。我无法在.NET中编写它,但已设法编写我在C#应用程序中引用的C ++ DLL。
C ++解决方案的内容我正在构建DLL:
#include <io.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <windows.h>
extern "C"
{
#pragma pack(push, 2)
typedef struct {
WORD Reserved1; // reserved, must be 0
WORD ResourceType; // type is 1 for icons
WORD ImageCount; // number of icons in structure (1)
BYTE Width; // icon width (32)
BYTE Height; // icon height (32)
BYTE Colors; // colors (0 means more than 8 bits per pixel)
BYTE Reserved2; // reserved, must be 0
WORD Planes; // color planes
WORD BitsPerPixel; // bit depth
DWORD ImageSize; // size of structure
WORD ResourceID; // resource ID
} GROUPICON;
#pragma pack(pop)
__declspec(dllexport) void __stdcall ChangeIcon(char *executableFile, char *iconFile, INT16 imageCount)
{
int len = strlen(executableFile) + 1;
wchar_t *executableFileEx = new wchar_t[len];
memset(executableFileEx, 0, len);
::MultiByteToWideChar(CP_ACP, NULL, executableFile, -1, executableFileEx, len);
len = strlen("MAINICON") + 1;
wchar_t *mainIconEx = new wchar_t[len];
memset(mainIconEx, 0, len);
::MultiByteToWideChar(CP_ACP, NULL, "MAINICON", -1, mainIconEx, len);
HANDLE hWhere = BeginUpdateResource(executableFileEx, FALSE);
char *buffer; // Buffer to store raw icon data
long buffersize; // Length of buffer
int hFile; // File handle
hFile = open(iconFile, O_RDONLY | O_BINARY);
if (hFile == -1)
return; // If file doesn't exist, can't be opened etc.
// Calculate buffer length and load file into buffer
buffersize = filelength(hFile);
buffer = (char *)malloc(buffersize);
read(hFile, buffer, buffersize);
close(hFile);
// Calculate header size
int headerSize = 6 + imageCount * 16;
UpdateResource(
hWhere, // Handle to executable
RT_ICON, // Resource type - icon
MAKEINTRESOURCE(1), // Make the id 1
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), // Default language
buffer + headerSize, // Skip the header bytes
buffersize - headerSize // Length of buffer
);
GROUPICON grData;
grData.Reserved1 = 0; // reserved, must be 0
grData.ResourceType = 1; // type is 1 for icons
grData.ImageCount = 1; // number of icons in structure (1)
grData.Width = 32; // icon width (32)
grData.Height = 32; // icon height (32)
grData.Colors = 0; // colors (256)
grData.Reserved2 = 0; // reserved, must be 0
grData.Planes = 2; // color planes
grData.BitsPerPixel = 32; // bit depth
grData.ImageSize = buffersize - 22; // size of image
grData.ResourceID = 1; // resource ID is 1
UpdateResource(
hWhere,
RT_GROUP_ICON,
// RT_GROUP_ICON resources contain information
// about stored icons
mainIconEx,
// MAINICON contains information about the
// application's displayed icon
MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
&grData,
// Pointer to this structure
sizeof(GROUPICON)
);
delete buffer; // free memory
// Perform the update, don't discard changes
EndUpdateResource(hWhere, FALSE);
}
}
这是我用来从以前编写的DLL中导入ChangeIcon
函数的C#代码:
[DllImport("IconChanger.dll")]
static extern void ChangeIcon(String executableFile, String iconFile, short imageCount);
/// <summary>
/// Changes the executable's icon
/// </summary>
/// <param name="exeFilePath">Path to executable file</param>
/// <param name="iconFilePath">Path to icon file</param>
static public void ChangeIcon(string exeFilePath, string iconFilePath)
{
short imageCount = 0;
using (StreamReader sReader = new StreamReader(iconFilePath))
{
using (BinaryReader bReader = new BinaryReader(sReader.BaseStream))
{
// Retrieve icon count inside icon file
bReader.ReadInt16();
bReader.ReadInt16();
imageCount = bReader.ReadInt16();
}
}
// Change the executable's icon
ChangeIcon(exeFilePath, iconFilePath, imageCount);
}
希望至少有人会觉得这很有用。
答案 3 :(得分:0)
UpdateResource的C#声明:
/// <summary>
/// Adds, deletes, or replaces a resource in a portable executable (PE) file. There are some restrictions on resource updates in files that contain Resource Configuration (RC Config) data: language-neutral (LN) files and language-specific resource (.mui) files.
/// </summary>
/// <param name="hUpdate">A module handle returned by the BeginUpdateResource function, referencing the file to be updated. </param>
/// <param name="lpType">The resource type to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), where ID is an integer value representing a predefined resource type. If the first character of the string is a pound sign (#), then the remaining characters represent a decimal number that specifies the integer identifier of the resource type. For example, the string "#258" represents the identifier 258. For a list of predefined resource types, see Resource Types. </param>
/// <param name="lpName">The name of the resource to be updated. Alternatively, rather than a pointer, this parameter can be MAKEINTRESOURCE(ID), where ID is a resource ID. When creating a new resource do not use a string that begins with a '#' character for this parameter.</param>
/// <param name="wLanguage">The language identifier of the resource to be updated. For a list of the primary language identifiers and sublanguage identifiers that make up a language identifier, see the MAKELANGID macro. </param>
/// <param name="lpData">The resource data to be inserted into the file indicated by hUpdate. If the resource is one of the predefined types, the data must be valid and properly aligned. Note that this is the raw binary data to be stored in the file indicated by hUpdate, not the data provided by LoadIcon, LoadString, or other resource-specific load functions. All data containing strings or text must be in Unicode format. lpData must not point to ANSI data. If lpData is NULL and cbData is 0, the specified resource is deleted from the file indicated by hUpdate. Prior to Windows 7: If lpData is NULL and cbData is nonzero, the specified resource is NOT deleted and an exception is thrown.</param>
/// <param name="cbData">The size, in bytes, of the resource data at lpData. </param>
/// <returns>Returns TRUE if successful or FALSE otherwise. To get extended error information, call GetLastError.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
public static extern Int32 UpdateResourceW(void* hUpdate, char* lpType, char* lpName, UInt16 wLanguage, [CanBeNull] void* lpData, UInt32 cbData);
对于字符串资源类型或名称,您只需传递字符串。
对于系统预定义类型(如RT_ICON
)和int ID(如IDI_APPLICATION
),您将该整数值重新解释为将其转换为指针,如(char*)3
RT_ICON
。