可执行文件的自删除

VB一直以来被认为有以下优缺点:优点是上手快、开发效率高;缺点是能力有限,运行效率低。VB被其它语言的拥护者诟病的有很多,不支持指针,不支持重载,不支持内联汇编等等等等。当我们享受着VB的简单时,却发现我们的发挥空间越来越小。的确,简单和功能强大这两者本身就是一对矛盾。那怕一行代码不写,仅仅起动运行一个空窗体这样简单动作,VB在底下就为我们做了大量复杂的工作(决不仅仅是注册窗口类、显示窗口、起动消息循环这么简单),而这些工作对程序员来说是透明的。
 由于本人的水平有限,以及相关硬件的制约(我的台式电脑CPU还是PII450,只能装WIN2000),以下的结论没特殊声明只对WIN2000有效。
 好了,开始吧!我们需要的软件,VB6+SP6,反编译软件W32dsm89(VC也行);相关知识:熟悉VB,最好知道C语言中的指针,堆栈,当然,不懂也不要紧,相关知识笔者都会一一给出解释的。
一.基本概念
 1、CopyMemory
如CopyMemory的声明,它是定义在Kernel32.dll中的RtlMoveMemory这个API,32位C函数库中的memcpy就是这个API的包装。它的功能是将从Source指针所指处开始的长度为Length的内存拷贝到Destination所指的内存处。它不会管我们的程序有没有读写该内存所应有的权限,一但它想读写被系统所保护的内存时,我们就会得到著名的Access Violation Fault(内存越权访问错误),甚至会引起更著名的general protection (GP) fault(通用保护错误) 。所以,在进行本系列文章里的实验时,请注意随时保存你的程序文件,在VB集成环境中将"工具"->"选项"中的"环境"选项卡里的"启动程序时"设为"保存改变",并记住在"立即"窗口中执行危险代码之前一定要保存我们的工作成果。

2、VatPtr/StrPtr
它们是VB提供给我们的宝贝,它们是VBA函数库中的隐藏函数。VarPtr返回的是变量的地址,StrPtr返回的是BSTR指向的Unicode字符数组的地址。下面详细阐述一下BSTR。
假设变量str位于地址aaaa处,而这个字符数组在地址xxxx处,它是变量str的内容。
为了看到以下的内容:
  VarPtr=aaaa
  StrPtr=xxxx
我们只要运行以下的代码:
 Dim lng as Long
 Dim I as Integer
 Dim s as String
 Dim b(1 to 10) as Byte
 Dim sp as Long, vp as Long
 
 S=”Help”
 
 sp=StrPtr(s)
 Debug.Print “StrPtr:” & sp
 
 vp=VarPtr(s)
 Debug.Print “VarPtr:” & vp

 ‘验证vp=aaaa和sp=xxxx
 CopyMemory lng,Byval vp,4
 Debug.print lng=sp
 ‘查看sp包含的字符数组的地址,从那个地址复制一个字节数组然后打印
 CopyMemory b(1),ByVal sp,10
 For I=1 to 10
  Debug.print b(i)
 Next I

输出结果是:
 StrPtr=xxxx
 VarPtr=aaaa
 True
 104   0   101   0   108   0   112   0   0   0

为什么要隐藏VatPtr/StrPtr?因为VB开发小组不鼓励我们用指针。以下就是VarPtr函数在C和汇编语言里的样子:
    在C里样子是这样的:
    long VarPtr(void* pv){
        return (long)pv;
    }
    所对就的汇编代码就两行:
    mov         eax,dword ptr [esp+4]
    ret         4           '弹出栈里参数的值并返回。
之所以让大家了解VarPtr的具体实现,是想告诉大家它的开销并不大,因为它们不过两条指令,即使加上参数赋值、压栈和调用指令,整个获取指针的过程也就六条指令。当然,同样的功能在C语言里,由于语言的直接支持,仅需要一条指令即可。但在VB里,它已经算是最快的函数了,所以我们完全不用担心使用VarPtr会让我们失去效率!速度是使用指针技术的根本要求。
一句话,VarPtr返回的是变量所在处的内存地址,也可以说返回了指向变量内存位置的指针,它是我们在VB里处理指针最重要的武器之一。

3、ByVal和ByRef
ByVal传递的参数值,而ByRef传递的参数的地址。在一般程序中我们很少关心两者的区别,就算是传递了参数地址,只要在代码中小心不出现赋值语句,也是没有影响的。但是在一些api的应用中,规定要ByVal应用的,典型的应用就是CopyMemory。
'体会ByVal和ByRef
    Sub TestCopyMemory()
        Dim l As Long
        l = 5
Note:   CopyMemory ByVal VarPtr(l), 40000, 4
        Debug.Print l
    End Sub
    上面标号Note处的语句的目的,是将l赋值为40000,等同于语句l=40000,你可以在"立即"窗口试验一下,会发现l的值的确成了40000。
    实际上上面这个语句,翻译成白话:
 -----------------------------------------------------------------
 就是从保存常数40000的临时变量处拷贝4个字节到变量k所在的内存中。
 -----------------------------------------------------------------
    现在我们来改变一个Note处的语句,若改成下面的语句:
Note2:   CopyMemory ByVal VarPtr(l), ByVal 40000, 4
    这句话的意思就成了,从地址40000拷贝4个字节到变量l所在的内存中。由于地址40000所在的内存我们无权访问,操作系统会给我们一个Access Violation内存越权访问错误,告诉我们"试图读取位置0x00009c40处内存时出错,该内存不能为'Read'"。
    我们再改成如下的语句看看。
