[英]How can I run a Windows shell command in an assembly program, without needing a C library?
我正在学习汇编,但我一直在学习 Linux,自从我了解 C/C++ 以来,它与 C/C++ 非常交织。
但是,现在,我想在我的 Windows 计算机上编写一个汇编程序,该程序调用一个运行 shell 命令的函数,并且希望不包含任何库,因为我希望这个可执行文件尽可能小。 (我制作的一个包含cstdlib
的简单 C++ 可执行文件是 32 KB,而我几乎没有输入任何代码。)
我想基本上写这个程序,但在汇编中:
#include <cstdlib>
int main()
{
system("echo ABCDEFG> msg.txt");
system("type msg.txt");
return 0;
}
到目前为止,我已经安装了 NASM 和 MinGW,并且我正在尝试使用它们 [编辑:我现在正尝试使用 MASM,与 Visual Studio 一起使用],但没有任何效果,我认为这是因为 Windows 拥有来自 Microsoft 的自己的东西显然不在 Linux 中,但我遇到的在线资源都没有成功编译我想要做的事情。 它似乎也不理解语句extern system
,所以我认为这可能是 C/C++ 库的东西(来自cstdlib
)。
此外,在某些情况下,我遇到了一个错误,它说“字符常量太长”。 我假设它指的是字符串; 在实际程序中,要回显的字符串比上面示例中的要长得多。
生成的 .obj 文件显然也有问题。
因此,基本上,我所需要的只是如何将上述程序转换为 Windows 程序集,以及如何让 NASM 和 MinGW 将其转换为可执行文件而不会出现错误。
编辑:经过几天的研究,我决定我不知道自己做错了什么,也不知道什么是对的。
我现在尝试过 Visual Studio,但又一次,它不理解extern system
。
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode: DWORD
.data
Var db 'dir && pause', 0
.code
extern system
main PROC
push ebp
mov ebp, esp
sub esp, 32
lea ecx, [Var]
call system
xor eax, eax
INVOKE ExitProcess, 0
main ENDP
END main
正如我在评论中提到的,Windows 确实有一个系统命令。 它位于 MSVCRT.dll 中,因此您需要与 msvcrt.lib 链接以解析外部引用。
您从“第 11 行”得到的错误是由于错误地定义了外部,这很奇怪,因为您在几行之前正确地对 ExitProcess(也是外部)执行了相同的操作。
32k 听起来可能很大,但 Windows 现在可以在 4k 页面上工作,允许它设置页面保护位(只读页面、可执行页面等)。 一页用于可执行代码,一页用于数据,一页用于常量、导入、导出、调试信息、异常信息等。按照今天的标准,8 页似乎并不那么离谱。
但正如我也提到的,如果有一些令人信服的理由,可以使用一些技巧来减小可执行文件的大小。 此示例(使用nasm.exe doit.asm -o doit.exe
使用 NASM 版本 2.15.05 构建)为 1,184 字节。 当您运行它时,似乎什么都没有发生(没有打开 cmd 窗口)而且速度非常快。 但是您会看到 foo.xxx 被创建并包含来自代码中嵌入的dir > foo.xxx
命令的输出。
这里有很多链接器通常会为您处理的 gobbledygook。 有趣的部分从ENTRY:
开始。 cmd 在代码末尾附近定义。
此代码的隐秘性与小尺寸相结合,为使用此代码进行恶作剧提供了可能性。 我会假设这不是你的意图。
; Check for NASM version at least 2.15.05
%if __?NASM_VERSION_ID?__ < 0x0020F0500
%error "Newer version of nasm required"
%endif
%define RoundTo(a, b) ((((a) + ((b) - 1)) / (b)) * (b))
%define Stringify(&val) val
%macro NameEntry 2
%1__ dw %2
db Stringify(%1), 0
%endmacro
salign equ 1000h ; Page size in memory
falign equ 200h ; Page size in file
imageBase equ 400000h ; Requested load address
BITS 16
section headers start=0
startoffile:
; MZ header https://wiki.osdev.org/MZ
dw "MZ" ; Signature
dw (dosBlkSize - mzStructSize) % 512 ; Bytes on last page
dw RoundTo(dosBlkSize, 512) / 512 ; # of 512 byte pages
dw 0 ; Relocation items
dw RoundTo(mzStructSize, 16) / 16 ; Header size in paragraphs
dw 0 ; Minimum allocation
dw 0xffff ; Maximum allocation in paragraphs (1M).
dw 0 ; Initial SS
dw 0xb8 ; Initial SP
dw 0 ; Checksum
dw 0 ; Initial IP
dw 0 ; Initial CS
dw 0 ; Relocation table
dw 0 ; Overlay
dq 0 ; Reserved
dw 0 ; OEM identifier
dw 0 ; OEM info
times 20 db 0 ; Reserved
dd PEHDR ; PE header start
mzStructSize equ $ - $$ ; aka 64
dosstartcode: ; Print the error and exit
push cs
pop ds
mov dx, dosmsg - dosstartcode
mov ah, 0x9
int 0x21 ; Show string up to '$'
mov ax, 4c01h
int 0x21 ; Exit process with error code 1
dosmsg db `This program cannot be run in DOS mode.\r\r\n$`
dosBlkSize equ $ - $$
ALIGN 16
; From https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
PEHDR:
dd "PE" ; signature
dw 8664h ; machine x64
dw SectionsCount ; # of sections
dd __POSIX_TIME__ ; timedatestamp
dd 0 ; pointer to symtab - deprecated
dd 0 ; # symtab entries
dw opthdrSize ; size of optional header
dw 2h ; flags: Executable
OPTHDR:
dw 20Bh ; magic
db 0 ; maj linker ver
db 0 ; minor linker ver
dd codeSizeS ; total memory code size
dd rdataSizeS ; total memory init data size
dd 0 ; total uninit data size
dd ENTRY ; entrypoint RVA
dd section..text.start ; base of code in file
dq imageBase ; image base
dd salign ; section address alignment
dd falign ; section pos alignment
dw 10 ; major OS version
dw 0 ; minor OS version
dw 0 ; major image ver
dw 1 ; minor image ver
dw 6 ; major subsystem ver
dw 2 ; minor subsystem ver
dd 0 ; win32 version value = 0
dd fileSize ; size of image in memory
dd headersSizeF ; size of DOS stub + PE header + sections
dd 0 ; checksum
dw 2 ; subsystem: GUI
dw 8160h ; dll characteristics: HighEntropy, Relocatable, NX, TS aware
dq 100h ; max stack
dq 100h ; min stack
dq 100h ; max heap
dq 100h ; min heap
dd 0 ; loader flag
HeaderDirectories:
dd HeaderDirectoryCount ; number of directories
; Address, Size
dd 0, 0 ; Export
dd ImportsDir, ImportsDirSize ; Import
dd 0, 0 ; Resource
dd 0, 0 ; Exception
dd 0, 0 ; Certificates
dd 0, 0 ; Base Relocation
dd 0, 0 ; Debug
dd 0, 0 ; Architecture
dd 0, 0 ; Global Pointer
dd 0, 0 ; Thread Storage
dd 0, 0 ; Load Configuration
dd 0, 0 ; Bound Import
dd IATStart, IATSize ; Import Address Table
dd 0, 0 ; Delay Import
dd 0, 0 ; COM Descriptor
dd 0, 0 ; Reserved
HeaderDirectorySize equ $ - HeaderDirectories
HeaderDirectoryCount equ HeaderDirectorySize / 8
opthdrSize equ $ - OPTHDR
startOfSections:
dq ".text"
dd codeSizeS ; size in memory pages
dd ENTRY ; addr RVA (memory offset)
dd codeSize ; length
dd section..text.start ; pos (file offset)
dd 0 ; relocations addr
dd 0 ; linenum addr
dw 0 ; relocations count
dw 0 ; linenum count
dd 030000020h ; flags: Code, Shared, Execute Only
dq ".rdata"
dd rdataSizeS ; size in memory pages
dd RDATA ; addr RVA (memory offset)
dd rdataSize ; length
dd section.rdata.start ; pos (file offset)
dd 0 ; relocations addr
dd 0 ; linenum addr
dw 0 ; relocations count
dw 0 ; linenum count
; Take advantage of the fact that the loader cheats and
; writes imports to readonly pages @ startup
dd 040000040h ; flags: Initialized Data, Read Only
SectionsSize equ $ - startOfSections
SectionsCount equ SectionsSize / 40
ALIGN 16
headersSizeF equ RoundTo($ - $$, falign)
headersSizeS equ RoundTo($ - $$, salign)
BITS 64
DEFAULT REL ; so we don't have to keep adding imageBase
SECTION .text vstart=headersSizeS align=falign follows=headers
ENTRY:
sub rsp, 28h
lea rcx, [cmd] ; LPCSTR lpCmd
call [system]
; Use the return value from the call to system
mov ecx, eax
call [ExitProcess]
codeSize equ $ - $$
codeSizeS equ RoundTo(codeSize, salign)
SECTION rdata vstart=headersSizeS+codeSizeS align=falign
RDATA:
IATStart:
; Import Address Table
Kernel32TableA:
ExitProcess dq ExitProcess__
MSVCRTTableA:
system dq system__
IATSize equ $ - IATStart
ImportsDir:
dd Kernel32TableL, 0, 0, kernel32dll, Kernel32TableA
dd MSVCRTTableL, 0, 0, MSVCRTdll, MSVCRTTableA
ImportsDirSize equ $ - ImportsDir
; Kernel32 Import Lookup Table
Kernel32TableL:
dq ExitProcess__
dq 0 ; end of table marker
; Name, Hint
NameEntry ExitProcess, 164h
; MSVCRT Import Lookup Table
MSVCRTTableL:
dq system__
dq 0 ; end of table marker
; Name, Hint
NameEntry system, 4e0h
kernel32dll db "KERNEL32.dll", 0
MSVCRTdll db "MSVCRT.dll", 0
; Constant data
cmd db "dir > foo.xxx", 0h
ALIGN 16
rdataSize equ $ - RDATA
rdataSizeS equ RoundTo(rdataSize, salign)
fileSize equ RDATA + rdataSizeS
您使用 extern 没有任何意义,masm 中的 extern 意味着您正在调用 API 函数(如 c 中的 printf)而不是可执行文件。 第一条评论是正确的。 在 win32 api 中,从另一个进程启动的进程需要 CreateProcessA 或 CreateProcessW 函数,并且需要设置一个 PROCESS_INFORMATION 结构来传递它。 您可能还必须设置其他结构,我不记得了,然后如果您手动将参数推送到堆栈中,或者只是调用它并让汇编器处理它,您就会调用它。 我建议获取一份 masm32 的副本,这应该很容易,其中或互联网上可能已经有一个示例。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.