题目附件https://github.com/nyyyddddn/ctf/tree/main/%E7%BE%8A%E5%9F%8E%E6%9D%AF2023
risky_login
一道riscv64 小端序 栈溢出 ret2text的题目,最新的ida 9.0可以反编译riscv架构的程序,就不需要用难用的ghidra去分析了
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
https://ta0lve.github.io/posts/pwn/risc-v/0x01/#%E5%AF%84%E5%AD%98%E5%99%A8%E5%AD%A6%E4%B9%A0
在my input这里存在一个栈溢出,因为strlen只检查低一个字节的数据,那该溢出多少?通过阅读文档可以发现这个ret相当于jmp ra的作用,然后sd ra, 110h+var_s8(sp) 这个是存储返回地址,所以返回地址在栈底 - 0x8的位置,然后strcpy地址距离栈底 0x108,偏移0x100就刚刚好到返回地址那了
1 | .text:0000000012345786 my_input: # CODE XREF: main+7A↓p |
覆盖返回地址为backdoor,然后cat fl* 就能把flag读出来了
那riscv的程序如何调试呢?? qemu 有一个 -g的参数可以通过gdb-multiarch remote连上去打断点调试,在process开程序的时候加这个-g的参数就可以通过gdb连上去调试了
debug.sh
1 | gdb-multiarch -ex "set architecture riscv" \ |
exp
1 | from pwn import * |
shellcode
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
checksec 栈有rwx的权限
1 | lhj@lhj-virtual-machine:~/Desktop/ycb2023/shellcode$ checksec shellcode |
程序的逻辑是输入17个字节的数据(read 输入数据的逻辑有问题导致可以多输入一个字节) 然后判断输入的长度是否大于等于16个字节,判断每个字节是否是[79,95]这个范围的字节码,如果是就会执行这些shellcode
因为seccomp要满足一定的输入约束才会执行load bpf,所以直接seccomp dump是dump不出来沙箱规则的,得用python模拟符合输入约束的数据才能把沙箱规则dump出来,可以发现要用orw把flag读出来,然后rw对fd还有些约束,r的fd必须小于等于2,w的fd必须大于2,dup2(old_fd,new_fd)系统调用可以将一个fd重定向到一个新的fd,所以orw得用dup2重定向一下
1 | lhj@lhj-virtual-machine:~/Desktop/ycb2023/shellcode$ python test.py | seccomp-tools dump ./shellcode |
test.py
1 | from pwn import * |
限制的范围都是一些可以调整堆栈和寄存器的asm
1 | # for i in range(79,96): |
通过yes和no那两个字节输入那 写一个syscall,push和pop调整堆栈,把syscall给读到一个寄存器里,然后通过push写满整个栈,这样在shellcode执行到结尾的时候就会取到这个syscall,syscall read一次后用dup2 orw的shellcode把flag读出来就行了
exp
1 | from pwn import * |
easy_vm
1 | void __fastcall __noreturn main(int a1, char **a2, char **a3) |
题目实现了一个简单的vm,有任意地址写的功能,然后stack_memroy残留了一个指向main_arena的指针,可以通过这个指针算出libc_base和ld_base,glibc 2.31下有一个exit hook,具体是exit - > __run_exit_handlers -> _dl_fini - > (rtld_lock_default_lock_recursive & rtld_lock_default_unlock_recursive ) lock 和 unlock这两个函数的调用方式是在rtld_global找这两个函数的指针然后call,然后rtld_global又在ld的数据段上,如果能知道ld_base就能算出这两个指针的地址,所以可以通过写这两个指针为one gadget去劫持控制流
exp
1 | from pwn import * |