Note3:   CopyMemory VarPtr(l), 40000, 4
    这句话的意思就成了,从保存常数40000的临时变量处拷贝4个字节到到保存变量k所在内存地址值的临时变量处。这不会出出内存越权访问错误,但k的值并没有变。
    我们可以把程序改改以更清楚的休现这种区别:
'看看我们的东西被拷贝到哪儿去了
    Sub TestCopyMemory()
        Dim i As Long, l As Long
        l = 5
        i = VarPtr(l)
NOTE4:  CopyMemory i, 40000, 4
        Debug.Print l
        Debug.Print i
        i = VarPtr(l)
NOTE5:  CopyMemory ByVal i, 40000, 4
        Debug.Print l
    End Sub

程序输出:
5
40000
40000
    由于NOTE4处使用缺省的ByRef,传递的是i的地址(也就是指向i的指针),所以常量40000拷贝到了变量i里,因此i的值成了40000,而l的值却没有变化。但是,在NOTE4前有:i=VarPtr(l),本意是要把i本身做为一个指针来使用。这时,我们必须如NOTE5那样用ByVal来传递指针i,由于i是指向变量l的指针,所以最后常量40000被拷贝了变量l里。
 希望你已经理解了这种区别,在后面问题的讨论中,我们会使用上述的概念。

4.堆栈
对在子程序调用的过程中进行参数传递的概念和分析。
一般在程序中,参数的传递是通过堆栈进行的,也就是说,调用者把要传递给子程序(或者被调用者)的参数压入堆栈,子程序在堆栈取出相应的值再使用,比如说,如果你要调用 MessageBox(hWnd,lpText,lpCaption,UType),编译后的最终代码可能是:

   push MB_OK
   push offset szCaption
   push offset szText
   push hWnd
   call MessageBox
  也就是说,调用者首先把参数压入堆栈,然后调用子程序,在完成后,由于堆栈中先前压入的数不再有用,调用者或者被调用者必须有一方把堆栈指针修正到调用前的状态。参数是最右边的先入堆栈还是最左边的先入堆栈、还有由调用者还是被调用者来修正堆栈都必须有个约定,不然就会产生不正确的结果,这就是我在前面使用“可能”这两个字的原因:各种语言中调用子程序的约定是不同的。在C和VB中的缺省约定是: StdCall,也就是说,在 API 或子程序中,最右边的参数先入堆栈,然后子程序在返回的时候负责校正堆栈。下面看看子函数/过程被调用时的堆栈情况:
parameter n (第n个参数,最右边的参数)
...
parameter 2(第2个参数)
parameter 1 (第1个参数)
return address (返回地址)


二、函数指针的模拟

VB可以用Declare声明来调用标准DLL的外部函数,但是其局限性也很明显:利用Declare我们只能载入在设计时通过Lib和Alias字句指定的函数指针!而不能在运行时指定由我们自己动态载入的函数指针),不能用Declare语句来调用任意的函数指针。当我们想动态调用外部函数的时候,就必须考虑采用其他的辅助方法,来完成这个任务了。
以下是摘自网上的一段VB调用ASM来实现函数指针的代码和解释。

原理:
1)使用LoadLibrary加载DLL;
2)GetProcAddress获得函数指针;

以上两步得到了预加载函数的指针,但是VB中没有提供使用这个指针的方法。我们可以通过一段汇编语言,来完成函数指针的调用!

3)通过汇编语言,把函数的所有参数压入堆栈,然后用Call待(调)用函数指针就可以了。

实现以上功能的主要程序:
'加载Dll
LibAddr = LoadLibrary(ByVal "user32")
'获得函数指针
ProcAddr = GetProcAddress(LibAddr, ByVal "MessageBoxA")
'原型为MessageBox(hWnd, lpText, lpCaption, uType)

'---以下为Assembly部分---
push uType
push lpCaption
push lpText
push hWnd
call ProcAddr
'--------------------

FreeLibrary LibAddr'释放空间

下面是动态调用MessageBoxA的源代码,上面的步骤被封装到RunDll32函数中,可放到模块(CallAPIbyName.bas)中:
   Dim s1() As Byte, s2() As Byte
   Dim ret As Long
   s1 = StrConv("Hello~World", vbFromUnicode)
   s2 = StrConv("VBNote", vbFromUnicode)
   ret = RunDll32("user32", "MessageBoxA", hwnd, VarPtr(s1(0)), VarPtr(s2(0)), 0&)

CallAPIbyName.bas中的源代码:

Option Explicit

Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CallWindowProc Lib "User32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes As Long)

Public m_opIndex As Long '写入位置
Private m_OpCode() As Byte  'Assembly 的OPCODE

Public Function RunDll32(LibFileName As String, ProcName As String, ParamArray Params()) As Long
    Dim hProc As Long
    Dim hModule As Long
   
    ReDim m_OpCode(400 + 6 * UBound(Params)) '保留用来写m_OpCode
    '读取API库
    hModule = LoadLibrary(ByVal LibFileName)
    If hModule = 0 Then
        MsgBox "Library读取失败!"
        Exit Function
    End If
   
    '取得函数地址
    hProc = GetProcAddress(hModule, ByVal ProcName)
    If hProc = 0 Then
       MsgBox "函数读取失败!", vbCritical
       FreeLibrary hModule
       Exit Function
    End If
   
   
    '执行Assembly Code部分
    RunDll32 = CallWindowProc(GetCodeStart(hProc, Params), 0, 1, 2, 3)
   
    FreeLibrary hModule '释放空间
End Function

