[这个问题很难吗?为什么没有人回答?]关于一个利用delphi实现二进制查询修改的问题(100分)

  • 主题发起人 主题发起人 黑河
  • 开始时间 开始时间

黑河

Unregistered / Unconfirmed
GUEST, unregistred user!
我有个文件D:/世界目录/检索.exe,我想对这个文件中的3b 67 aa ff进行修改,改为de 67 aa ff,而且检索.exe文件中有至少6个这样的值,我却只想改第一个3b 67 aa ff.
请问各位高手,如何编程实现.
本人比较笨,初学Delphi,希望高手能够讲述的详细些.谢谢.
 
改为
00 FF 00 FF
[:(!]
 
var
f:file of Longword;
i:integer;
Dw:Longword;//这个类型4字节
begin
try
assignfile(f,'D:/世界目录/检索.exe');
reset(f);
for i:=0 do begin
read(f,Dw);
if DW=你的值 then begin
seek(f,i);
write(f,想该的值);
exit;
end;
end;
finally
closefile(f);
end;
end;
 
我也来写一个,呵呵
var
tmpFStream; TStream;
i, tmpCount, tmpPos: Integer;
tmpBArray: array[1..4] of Byte;
begin
tmpFStream := TFileStream.Create('c:/1234.dat', fmOpenReadWrite);
tmpCount := 0;
try
tmpFStream.Seek(4, soFromEnd); //到最后
for i := 1 to tmpFSteam.Size - 4 do
begin
tmpFStream.Read(tmpBArray, 4);
if (tmpBArray[1] = $3b) and //3b 67 aa ff
(tmpBArray[2] = $67) and
(tmpBArray[3] = $aa) and
(tmpBArray[4] = $ff) then
begin
Inc(tmpCount);
tmpPos := 4 + i- 1;
end;
tmpFStream.Seek(4 + i, soFromEnd);
end;
if tmpCount > 6 then
begin
tmpBArray[1] := $de;
tmpFStream.Seek(tmpPos, soFromEnd);
tmpFStream.Write(tmpBArray[1], 1);
end;
finally
tmpFStream.Free;
end;
end;
 
按照bmsr来做,出现很多错误。
按照nicai_wgl的来做,程序运行很长时间后,检索.exe的程序没有被改变,代码如下:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, RzButton;

type
TForm1 = class(TForm)
RzButton1: TRzButton;
procedure RzButton1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.RzButton1Click(Sender: TObject);
var
tmpFStream: TStream;
i, tmpCount, tmpPos: Integer;
tmpBArray: array[1..4] of Byte;
begin
tmpFStream := TFileStream.Create('D:/世界目录/检索.exe', fmOpenReadWrite);
tmpCount := 0;
try
tmpFStream.Seek(4, soFromEnd); //到最后
for i := 1 to tmpFStream.Size - 4 do
begin
tmpFStream.Read(tmpBArray, 4);
if (tmpBArray[1] = $3b) and //3b 67 aa ff
(tmpBArray[2] = $67) and
(tmpBArray[3] = $aa) and
(tmpBArray[4] = $ff) then
begin
Inc(tmpCount);
tmpPos := 4 + i- 1;
end;
tmpFStream.Seek(4 + i, soFromEnd);
end;
if tmpCount > 6 then
begin
tmpBArray[1] := $de;
tmpFStream.Seek(tmpPos, soFromEnd);
tmpFStream.Write(tmpBArray[1], 1);
end;
finally
tmpFStream.Free;
end;
end;


end.

希望高手们帮忙解决!感谢!
 
上面得还需要把stream 写回另一个文件
我的错是什么! 你说说!
另外我就是给你写个方法,你看懂了自然会改。你不要一字不改得照抄,只要是理解!
 
如何把stream写回另一个文件?
bmsr,对不起,我对二进制的文件的读写一窍不通,可以说很难理解您写的程序中部分代码的意思,所以不太会理解着改写您提供的帮助,于是出现了不少错误。
初学者嘛,很多地方不明白,希望您能够提供一个完整代码,加上注释,让我能够真正理解代码的含义,可以做到举一反三。
呵呵,可能提出的要求有些过分,但是真的万分感谢。
 
procedure TForm1.Button1Click(Sender: TObject);
var
f:file of Longword; //将你要操作的文件看作一个由N个 Longword组成的文件
i:integer;
Dw:Longword;//这个类型4字节 因为你查找的内容是4字节,
//用此类型正好可以一次取 4字节
begin
try
assignfile(f,'D:/世界目录/检索.exe');
reset(f);

read(f,Dw);//每次读完后文件指针会指向下一个开始
while not eof(f) do
begin
if DW=$3b67aaff then //3b67aaff 等于10进制的 996649727
begin
i:=FilePos(F)-1;
seek(f,i); //回到上一个数据起始位置
DW:=$de67aaff;
write(f,DW); //把这个数据替换掉
exit; //由于你只想替换第一个 ,所以替换完第一个就退出
end;
read(f,Dw);
end;

finally
closefile(f); //无论如何都会关闭打开的文件 以保证更改生效
end;
end;
 
bmsr,您写的代码编译没有问题,但是文件没有改动,不知道为什么。
另外,“read(f,Dw);//每次读完后文件指针会指向下一个开始”这句代码是指一行一行读取吗?不知道为什么,运行您的代码时,程序会有一段时间的停顿,是不是因为检索.exe文件太大?检索.exe是5M的文件。
“i:=FilePos(F)-1;”这句代码和后面的“seek(f,i); //回到上一个数据起始位置”有点让我雾水。。。
希望您再帮我看一下,为什么,执行程序后,检索.exe没有被改动。好像是没有找到3b 67 aa ff。
 
前面的程序的确有问题,4字节4字节的取,是很可能找不到。
用下面的方法应该没问题了。
i:=FilePos(F)-1;”这句代码和后面的“seek(f,i); 是为了将指针定位到找到所要的数据段的开始,因为每次read后指针会指向下一个数据段的开始,要修改刚才的数据段必须向前倒回一个数据

procedure TForm1.Button1Click(Sender: TObject);
var
f:file of shortstring;
st:shortstring;
i:integer;
begin
try
assignfile(f,'D:/世界目录/检索.exe');
reset(f);

read(f,st);//每次读完后文件指针会指向下一个shortstring
while not eof(f) do
begin
if pos(#$3b#$67#$aa#$ff,st)<>0 then //3b67aaff
begin //#表示将后面的数字作为char类型
stringreplace(st,#$3b#$67#$aa#$ff, #$de#$67#$aa#$ff,[]);
i:=FilePos(F)-1; // FilePos(F)取的当前指针
seek(f,i); //回到上一个shortstring起始位置
write(f,st); //把这个数据替换掉
exit; //由于你只想替换第一个 ,所以替换完第一个就退出
end;
read(f,st);
end;

finally
closefile(f); //无论如何都会关闭打开的文件 以保证更改生效
end;
end;
 
bmsr,现在程序运行起来不再有停顿,但是检索.exe还是没有被改。不知道为什么。您的代码我这次看的可以理解,按说不应该有错。。。可是为什么还是改不了啊?我检测好像程序找到要改的字节,但是没有做改动。
 
如此简单的一个问题,真是让人哭笑不得, 敬佩bmsr的好心, 但对bmsr的水平真不敢恭维,
procedure TForm1.Button1Click(Sender: TObject);
var f : TFileStream;
b : byte;
xpos : integer; // or int64;
begin
f := TFileStream.Create('d:/yourfilename.exe', fmOpenReadWrite);
while f.Position < f.Size - 4 do
begin
xpos := f.Position ;
f.Read(b,1);
//3b 67 aa ff进行修改,改为de 67 aa ff
if b=$3b then
begin
f.Read(b,1);
if b=$67 then
begin
f.Read(b,1);
if b=$aa then
begin
f.Read(b,1);
if b=$ff then
begin
// ok, now update new data...
f.Seek(xpos, soFromBeginning);
b := $de; f.Write(b,1);
b := $67; f.Write(b,1);
b := $aa; f.Write(b,1);
b := $ff; f.Write(b,1);
// then close the file and exit
f.Destroy ;
exit;
end;
end;
end;
end;
end;
// finally, must close the file
f.Destroy ;
// 这是最简单直接的方法, 如果要速度, 可以优化文件的读写与搜索方法
end;
 
用我这个把,我这个已经测试成功了
const
SearchByteArr : array [0..3] of Byte = ($3b, $67, $aa, $ff);
PatchByteArr : array [0..3] of Byte = ($de, $67, $aa, $ff);
var
PatchFileStream: TFileStream;
rBuf: array [0..32766] of Byte;
rPatchBuf: array [0..3] of Byte;
rCount: Integer;
rPos: Integer;
I: Integer;
OldPos: Integer;
begin
PatchFileStream := TFileStream.Create('D:/世界目录/检索.exe', fmOpenReadWrite or
fmShareExclusive);
try
rPos := 0;
if PatchFileStream.Size > 0 then
repeat
rCount := PatchFileStream.Read(rBuf, SizeOf(rBuf));
if rCount <> 0 then
begin
for I := 0 to rCount - 1 do
begin
Inc(rPos);
if rBuf = SearchByteArr[0] then
begin
OldPos := PatchFileStream.Position;
try
PatchFileStream.Seek(rPos - 1, soBeginning);
if PatchFileStream.Read(rPatchBuf, SizeOf(rPatchBuf)) = SizeOf(rPatchBuf) then
begin
if Integer(rPatchBuf) = Integer(SearchByteArr) then
begin
PatchFileStream.Seek(-4, soFromCurrent);
if PatchFileStream.Write(PatchByteArr, SizeOf(PatchByteArr)) = SizeOf(PatchByteArr) then
Exit;
end;
end;
finally
PatchFileStream.Position := OldPos;
end;
end;
end;
end;
until rCount = 0;
finally
PatchFileStream.Free;
end;
end;
 
新世纪的方法也不错,只是他一个字节一个字节的读会比较慢。
 
bmsr兄的错误就在于,他没有考虑读取文件的时候,把目标查找的3b 67 aa ff给截断的可能。如果查找的4个字节正好在一半一个缓冲区结尾,另外一半在下一个缓冲区开头,则根本无法搜索到查找目标。因此,改成读取缓冲区后,查找3b 67 aa ff的第一个字节相同的字节(3b),然后从这个找到的位置连续读4个字节之后,就可以判断是否找到目标了。
 
嗯!是的!我之前就发现这个问题了。
我自己做时 ,是用以前的写好的函数直接把整个文件转成一个string,只要这个文件小于2G就可以了。改完之后再把这个string写成文件。由于那恋歌函数不是专门用于干这个事情的,代码太多,就偷懒了把原来的改写了以下,也没测试。楼主在我那帖子里说我负责,我很惭愧!
其实可以吧 文件读入流中,还是255个字节的找,但每次找下一个时,让流的pos前移3个字节,这样就可以解决截断问题。
代码就不写了,参考楼上和我的代码即可。(注意:流的pos间隔始终是1个字节)
 
zqw0117的代码达到效果了。无论您回复的答案是否帮我解决了问题,非常感谢以上回复我问题的朋友们。
bmsr回复的代码虽然一直未达到效果,但是他认真负责的态度令我钦佩。
新世纪的代码确实运行比较慢,会有程序的停顿,但是一样可以达到效果,感谢您的回复。
zqw0117的代码我不是很理解,希望您能够帮我把程序每行作注释,让我可以理解清楚,算是学习啊。最好可以发到我的邮箱heihe@126.com。谢谢了。
 
const
{SearchByteArr 是一个常量数组,其内容就是文件中要寻找的字节串}
SearchByteArr : array [0..3] of Byte = ($3b, $67, $aa, $ff);
{PatchByteArr 是常量数组,其内容就是需要补丁的字节串}
PatchByteArr : array [0..3] of Byte = ($de, $67, $aa, $ff);
var
{PatchFileStream 是流对象,准确的说是TFileStream的流对象。}
PatchFileStream: TFileStream;
{rBuf 是一个占用32767字节的缓冲区,这个缓冲区用来给流对象一次从文件中读取
32K字节,然后把读入的内容存储在rBuf缓冲区中,以便可以在内存中快速查找目标,
即SearchByteArr。这里需要说明一点,因为I/O访问是需要占用CPU时间的,所以
如果按照一个字节一个字节的读写文件,会让系统非常缓慢,而且也没有效率。
如果把所需要的内容读取到内存中,然后进行比对,则速度会非常快。故这里用了
缓冲区}
rBuf: array [0..32766] of Byte;
{rPachBuf是一个4字节的缓冲区,这个缓冲区主要是在找到了SearchByteArr所定义的
字节串的时候,从文件中把这4个字节读进来,以便和SearchByteArr做比较,用于判断
是否找到了需要补丁的字节位置}
rPatchBuf: array [0..3] of Byte;
{rCount是保存流对象实际读取的字节长度的值}
rCount: Integer;
{rPos是已经分析到文件的哪个字节了,rPos指向的那个字节如果和SearchByteArr的第
一个字节相同,就可以从该字节前一个字节连续读4个字节,来和SearchByteArr比较了。
一旦比较结果相同,则说明找到需要补丁的字节位置}
rPos: Integer;
{I是一个循环变量,用于从读取rBuf缓冲的第一个字节到最后一个字节(rCount表示)
之间进行循环}
I: Integer;
{OldPos保存旧的TFileStream.Position位置,以便后续恢复}
OldPos: Integer;
begin
{生成一个TFileStream对象,其构造函数第一个参数给文件名,第二个参数给予打开权限
和共享权限。这里fmOpenReadWrite表示按照读写模式打开; fmShareExclusive表示用独占
方式共享文件(即,其他任何程序之后都无法打开“检索.exe”直到TFileStream对象释放
后才解除锁定}
PatchFileStream := TFileStream.Create('D:/世界目录/检索.exe', fmOpenReadWrite or
fmShareExclusive);
try
rPos := 0; //初始化rPos
if PatchFileStream.Size > 0 then //如果打开的文件Size大于0则进入下面的循环
repeat
{调用TFileStream.Read方法,从文件中读取字节串到缓冲区中。rBuf是缓冲区,
SizeOf(rBuf)返回缓冲区的大小,实际上就是32767字节. TFileStream.Read返回
的返回值表示实际读取的字节长度(如文件没有32K大,返回值就是实际读取的字节
数. 当TFileSTream.Read方法调用后,TFileStream.Position就指向实际读取字节
的偏移量,下次调用Read的时候,会从偏移位置继续读}
rCount := PatchFileStream.Read(rBuf, SizeOf(rBuf));
if rCount <> 0 then //如果读取的字节不是0(出现0的情况是因为读到文件尾了)
begin
for I := 0 to rCount - 1 do //进入循环
begin
Inc(rPos); //= rPos := rPos + 1
{比对rBuf中第I个字节是否和SearchByteArr第一个字节相同}
if rBuf = SearchByteArr[0] then
begin //相同
OldPos := PatchFileStream.Position; //保存当前文件指针位置
try
{调用TFileStream.Seek方法移动指针。第一个参数表示移动的偏移量,
第二个参数表示按照什么方式移动(soBeginning表示从文件头开始移动).
这里需要从文件头开始移动,而不是文件指针的当前位置,是因为,文件指针
当前的位置是按照32K的读取方式偏移的,因此指针指向的位置是上次Read后
指针所在的位置。这里rPos计算了已经分析的总字节数,所以从rPos - 1
的位置就是想要比对的4个字节的起始位置}
PatchFileStream.Seek(rPos - 1, soBeginning);
{读取4个字节到rPatchBuf缓冲区中,这里用一个if判断,是为了验证是否真的
读了4个字节(因为可能指针已经指向文件尾,或者从这个位置往后只存在1-
3个字节,而不够4个字节,如果有这种情况,就没有比较继续比对是否和
要Patch的字节相同进而写入文件了}
if PatchFileStream.Read(rPatchBuf, SizeOf(rPatchBuf)) = SizeOf(rPatchBuf) then
begin //成功读取4字节
{我们都知道,4个字节可以理解为一个Integer,所以这里用强制转换的方式
让编译器把读取的4字节和SearchByteArr常量的4字节都看作Integer进行比对}
if Integer(rPatchBuf) = Integer(SearchByteArr) then
begin //相等
{此刻指针位置在需要补丁的4字节后,于是移动指针到这4个字节的开头
位置。soFromCurrent表示从当前文件指针开始移动Position}
PatchFileStream.Seek(-4, soFromCurrent);
{写入事先准备好的PatchByteArr,如果写入正确,说明补丁完毕}
if PatchFileStream.Write(PatchByteArr, SizeOf(PatchByteArr)) = SizeOf(PatchByteArr) then
Exit; //退出Procedure
end;
end;
finally
{还原保存的旧指针位置,以便读取下个32k数据}
PatchFileStream.Position := OldPos;
end;
end;
end;
end;
until rCount = 0; //直到读不到内容(说明已经读完整个文件了}
finally
PatchFileStream.Free; //释放流对象
end;
end;
 
谢谢zqw0117!十分感谢!
 
后退
顶部