在编译时已知汇编指令的机器码该如何获取?

5

我希望能够将一行汇编代码转换为shellcode。例如:

CALL EBX

我该如何操作,以及如何正确转换这个 shellcode,使我可以在 Delphi 应用程序中将其存储在变量中。

var ShellCodeArray:  array[0..3] of Byte = ($55,$8B,$EC,$81);

2
如果您正在使用具有内置汇编程序的语言,为什么不使用该汇编程序呢? - user743382
各种原因。但感谢您的回复。 - Josh Line
1
你在寻找类似这样的东西吗? http://www.delphibasics.info/home/delphibasicssnippets/executingpreparedshellcodeindelphi - bummi
只有这样才对:const CALLEBX: array [0..1] of Byte = ($FF, $D3);,如果你真的意味着一个变量而不是一个常量,则为CALLEBX := TBytes.Create($FF,$D3); - David Heffernan
@RobKennedy 我对问题的理解不是这样,也就是其他人到目前为止没有发现这样做。我并不是建议分发Delphi汇编程序,而是建议将asm call ebx end;放入Delphi源文件中,并检查生成了什么。 - user743382
显示剩余3条评论
3个回答

5

如果我理解正确,您想要使用Delphi内置汇编语言获取单个汇编指令CALL EBX的机器码。

function CodeSize: Integer;
asm
    lea EAX, @@end
    lea EDX, @@start
    sub EAX, EDX
    JMP @@end
@@start:
    call EBX
@@end:
end;

procedure Code;
asm
    call EBX
end;

function CodeToBytes: TBytes;
var
  I, N: Integer;
  P: PByte;

begin
  N:= CodeSize;
  SetLength(Result, N);
  P:= @Code;
  for I:= 0 to N - 1 do begin
    Result[I]:= P^;
    Inc(P);
  end;
end;

你应该这样编写代码,以便只有一个汇编块的实例。 - David Heffernan
最好一次性地写好某件事,而不是写两次。这也非常容易做到。 - David Heffernan
@DavidHeffernan - 上面的asm过程没有开场白。 - kludg

2

就我个人而言,我会避免重复Serg的回答,并将其写成这样:

function CodeToBytes: TBytes;
var
  StartAddr, EndAddr: Pointer;
begin
  asm
    LEA EAX, @@start
    MOV StartAddr, EAX
    LEA EAX, @@end
    MOV EndAddr, EAX
    JMP @@end
  @@start:
    CALL EBX
  @@end:
  end;
  SetLength(Result, Integer(EndAddr)-Integer(StartAddr));
  Move(StartAddr^, Pointer(Result)^, Length(Result));
end;

很显然,您可以在起始和结束标签之间放置任何内容。

1

只需在代码后面使用一个虚拟过程并将两者相减,例如:

procedure Code
asm
   call ebx;
end;

procedure CodeEnd;
asm end;

CodeSize := DWORD_PTR(@CodeEnd) - DWORD_PTR(@Code);
CopyMemory(@MyByteArray[0], @Code, CodeSize);

请注意,在代码中,只要不调用其他代码(函数/过程/rtl),您也可以使用Delphi代码而不是汇编代码。
编辑:作为对Serg和David Heffernan评论的回答,我在Delphi 2010发布模式下验证了结果。
我使用了以下代码:
procedure Code;
asm
  mov eax, 0;
end;

procedure CodeEnd;
asm end;


procedure TForm4.Button1Click(Sender: TObject);
begin
  ShowMessageFmt('CodeSize=%d', [DWORD_PTR(@CodeEnd) - DWORD_PTR(@Code)]);
end;

报告的CodeSize为8字节,我随后使用Ida Pro(可执行文件反汇编器)进行了验证:

.text:004B3344                 Code            proc near              
.text:004B3344                                                        
.text:004B3344 B8 00 00 00 00                  mov     eax, 0
.text:004B3349 C3                              retn
.text:004B3349                 Code            endp
.text:004B3349
.text:004B3349                 ; -----------------------------
.text:004B334A 8B C0                           align 4

所以在这个例子中,mov eax,0是5个字节(B8 00 00 00 00),retn(由编译器添加)是1个字节(C3),align 4是2个字节(8B C0),总共是8个字节。


不确定那样是否安全,因为编译器可以对虚拟过程的起始点进行对齐,这就是我使用 CodeSize 函数的原因。 - kludg
@Serg:如果它对齐了(编译器会这样做吗?我从未见过),那么我肯定希望编译器插入NOP指令 :-) - Remko
好的,nop是可以的,但即使它是随机字节也没关系,因为过程代码以retn结尾。 - Remko
更重要的是,您的代码应包括“ret”指令,并且无论哪种情况下,CodeSize值都是错误的。 - kludg
如果你的代码包含 RET,则与接受的答案不同。 - David Heffernan
我现在没有编译器访问权限,所以无法检查,但我认为编译器会添加一个retn来从过程中返回。代码大小可能(在对齐的情况下)包括一些nop,但对于执行它来说这不是问题。 - Remko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接