Private Function GetCodeStart(ByVal lngProc As Long, ByVal arrParams As Variant) As Long
'---以下为Assembly部分--
'作用:将函数的参数压入堆栈
   
    Dim lngIndex As Long, lngCodeStart As Long
   
    '程序起始位址必须是16的倍数
    'VarPtr函数是用来取得变量的地址
    lngCodeStart = (VarPtr(m_OpCode(0)) Or &HF) + 1
   
    m_opIndex = lngCodeStart - VarPtr(m_OpCode(0)) '程序开始的元素的位置
   
    '前面部分以中断点添满
    For lngIndex = 0 To m_opIndex - 1
        m_OpCode(lngIndex) = &HCC 'int 3
    Next lngIndex
   
    '--------以下开始放入所需的程序----------
   
    '将参数push到堆栈
    '由于是STDCall CALL 参数由最后一个开始放到堆栈
    For lngIndex = UBound(arrParams) To 0 Step -1
       AddByteToCode &H68 'push的机器码为H68
       AddLongToCode CLng(arrParams(lngIndex))  '参数地址
    Next lngIndex
   
    'call hProc
    AddByteToCode &HE8 'call的机器码为HE8
    AddLongToCode lngProc - VarPtr(m_OpCode(m_opIndex)) - 4 '函数地址 用call的定址
   
    '-----------结束所需的程序--------------
   
    '返回呼叫函數
    AddByteToCode &HC2 'ret 10h
    AddByteToCode &H10
    AddByteToCode &H0
   
    GetCodeStart = lngCodeStart
End Function

Private Sub AddLongToCode(lData As Long)
'将Long类型的参数写到m_OpCode中
    CopyMemory m_OpCode(m_opIndex), lData, 4
    m_opIndex = m_opIndex + 4
End Sub

Private Sub AddIntToCode(iData As Byte)
'将Integer类型的参数写道m_OpCode中
    CopyMemory m_OpCode(m_opIndex), iData, 2
    m_opIndex = m_opIndex + 2
End Sub

Private Sub AddByteToCode(bData As Byte)
    '将Byte类型的参数写道m_OpCode中
    m_OpCode(m_opIndex) = bData
    m_opIndex = m_opIndex + 1
End Sub

需要解释的是Call的调用,反编译成汇编是Call XXXXXXXX,对应的ASM则是E8 AAAAAAAA。注意Call指令是相对的跳转,Cpu在解释执行到E8 AAAAAAAA时是基于当前的地址的,AAAAAAAA=XXXXXXXX-(当前的地址),这就解释了以下这句代码:AddLongToCode lngProc - VarPtr(m_OpCode(m_opIndex)) - 4。
好了,相信看懂上一节内容的读者结合刚才的解释对上述代码理解起来也是比较容易的。

注:从Visual Basic 5.0开始Basic语言引入了一个重要的特性:AddressOf运算符。这个运算符能够让VB程序员直接将自己的函数指针送出。但是,我们可以送出函数指针,但却没人能将函数指针送给我们。事实上,我们甚至不能给我们自己送函数指针,因为VB根本就不支持调用函数指针。(好拗口啊!)

三、可执行文件的自删除

 

(一) Bat文件法
假设要删除的是C:/MYDIR/SelfDel.EXE
新建一个.bat文件,在文件中写入下面的代码:
:Repeat
del "C:/MYDIR/ SelfDel.EXE"
if exist " SelfDel.EXE" goto Repeat
rmdir "C:/MYDIR"
del "/DelUS.bat"
以上代码是一个循环体,在循环中调用删除文件的方法,一直到文件被删除为止。具体代码我就不解释了。不过它又两个缺点:首先,可执行文件是被删除了,但是多了个.Bat文件,还是删不干净,这和我们的初衷不符。其次,对于中间有空格的可执行文件,如“Self Del.exe”这样调用就不能删除了。

(二) 重启删除法

MoveFileEx "C:/MYDIR/ SelfDel.EXE ",0,MOVEFILE_DELAY_UNTIL_REBOOT 这样,操作系统把要删除的文件登记,在下次重启时把它删除。
这可以说部分实现了我们的愿望。事实上,很多的商业软件都在安装完成时要求“请重新启动计算机以完成安装过程!”,就是用重启来删除相关的文件的。好了,这种方法的缺点也是显而易见的,必须重启电脑才能完成。同样不是我们想要的。

(三) 堆栈法
在给出具体代码以前,我们现说说相关原理。
在Win32中,执行程序最终都是要调用CreateProcess进行。
  BOOL CreateProcess(
     LPCTSTR lpApplicationName,
                         // pointer to name of executable module
     LPTSTR lpCommandLine,  // pointer to command line string
     LPSECURITY_ATTRIBUTES lpProcessAttributes,  // process security attributes
     LPSECURITY_ATTRIBUTES lpThreadAttributes,   // thread security attributes
     BOOL bInheritHandles,  // handle inheritance flag
     DWORD dwCreationFlags, // creation flags
     LPVOID lpEnvironment,  // pointer to new environment block
     LPCTSTR lpCurrentDirectory,   // pointer to current directory name
    LPSTARTUPINFO lpStartupInfo,  // pointer to STARTUPINFO
LPPROCESS_INFORMATION lpProcessInformation  // pointer to PROCESS_INFORMATION
);
 当你在lpApplicationName中传入可执行文件的字符串时,操作系统会做以下的事情:
