-
Notifications
You must be signed in to change notification settings - Fork 19
/
drinkme.c
306 lines (235 loc) · 7.4 KB
/
drinkme.c
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/**********************************************************************************************************************
*
* drinkme
*
*
* emptymonkey's shellcode testing tool
* 2014-10-31
*
*
* The drinkme tool takes shellcode as input on stdin, and executes it. This is only intended as a simple framework
* for testing shellcode.
*
*
* The formats supported are:
* * "\x##"
* * "x##"
* * "0x##"
* * "##"
*
* The following characters will be ignored:
* * All whitespace, including newlines. (If entering directly through a tty, remember to hit ctrl+d to send EOF.)
* * '\'
* * '"'
* * ','
* * ';'
* * C and C++ style comments will be appropriately handled.
*
**********************************************************************************************************************/
#include <ctype.h>
#include <errno.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <unistd.h>
/* As we step through the input, we will maintain a state machine to handle comments. */
#define NO_COMMENT 0 /* Base case. */
#define COMMENT_OPEN 1 /* Case: '/' */
#define COMMENT_C 2 /* Case: '/' + '*' */
#define COMMENT_C_CLOSE 3 /* Case: '/' + '*' + ... + '*' */
#define COMMENT_CPP 4 /* Case: '/' + '/' */
char *program_invocation_short_name;
char *CALLING_CARD = "@emptymonkey - https://github.com/emptymonkey";
void usage(){
fprintf(stderr, "\nusage: %s [-p] [-h]\n", program_invocation_short_name);
fprintf(stderr, " -p Print the formatted shellcode. Don't execute it.\n");
fprintf(stderr, " -h Print this help message.\n");
fprintf(stderr, "\nExample: cat hello_world.x86_64 | %s\n\n", program_invocation_short_name);
exit(-1);
}
int main(int argc, char **argv){
/* Used for testing return conditions. */
int retval;
/* sc is the buffer for holding the raw shellcode. */
char *sc;
unsigned int sc_count = 0;
/* This is a temp buffer to hold a two-byte sequence. Once filled out, it
will be used with strtol() to push two bytes to the shellcode buffer. */
char byte[5];
unsigned int byte_count = 0;
/* (pagesize * num_pages) will be the allocated size of the sc buffer. */
unsigned int pagesize, num_pages;
/* State machine variable. */
unsigned int comment = 0;
/* For cli options. */
int opt;
int execute = 1;
/* Temp variables used in the manual reallocing of space. */
char *tmp_ptr;
unsigned int tmp_uint;
/* Setup a posix version of the GNU program_invocation_short_name. */
if((program_invocation_short_name = strrchr(argv[0], '/'))){
program_invocation_short_name++;
}else{
program_invocation_short_name = argv[0];
}
while ((opt = getopt(argc, argv, "ph")) != -1) {
switch (opt) {
case 'p':
execute = 0;
break;
case 'h':
default:
usage();
}
}
memset(byte, 0, sizeof(byte));
pagesize = getpagesize();
num_pages = 1;
/* Since we will be changing our mapping later to be executable, I'd prefer not to use malloc() / calloc(),
but rather just grab memory directly with mmap(). This also fits our strategy of having a pagesized buffer. */
errno = 0;
sc = (char *) mmap(NULL, (num_pages * pagesize), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(errno){
error(-1, errno, "mmap(NULL, %d, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)", (num_pages * pagesize));
}
/* Step through the input. */
while((retval = read(STDIN_FILENO, &(byte[byte_count]), 1)) == 1){
/* Handle the comments case. */
if(byte[byte_count] == '/'){
switch(comment){
case NO_COMMENT:
comment = COMMENT_OPEN;
byte[byte_count] = '\0';
continue;
case COMMENT_OPEN:
comment = COMMENT_CPP;
byte[byte_count] = '\0';
continue;
case COMMENT_C_CLOSE:
comment = NO_COMMENT;
byte[byte_count] = '\0';
continue;
case COMMENT_CPP:
case COMMENT_C:
break;
}
}
if(byte[byte_count] == '*'){
switch(comment){
case COMMENT_OPEN:
comment = COMMENT_C;
byte[byte_count] = '\0';
continue;
case COMMENT_C:
comment = COMMENT_C_CLOSE;
byte[byte_count] = '\0';
continue;
case NO_COMMENT:
case COMMENT_CPP:
case COMMENT_C_CLOSE:
break;
}
}
if(byte[byte_count] == '\n'){
switch(comment){
case COMMENT_OPEN:
case COMMENT_CPP:
comment = NO_COMMENT;
break;
case COMMENT_C_CLOSE:
comment = COMMENT_C;
break;
case NO_COMMENT:
case COMMENT_C:
break;
}
}
if(comment == COMMENT_C || comment == COMMENT_CPP){
byte[byte_count] = '\0';
continue;
}
/* Filter the characters we explicitly ignore. */
if( \
isspace(byte[byte_count]) || \
byte[byte_count] == '"' || \
byte[byte_count] == ',' || \
byte[byte_count] == ';' || \
byte[byte_count] == '\\' \
){
byte[byte_count] = '\0';
continue;
}
if((byte_count == 1) && !((byte[0] == '0') && (byte[1] == 'x'))){
/* Case: x##
Since '\' is ignored, Case \x## devolves to this one. */
if(byte[0] == 'x'){
byte[2] = byte[1];
byte[1] = byte[0];
byte[0] = '0';
byte_count = 2;
/* Case: ## */
}else{
byte[3] = byte[1];
byte[2] = byte[0];
byte[0] = '0';
byte[1] = 'x';
byte_count = 3;
}
}
/* Case: 0x## takes care of itself. */
byte_count++;
/* When !(byte_count % 4) then the byte[] array is ready to be processed. */
if(!(byte_count % 4)){
/* If the count is bigger than the buffer, time for us to allocate more space.
This is a manual version of realloc using mmap(), memcpy(), and munmap(). */
if(sc_count == (num_pages * pagesize) - 1){
tmp_uint = num_pages++;
errno = 0;
tmp_ptr = (char *) mmap(NULL, (num_pages * pagesize), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(errno){
error(-1, errno, "mmap(NULL, %d, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)", (num_pages * pagesize));
}
memcpy(tmp_ptr, sc, (tmp_uint * pagesize));
munmap(sc, (tmp_uint * pagesize));
sc = tmp_ptr;
}
sc[sc_count] = (char) strtol(byte, NULL, 16);
sc_count++;
byte_count = 0;
}
}
if(retval == -1){
error(-1, errno, "read(%d, %p, %d)", STDIN_FILENO, (void *) &(byte[byte_count]), 1);
}
/* Now, politely ask the kernel to make our char array executable. */
if(mprotect(sc, (num_pages * pagesize), PROT_READ|PROT_EXEC) == -1){
error(-1, errno, "mprotect(%p, %d, PROT_READ|PROT_EXEC)", (void *) sc, (num_pages * pagesize));
}
if(execute){
/* Let's check if we have a reasonable tty setup. If not, we'll do our best to reset it,
just in case the shellcode requires it. */
if(!isatty(STDIN_FILENO)){
if(isatty(STDOUT_FILENO)){
if(close(STDIN_FILENO) == -1){
error(-1, errno, "close(STDIN_FILENO)");
}
if(dup2(STDOUT_FILENO, STDIN_FILENO) == -1){
error(-1, errno, "dup2(STDOUT_FILENO, STDIN_FILENO)");
}
}
}
/* <3 C */
((int (*)()) sc)();
}else{
for(byte_count = 0; byte_count < sc_count; byte_count++){
printf("%02hhx", sc[byte_count]);
}
// Used to be a printf("\n"); here, but that kills in-lining this tool with xxd, which is my main usecase.
}
/* Shouldn't ever be here. */
return(-1);
}