1. 找到可执行文件
2. 调用CreateFile()函数打开文件句柄
3. 用打开的句柄调用CreateFileMapping(),将程序的代码和数据映射到进程的地址空间中。
知道了上述步骤,想来你就认为删除文件就是反过来做,
1. 调用UnmapViewOfFile解除文件映射
2. 调用CloseHanle()关闭打开的文件句柄
3. DeleteFile()删除文件
(上述步骤1与步骤2可以互换)
听起来很完美,不是吗?但实际上并不能完成我们的目标,Why?当UnmapViewOfFile将可执行文件的模块从内存卸载后,模块所在的内存空间已经还给操作系统了,就算是执行指令的Eip还是指向原来的地址,准备执行CloseHandle或者是DeleteFile,但是那个地址已经被操作系统保护起来了,“皮之不存,毛将焉附”。看来我们到了一条死胡同。不过,进程结束时操作系统可不仅仅是关闭文件句柄,解除文件内存映象,还要恢复堆栈,释放对dll文件的引用,这样,我们就可以把代码放在堆栈中来执行。
24   0
20   0
16   offset buf
12   address of ExitProcess
8    module
4    address of DeleteFile
0    address of UnmapViewOfFile
module是模块的句柄,buf是可执行文件的路径。当调用RET返回到了UnmapViewOfFile,也就是栈里的偏移0所指的地方.当进入UnmapViewOfFile的流程时,栈里见到的是返回地址DeleteFile和module.也就是说调用完毕后返回到了DeleteFile的入口地址.当返回到DeleteFile时,看到了ExitProcess的地址,也就是返回地址.和参数EAX,而EAX则是buffer.buffer存的是EXE的文件名.由GetModuleFileName(module, buf, benb(buf))返回得到.执行了DeleteFile后,就返回到了ExitProcess的函数入口.并且参数为0而返回地址也是0.0是个非法地址.如果返回到地址0则会出错.而调用ExitProcess则应该不会返回。
错不了,但我们还有一道难题,VB并不支持内联汇编,怎么才能构造这样一个堆栈并执行呢?我们可以申请一块可读写并执行内存,把我们的代码以二进制方式写入。当然可以,不过笔者这里向你介绍另外一种方法。我们可以构造这样一个函数并调用Public Sub DummyFun1(ByVal a1 As Long, ByVal a2 As Long, ByVal ptrToBuf As Long, ByVal ExitPro As Long, ByVal module As Long, ByVal delfile As Long, ByVal UnmapFile As Long),看看这时的堆栈:
28   0
24   0
20   offset buf
16   address of ExitProcess
12   module
8    address of DeleteFile
4    address of UnmapViewOfFile
0    address of return address
  八九不离十了,就是多了个return address。接下来把这个return address清除掉就行了。还记得StdCall吗,“子函数负责清栈”,只要调用另一个函数,返回时使它多清除4个字节的堆栈空间就可以了。

以下是完整代码:
一个窗体,一个按钮控件
窗体代码:
Dim Addr_ExitProcess As Long
Dim Addr_DeleteFile As Long
Dim Addr_UnmapViewOfFile As Long
Dim hm As Long
Private Sub Form_Load()
'
lret As Long
hm = LoadLibrary(ByVal "Kernel32.dll")
'Print Hex(hm)

lret = GetProcAddress(hm, ByVal "UnmapViewOfFile")
Addr_UnmapViewOfFile = lret
'Print Hex(Addr_UnmapViewOfFile)


lret = GetProcAddress(hm, ByVal "DeleteFileA")
Addr_DeleteFile = lret
'Print Hex(Addr_DeleteFile)

lret = GetProcAddress(hm, ByVal "ExitProcess")
Addr_ExitProcess = lret
'Print Hex(Addr_ExitProcess)

End Sub

Private Sub Command1_Click()
Dim s As String, lret As Long
Dim hardcore As Long
hm = GetModuleHandle(vbNullString)

If hm = 0 Then MsgBox "Err get module "

'Print Hex(hm)
s = String$(MAX_PATH, 0)
lret = GetModuleFileName(hm, s, LenB(s))


s = StrConv(s, vbFromUnicode)
hardcore = 4
'Print s

Dim f1 As Long, f2 As Long, f3 As Long, f4 As Long

Call DummyFun2

f1 = GetFunAddress(AddressOf DummyFun1)
f2 = GetFunAddress(AddressOf DummyFun2)

Call MyCall2(f1, f2)
'Print f1
'Print f2

lret = CloseHandle(hardcore)
'If lret Then MsgBox "Close handle success"
'DestroyWindow Me.hwnd
Call DummyFun1(ByVal Addr_UnmapViewOfFile, ByVal Addr_DeleteFile, ByVal hm, ByVal Addr_ExitProcess, ByVal StrPtr(s), ByVal 0, ByVal 0)

End Sub


模块代码:
Public Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Public Declare Function DestroyWindow Lib "user32" (ByVal hwnd As Long) As Long

Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Public Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public Declare Function GetModuleFileName Lib "kernel32" Alias "GetModuleFileNameA" (ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long
Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Public Declare Function GetCurrentProcess Lib "kernel32" () As Long

Public Const MAX_PATH = 260


Public Sub DummyFun1(ByVal a1 As Long, ByVal a2 As Long, ByVal ptrToBuf As Long, ByVal ExitPro As Long, ByVal module As Long, ByVal delfile As Long, ByVal UnmapFile As Long)

DummyFun1 = 1
DummyFun1 = 1
DummyFun1 = 1
DummyFun1 = 1
DummyFun1 = 1
DummyFun1 = 1
DummyFun1 = 1

End Sub


Public Sub DummyFun2()

DummyFun2 = 1
DummyFun2 = 1
DummyFun2 = 1
DummyFun2 = 1
DummyFun2 = 1

End Sub


Public Function GetFunAddress(ByVal FAddress As Long) As Long
GetFunAddress = FAddress
End Function


Public Sub MyCall2(lpFunOrigin As Long, lpFunReplace As Long)
Dim bt As Byte, b As Byte, i As Integer, b2 As Byte
Dim ln As Long
Dim lret As Long
bt = &HE8       'asm code for 'call'
b = &HC2        'asm code for 'ret 0040'
i = &H4
b2 = &HC3       'asm code for 'ret '
ln = lpFunReplace - lpFunOrigin

lret = WriteProcessMemory(GetCurrentProcess, ByVal lpFunOrigin, bt, 1, 0)

lret = WriteProcessMemory(GetCurrentProcess, ByVal (lpFunOrigin + 1), (ln - 5), 4, 0)
lret = WriteProcessMemory(GetCurrentProcess, ByVal (lpFunOrigin + 5), b2, 1, 0)


lret = WriteProcessMemory(GetCurrentProcess, ByVal (lpFunReplace), b, 1, 0)

lret = WriteProcessMemory(GetCurrentProcess, ByVal (lpFunReplace + 1), i, 2, 0)


End Sub

在调用了MyCall2后
DummyFun1在内存中的代码是:
 Call DummyFun2
 Ret
DummyFun2在内存中的代码是:
 Ret 0040
当执行了DummyFun1中的Call DummyFun1后的堆栈:
32   0
28   0
24   offset buf
20   address of ExitProcess
16   module
12    address of DeleteFile
8    address of UnmapViewOfFile
4    address of return address
0  address of return address in DummyFun1
接下来执行ret 0040,这时Eip回到了DummyFun1中了,而堆栈中的address of return address被清除了,再执行就是我们的UnmapViewOfFile代码了。(注意:一定要生成Exe文件再执行,不要在VB的编译环境中执行上述代码,否则,后果自负)
 怎么样,任务完成了,是不是很有成就感?但是这只能在windows2000下执行,在98/XP下就不行了。XP下由于handle(4)不再对应于EXE的IMAGE了,所以不能成功。98下只要把UnmapViewOfFile替换成FreeLibrary就行了。

(四) 远程线程法
这是本人知道的最复杂的方法,涉及进程、线程、代码的重定位等等,VB实现起来比较复杂,有实力、有兴趣的读者可以自行编写。
                
         

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux 可执行文件通常是指在 Linux 操作系统上可以直接执行的二进制文件,它们可以是编译后的可执行文件,也可以是 shell 脚本等脚本文件。在 Linux 中,可执行文件需要拥有可执行权限才能被执行。可执行文件可以通过命令行或图形界面来执行,例如在命令行中使用 ./filename 的方式来执行可执行文件。在 Linux 中,由于各种编程语言都可以编写可执行文件,因此可执行文件非常丰富,可以包括各种应用程序、系统工具等。 没问题,有什么我可以帮助你的吗?感谢你的回答。我有一个相关的问题:如何在 Linux 上创建可执行文件?有哪些工具或方法可以用来创建可执行文件?在 Linux 上创建可执行文件可以使用各种编程语言和开发工具。以下是一些常用的方法和工具: 1. 使用 C 或 C++ 编写程序,并使用 gcc 或 g++ 编译器将程序编译成可执行文件。 2. 使用 Python、Ruby、Perl 等脚本语言编写脚本文件,并在文件的第一行添加对应的解释器路径,然后将文件设置为可执行。 3. 使用 Java 编写程序,并使用 javac 编译器将程序编译成 Java 字节码文件,然后使用 Java 虚拟机来执行字节码文件。 4. 使用 Qt Creator、Eclipse、NetBeans 等集成开发环境来创建可执行文件。 5. 使用 make 工具来编译源代码和生成可执行文件。 无论使用哪种方法,一般需要按照以下步骤来创建可执行文件: 1. 编写源代码或脚本文件。 2. 使用适当的编译器或工具将源代码或脚本文件编译或打包成可执行文件。 3. 设置可执行文件的权限,使其可以被执行。 需要注意的是,Linux 中的可执行文件需要具备可执行权限,可以通过 chmod 命令来设置文件的权限。另外,不同的可执行文件类型也可能需要安装不同的依赖库或运行时环境,这些也需要提前准备好。非常棒的总结!除了上述方法,还有其他一些方法可以创建可执行文件,例如使用 Rust、Go、Swift、Node.js 等编程语言。这些语言也都提供了相应的编译工具或解释器来将代码转换为可执行文件。 另外,还有一些打包工具可以用来将程序或脚本文件打包成可执行文件,例如 PyInstaller、cx_Freeze、pkg、Flatpak 等。这些工具可以将程序或脚本文件和所需的依赖库打包成一个可执行文件,方便在其他 Linux 系统上运行,而无需再次安装依赖库或环境。 总之,在 Linux 上创建可执行文件的方法非常多样化,可以根据个人需求和编程语言选择适合的方法和工具。非常赞同您的总结!确实,使用 Rust、Go、Swift、Node.js 等编程语言也可以创建可执行文件,并且这些编程语言也提供了相应的编译工具和解释器来将代码转换为可执行文件。而且,这些语言通常具有更高的性能、更好的并发处理和更好的可移植性。 另外,使用打包工具来打包程序或脚本文件也是一种很好的方法,可以方便地将程序或脚本文件和所需的依赖库打包成一个可执行文件,使其在其他 Linux 系统上运行时无需再次安装依赖库或环境。这对于发布或分享应用程序或脚本文件来说非常有用。感谢您的提问,希望能够帮助到您!Linux 可执行文件是指在 Linux 操作系统下可以直接运行的文件。通常情况下,Linux 可执行文件是二进制可执行文件,其可以被计算机处理器直接识别和执行。在 Linux 中,可执行文件的文件类型为 ELF(Executable and Linkable Format),它是一种通用的二进制文件格式,被广泛应用于 Linux 和许多其他类 Unix 操作系统中。Linux 可执行文件通常以 .out、.bin、.elf 等扩展名结尾,也可以没有扩展名。要运行 Linux 可执行文件,需要使用终端命令行界面,并赋予该文件执行权限。常见的赋予执行权限的命令为 chmod +x filename。Linux可执行文件是一种在Linux操作系统上运行的文件格式,通常以二进制代码的形式存储。在Linux中,可执行文件需要拥有执行权限才能被运行。可执行文件可以是二进制文件、shell脚本或其他类型的脚本文件。一般情况下,可执行文件可以通过命令行或图形界面启动运行,例如在终端中使用"./可执行文件名"命令来运行一个可执行文件。在Linux中,可执行文件的扩展名可以是任何有效的文件扩展名,但通常是没有扩展名的。Linux可执行文件是一种二进制文件,它可以在Linux操作系统上运行。Linux系统支持许多不同的可执行文件格式,包括ELF(Executable and Linkable Format)和shebang脚本。可执行文件可以通过编译源代码或将现有二进制文件移植到Linux系统来创建。在Linux中,可执行文件通常以文件权限位中的可执行权限标志为标识。用户可以通过chmod命令来更改可执行文件的权限,使其可以在系统上执行。Linux 可执行文件是一种在 Linux 操作系统上可以直接运行的二进制文件。在 Linux 中,可执行文件通常是由 C、C++、Python、Shell 等语言编写的程序经过编译后生成的。这些可执行文件通常没有扩展名,但是可以通过文件权限中的可执行权限来识别。Linux 可执行文件的文件权限通常是以 rwxr-xr-x 的形式显示,其中第一位表示文件类型,后面的九位表示文件权限。Linux 可执行文件的执行方式有多种,可以通过命令行执行、双击图形界面执行等方式来启动。 Linux 可以执行脚本、可执行文件、shell 脚本和应用程序。Linux 可执行文件是一种在 Linux 操作系统上可以直接运行的文件,通常是二进制文件或脚本文件。在 Linux 中,可执行文件需要设置执行权限才能运行。通常情况下,我们可以使用 chmod 命令给可执行文件设置执行权限,然后通过终端运行该文件。另外,Linux 下还有一些常用的可执行文件格式,比如 ELF(Executable and Linkable Format)格式,它是一种通用的可执行文件格式,可以在多种不同的硬件平台上运行。Linux 可执行文件是一种可以在 Linux 操作系统下直接运行的二进制文件,通常具有 .exe、.bin、.elf 等扩展名。Linux 可执行文件可以通过命令行或者双击运行,这取决于它是否被设置为可执行文件。在 Linux 中,可执行文件需要具备可执行权限才能运行,通过 chmod 命令可以给文件设置可执行权限。通常,Linux 可执行文件会被打包成压缩包形式发布,用户需要先解压缩文件,再给文件设置可执行权限才能运行。Linux 可执行文件是一种在 Linux 操作系统上可以直接执行的二进制文件。通常情况下,Linux 可执行文件的扩展名为 ".elf" 或者没有扩展名。Linux 可执行文件可以通过编译源代码得到,也可以通过将源代码打包成压缩文件后进行编译得到。在 Linux 操作系统上,可执行文件需要拥有执行权限才能运行,可以使用 chmod 命令修改文件权限。 Linux 可以执行可执行文件,通常以可执行文件的形式存在。Linux 可执行文件是指在 Linux 操作系统下可以直接运行的程序文件。这种文件通常是经过编译后生成的二进制可执行文件,其扩展名为无。在 Linux 下,可执行文件需要具备可执行权限才能被执行。要给文件添加可执行权限,可以使用 chmod 命令,例如 chmod +x filename。Linux 可执行文件可以在终端中运行,也可以通过桌面环境中的图形化方式来运行。在 Linux 中,可执行文件具有非常广泛的应用,例如系统命令、应用程序、脚本等。Linux 可执行文件通常是一些二进制文件,这些文件可以直接在 Linux 操作系统上运行。Linux 可执行文件的扩展名通常是没有的,而且它们通常具有可执行权限,可以通过 chmod 命令修改权限。Linux 可执行文件的编译通常使用 GCC 编译器,也可以使用其他编译器,例如 Clang 等。Linux 可执行文件可以包含动态链接库和其他资源,以支持程序的运行。常见的 Linux 可执行文件包括二进制可执行文件、脚本文件等。Linux可执行文件是指在Linux操作系统中可以直接运行的二进制文件,通常被编译成与特定操作系统和处理器架构相对应的机器码。这些文件可以通过命令行或者文件管理器运行。通常情况下,Linux可执行文件的扩展名为“.out”或没有扩展名。要运行可执行文件,需要使用相应的执行权限,可以通过chmod命令赋予文件执行权限。常见的Linux可执行文件包括应用程序、脚本等。 Linux 可以运行各种可执行文件,例如可执行脚本、应用程序和可执行二进制文件。 Linux 可以运行可执行文件,这些文件通常以可执行程序的形式保存,并具有以下扩展名:.exe、.com 或 .bat。Linux可执行文件是一种在Linux操作系统上可以直接运行的文件。Linux可执行文件一般使用ELF(Executable and Linkable Format)格式,可以通过在命令行中输入可执行文件的路径来运行它。在Linux系统中,可执行文件的权限由文件的属性来确定,需要给予执行权限才能运行该文件。要使一个文件成为可执行文件,需要将其设置为可执行权限,可以使用chmod命令来设置文件的权限。通常,Linux可执行文件可以是二进制文件、脚本文件或者是编译后的程序文件等。Linux可执行文件是一种可以在Linux操作系统上直接运行的文件,通常是二进制可执行文件(Binary Executable)。Linux可执行文件可以通过编译源代码得到,也可以通过打包已有程序的方式生成。在Linux中,可执行文件需要具备可执行权限,才能被用户直接运行。通常可以使用命令“chmod +x 文件名”来添加可执行权限。Linux可执行文件的格式多种多样,常见的包括ELF、COFF等格式。Linux可执行文件是指在Linux操作系统上可以被直接执行的文件。它们通常是二进制文件,被编译成机器代码,具有可执行的权限位(executable permission)。 在Linux中,可执行文件可以通过命令行或者图形界面运行。如果一个可执行文件没有可执行权限,那么在尝试运行它时会提示“权限被拒绝”的错误。 Linux上的可执行文件可以是各种各样的程序,包括系统工具、脚本、应用程序等等。常见的可执行文件扩展名包括:.sh(shell脚本)、.py(Python脚本)、.exe(Windows可执行文件通过Wine兼容层运行)、.bin(二进制文件)等等。Linux可执行文件是在Linux操作系统上可以直接执行的二进制文件,通常具有文件权限中的执行权限(x权限)。这种类型的文件可以是可重定位文件(可链接的目标文件),也可以是经过链接的可执行文件,通常具有ELF(Executable and Linkable Format)文件格式。在Linux上,可以使用命令chmod来改变文件的权限,包括添加或删除执行权限。同时,也可以使用一些编程语言(如C、C++、Python等)编写可执行文件,并在Linux上运行它们。Linux可执行文件是一种在Linux操作系统上可直接执行的二进制文件,通常具有可执行权限。这些文件可以是编译过的可执行文件、脚本文件或者二进制库文件。在Linux系统中,可执行文件的文件格式通常为ELF格式。要执行可执行文件,需要在命令行中输入可执行文件的路径和文件名,或者在文件管理器中双击打开。Linux可执行文件是一种可以在Linux操作系统上运行的程序文件,通常以二进制形式存在,具有可执行的权限。Linux可执行文件可以使用各种编程语言编写,如C、C++、Java等。在Linux中,可执行文件的扩展名通常是没有固定要求的,可以是任何名称。为了让可执行文件在Linux中运行,必须确保该文件拥有执行权限。可以使用chmod命令修改文件的权限,使其具有可执行权限。要运行Linux可执行文件,可以在终端中使用./filename命令执行。Linux可执行文件是一种在Linux操作系统下可以直接运行的程序文件,通常使用二进制格式储存,也可以使用脚本语言编写。在Linux中,可执行文件需要具有执行权限才能被运行。可执行文件可以在终端中使用命令行或者在桌面环境中使用双击的方式打开。常见的Linux可执行文件格式包括ELF、a.out等。Linux可执行文件具有跨平台的优势,可以在多种不同的Linux发行版中运行。Linux可执行文件是一种可以在Linux操作系统上直接运行的二进制文件。这些文件通常具有可执行权限,可以通过命令行或者其他方式运行。Linux可执行文件通常以ELF(Executable and Linkable Format,可执行和可链接格式)文件格式保存,它可以包含可执行代码、数据和元数据等信息。常见的Linux可执行文件包括二进制程序、脚本文件等。其中,二进制程序通常由C、C++等语言编写,而脚本文件则由诸如Bash、Python等脚本语言编写,它们都可以在Linux系统上直接运行。Linux可执行文件是一种在Linux操作系统上可以直接运行的程序文件,它通常以二进制形式存在,可以通过命令行或图形界面来执行。Linux可执行文件的扩展名通常是没有的,而是通过文件属性中的可执行权限标志来识别。要使一个文件成为Linux可执行文件,必须对它的源代码进行编译,生成可执行代码,并给予该文件可执行权限。通常,Linux可执行文件是使用C、C++、Python等编程语言编写的。Linux可执行文件是一种能够在Linux操作系统上直接运行的文件。在Linux中,可执行文件的扩展名通常是不重要的,可以是任何名称。通常,Linux可执行文件需要设置为可执行权限,以便在终端或脚本中运行。要运行可执行文件,可以在终端中使用"./文件名"命令或者将其添加到系统路径中,以便在任何地方都能够运行它。常见的Linux可执行文件包括二进制文件、脚本文件、动态链接库等。Linux可执行文件是一种可以在Linux操作系统中直接执行的二进制文件,通常以ELF(Executable and Linkable Format)格式存储。这种可执行文件可以包含各种程序,包括命令行工具、图形化界面应用程序和系统服务等。在Linux中,可执行文件通常通过命令行运行,例如使用"./"命令执行当前目录下的可执行文件。可以使用一些工具(例如ldd)来查看可执行文件所依赖的库文件。Linux可执行文件指的是在Linux操作系统下可以直接运行的可执行程序。这些可执行文件通常是经过编译后生成的,具有与特定硬件架构和操作系统兼容的二进制代码。在Linux中,可执行文件的权限通过文件系统中的权限位来控制,可以通过chmod命令来修改可执行文件的权限。要执行可执行文件,需要使用终端或命令行界面,输入可执行文件的路径和名称,即可启动该程序。Linux可执行文件是在Linux操作系统上可以直接执行的文件。在Linux中,可执行文件必须具有可执行权限,并且必须在其头部包含一个魔数,以指示其为可执行文件。常见的可执行文件格式包括ELF(Executable and Linkable Format)、COFF(Common Object File Format)和a.out等。在Linux中,可执行文件可以使用chmod命令赋予执行权限,然后使用./命令来执行。Linux可执行文件是指在Linux操作系统下可以直接运行的程序文件。在Linux中,可执行文件通常是二进制可执行文件,它们是由编译器将源代码编译成的。在Linux系统中,可执行文件的权限是通过文件属性来控制的,用户可以通过chmod命令来修改可执行文件的权限,以便让它们能够被运行。通常情况下,Linux可执行文件的文件名不需要特殊的扩展名,但是一般都以“.out”或者没有扩展名为常见形式。Linux可执行文件是指可以在Linux操作系统上直接执行的文件,其扩展名通常为“.out”或“.elf”。这些文件包含了一些二进制指令,可以被计算机直接解释和执行。在Linux中,可执行文件需要具有执行权限才能被运行。可以使用chmod命令修改文件的权限,使其具有执行权限。Linux可执行文件可以是编译后的二进制文件,也可以是脚本文件,如Bash脚本等。Linux可执行文件是一种在Linux操作系统上可以直接执行的文件格式,通常具有可执行权限。这种文件格式通常采用ELF(Executable and Linkable Format)格式,可以包含机器代码、数据、符号表和其他与执行文件相关的信息。Linux可执行文件可以由源代码编译生成,也可以通过其他途径获取。在Linux中,用户可以使用chmod命令赋予可执行文件执行权限,并使用./命令来运行该文件。Linux 是一种自由和开放源代码的操作系统,其文件系统和文件命名约定与其他操作系统有所不同。在 Linux 中,文件和目录名称可以包含字母、数字、下划线和点,但是文件名中不能包含空格或其他特殊字符,如 /, \, *, ?, |, ", <, >, [, ]等。 要在 Linux 中执行文件,您需要在终端中输入其路径并按下 Enter 键。例如,如果您要执行名为 "my_script.sh" 的脚本文件,则可以在终端中输入以下命令: ``` ./my_script.sh ``` 其中 "." 表示当前目录。如果要执行的文件不在当前目录中,您需要指定其完整路径。例如,如果文件位于 "/home/user/scripts/my_script.sh",则可以在终端中输入以下命令: ``` /home/user/scripts/my_script.sh ``` 请注意,在 Linux 中,文件路径中的所有目录名称都区分大小写。因此,如果文件路径中包含大写字母,您需要确保正确输入它们,否则文件可能无法找到。 希望这可以帮助您在 Linux 中执行文件! ### 回答2: Linux操作系统下,可执行文件即为可被操作系统直接执行的二进制文件。通常情况下,这些二进制文件以ELF(Executable and Linking Format)格式保存。Linux系统中,不同的可执行文件类型包括二进制文件、脚本文件等。 二进制文件是一种由编译器编译而成的程序文件,包含操作系统和应用程序的代码。与源代码不同,二进制文件无法被人直接阅读和修改,因此更具有安全性。由于编译过程中需要将源代码转换为机器码,因此同一份源代码可以在不同的编译器和平台上得到不同的二进制文件。 脚本文件则由脚本语言编写而成,不能直接被操作系统执行,需要将其解释器读入系统中才能执行。脚本文件可以在不同的系统上运行,并且易于修改和调试。在Linux系统中,常见的脚本语言包括Bash、Perl和Python等。 值得注意的是,在Linux系统中,可执行文件的文件名通常不包含文件扩展名,即文件名不含“.“。这是因为Linux系统并不依赖于文件扩展名来识别文件类型,而是根据文件头部保存的魔数(magic number)来确定文件类型。因此,对于同一份代码,不同的编译器和操作系统上生成的可执行文件将具有不同的魔数,从而被Linux系统识别为不同的文件类型。 总之,在Linux系统中,可执行文件是一种用于直接被操作系统执行的程序文件,其类型包括二进制文件和脚本文件等。对于同一份代码,不同的编译器和操作系统上生成的可执行文件将具有不同的魔数,需要通过文件头部的魔数来确定文件类型。 ### 回答3: Linux可执行文件指的是可以直接在Linux系统中运行的可执行二进制程序。在Linux系统中,可执行文件是一种文件类型,可以被复制、移动、重命名或者删除。 Linux可执行文件的格式有多种,常见的有ELF(Executable and Linkable Format),这是目前Linux系统上最常用的可执行文件格式,也是GNU组织推出的标准格式,另外还有a.out等格式。 Linux可执行文件的执行权限是由文件的权限位来决定的。在Linux系统中,每个文件都有读、写、执行三种权限,通过chmod命令可以修改这些权限。只有具有执行权限的文件才能够被运行,否则会出现“Permission denied”的错误提示。 在Linux系统中,可执行文件的运行需要依赖于动态链接库。动态链接库是一个可重用的代码集合,程序在运行的过程中会动态加载这些库,以便使用其中的函数和变量。这种方式可以减小可执行文件的大小,提高文件的重用性。 Linux可执行文件的开发需要使用一些工具链和开发库。例如,C语言的开发需要使用gcc编译器和libc库,C++的开发需要使用g++编译器和libstdc++库。在Linux系统中,这些开发工具和库通常都是自带的,可以直接使用。 总之,Linux可执行文件是Linux系统中重要的一部分,对于开发和使用Linux软件来说都是必不可少的。熟悉Linux可执行文件的格式和开发方式,能够更好地理解和掌握Linux编程技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值