/* Unix SMB/CIFS implementation. SMB torture tester Copyright (C) Andrew Tridgell 2003 Copyright (C) Jelmer Vernooij 2006 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "system/filesys.h" #include "system/locale.h" #include "librpc/ndr/libndr.h" #include "librpc/ndr/ndr_table.h" #include "librpc/gen_ndr/ndr_dcerpc.h" #include "lib/cmdline/cmdline.h" #include "param/param.h" #include "lib/util/base64.h" static const struct ndr_interface_call *find_function( const struct ndr_interface_table *p, const char *function) { unsigned int i; if (isdigit(function[0])) { char *eptr = NULL; i = strtoul(function, &eptr, 0); if (i >= p->num_calls || eptr == NULL || eptr[0] != '\0') { printf("Function number '%s' not found\n", function); exit(1); } return &p->calls[i]; } for (i=0;inum_calls;i++) { if (strcmp(p->calls[i].name, function) == 0) { break; } } if (i == p->num_calls) { printf("Function '%s' not found\n", function); exit(1); } return &p->calls[i]; } /* * Find a public structure on the pipe and return it as if it were * a function (as the rest of ndrdump is based around functions) */ static const struct ndr_interface_call *find_struct( const struct ndr_interface_table *p, const char *struct_name, struct ndr_interface_call *out_buffer) { unsigned int i; const struct ndr_interface_public_struct *public_struct = NULL; if (isdigit(struct_name[0])) { char *eptr = NULL; i = strtoul(struct_name, &eptr, 0); if (i >= p->num_public_structs || eptr == NULL || eptr[0] != '\0') { printf("Public structure number '%s' not found\n", struct_name); exit(1); } public_struct = &p->public_structs[i]; } else { for (i=0;inum_public_structs;i++) { if (strcmp(p->public_structs[i].name, struct_name) == 0) { break; } } if (i == p->num_public_structs) { printf("Public structure '%s' not found\n", struct_name); exit(1); } public_struct = &p->public_structs[i]; } *out_buffer = (struct ndr_interface_call) { .name = public_struct->name, .struct_size = public_struct->struct_size, .ndr_pull = public_struct->ndr_pull, .ndr_push = public_struct->ndr_push, .ndr_print = public_struct->ndr_print }; return out_buffer; } _NORETURN_ static void show_pipes(void) { const struct ndr_interface_list *l; printf("\nYou must specify a pipe\n"); printf("known pipes are:\n"); for (l=ndr_table_list();l;l=l->next) { if(l->table->helpstring) { printf("\t%s - %s\n", l->table->name, l->table->helpstring); } else { printf("\t%s\n", l->table->name); } } exit(1); } _NORETURN_ static void show_functions(const struct ndr_interface_table *p) { int i; printf("\nYou must specify a function\n"); printf("known functions on '%s' are:\n", p->name); for (i=0;inum_calls;i++) { printf("\t0x%02x (%2d) %s\n", i, i, p->calls[i].name); } printf("known public structures on '%s' are:\n", p->name); for (i=0;inum_public_structs;i++) { printf("\t%s\n", p->public_structs[i].name); } exit(1); } static char *stdin_load(TALLOC_CTX *mem_ctx, size_t *size) { int num_read, total_len = 0; char buf[255]; char *result = NULL; while((num_read = read(STDIN_FILENO, buf, 255)) > 0) { if (result) { result = talloc_realloc( mem_ctx, result, char, total_len + num_read); } else { result = talloc_array(mem_ctx, char, num_read); } memcpy(result + total_len, buf, num_read); total_len += num_read; } if (size) *size = total_len; return result; } static const struct ndr_interface_table *load_iface_from_plugin(const char *plugin, const char *pipe_name) { const struct ndr_interface_table *p; void *handle; char *symbol; handle = dlopen(plugin, RTLD_NOW); if (handle == NULL) { printf("%s: Unable to open: %s\n", plugin, dlerror()); return NULL; } symbol = talloc_asprintf(NULL, "ndr_table_%s", pipe_name); p = (const struct ndr_interface_table *)dlsym(handle, symbol); if (!p) { printf("%s: Unable to find DCE/RPC interface table for '%s': %s\n", plugin, pipe_name, dlerror()); talloc_free(symbol); dlclose(handle); return NULL; } talloc_free(symbol); return p; } static void ndrdump_data(uint8_t *d, uint32_t l, bool force) { dump_data_file(d, l, !force, stdout); } static void ndrdump_data_diff(const uint8_t *d1, size_t l1, const uint8_t *d2, size_t l2, bool force) { dump_data_file_diff(stdout, !force, d1, l1, d2, l2); } static NTSTATUS ndrdump_pull_and_print_pipes(const char *function, struct ndr_pull *ndr_pull, struct ndr_print *ndr_print, const struct ndr_interface_call_pipes *pipes) { enum ndr_err_code ndr_err; uint32_t i; for (i=0; i < pipes->num_pipes; i++) { uint64_t idx = 0; while (true) { void *saved_mem_ctx; uint32_t *count; void *c; char *n; c = talloc_zero_size(ndr_pull, pipes->pipes[i].chunk_struct_size); talloc_set_name(c, "struct %s", pipes->pipes[i].name); /* * Note: the first struct member is always * 'uint32_t count;' */ count = (uint32_t *)c; n = talloc_asprintf(c, "%s: %s[%"PRIu64"]", function, pipes->pipes[i].name, idx); saved_mem_ctx = ndr_pull->current_mem_ctx; ndr_pull->current_mem_ctx = c; ndr_err = pipes->pipes[i].ndr_pull(ndr_pull, NDR_SCALARS, c); ndr_pull->current_mem_ctx = saved_mem_ctx; printf("pull returned %s\n", ndr_map_error2string(ndr_err)); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { talloc_free(c); return ndr_map_error2ntstatus(ndr_err); } pipes->pipes[i].ndr_print(ndr_print, n, c); if (*count == 0) { talloc_free(c); break; } talloc_free(c); idx++; } } return NT_STATUS_OK; } static void ndr_print_dummy(struct ndr_print *ndr, const char *format, ...) { /* This is here so that you can turn ndr printing off for the purposes of benchmarking ndr parsing. */ } int main(int argc, const char *argv[]) { const struct ndr_interface_table *p = NULL; const struct ndr_interface_call *f; struct ndr_interface_call f_buffer; const char *pipe_name = NULL; const char *filename = NULL; /* * The format type: * in: a request * out: a response * struct: a public structure */ const char *type = NULL; /* * Format is either the name of the decoding function or the * name of a public structure */ const char *format = NULL; const char *cmdline_input = NULL; const uint8_t *data; size_t size; DATA_BLOB blob; struct ndr_pull *ndr_pull; struct ndr_print *ndr_print; TALLOC_CTX *mem_ctx; ndr_flags_type flags = 0; poptContext pc; NTSTATUS status; enum ndr_err_code ndr_err; void *st; void *v_st; const char *ctx_filename = NULL; const char *plugin = NULL; bool validate = false; bool dumpdata = false; bool assume_ndr64 = false; bool quiet = false; bool hex_input = false; bool base64_input = false; bool print_after_parse_failure = false; int opt; enum { OPT_CONTEXT_FILE=1000, OPT_VALIDATE, OPT_DUMP_DATA, OPT_LOAD_DSO, OPT_NDR64, OPT_QUIET, OPT_BASE64_INPUT, OPT_HEX_INPUT, OPT_CMDLINE_INPUT, OPT_PRINT_AFTER_PARSE_FAILURE, }; struct poptOption long_options[] = { POPT_AUTOHELP {"context-file", 'c', POPT_ARG_STRING, NULL, OPT_CONTEXT_FILE, "In-filename to parse first", "CTX-FILE" }, {"validate", 0, POPT_ARG_NONE, NULL, OPT_VALIDATE, "try to validate the data", NULL }, {"dump-data", 0, POPT_ARG_NONE, NULL, OPT_DUMP_DATA, "dump the hex data", NULL }, {"load-dso", 0, POPT_ARG_STRING, NULL, OPT_LOAD_DSO, "load from shared object file", NULL }, {"ndr64", 0, POPT_ARG_NONE, NULL, OPT_NDR64, "Assume NDR64 data", NULL }, {"quiet", 0, POPT_ARG_NONE, NULL, OPT_QUIET, "Don't actually dump anything", NULL }, {"base64-input", 0, POPT_ARG_NONE, NULL, OPT_BASE64_INPUT, "Read the input file in as a base64 string", NULL }, {"hex-input", 0, POPT_ARG_NONE, NULL, OPT_HEX_INPUT, "Read the input file in as a hex dump", NULL }, {"input", 0, POPT_ARG_STRING, NULL, OPT_CMDLINE_INPUT, "Provide the input on the command line (use with --base64-input)", "INPUT" }, {"print-after-parse-failure", 0, POPT_ARG_NONE, NULL, OPT_PRINT_AFTER_PARSE_FAILURE, "Try to print structures that fail to parse (used to develop parsers, segfaults are likely).", NULL }, POPT_COMMON_SAMBA POPT_COMMON_VERSION POPT_TABLEEND }; uint32_t highest_ofs; struct dcerpc_sec_verification_trailer *sec_vt = NULL; bool ok; ndr_table_init(); /* Initialise samba stuff */ smb_init_locale(); setlinebuf(stdout); mem_ctx = talloc_init("ndrdump.c/main"); if (mem_ctx == NULL) { exit(ENOMEM); } ok = samba_cmdline_init(mem_ctx, SAMBA_CMDLINE_CONFIG_CLIENT, false /* require_smbconf */); if (!ok) { DBG_ERR("Failed to init cmdline parser!\n"); TALLOC_FREE(mem_ctx); exit(1); } pc = samba_popt_get_context(getprogname(), argc, argv, long_options, 0); if (pc == NULL) { DBG_ERR("Failed to setup popt context!\n"); TALLOC_FREE(mem_ctx); exit(1); } poptSetOtherOptionHelp( pc, " []"); while ((opt = poptGetNextOpt(pc)) != -1) { switch (opt) { case OPT_CONTEXT_FILE: ctx_filename = poptGetOptArg(pc); break; case OPT_VALIDATE: validate = true; break; case OPT_DUMP_DATA: dumpdata = true; break; case OPT_LOAD_DSO: plugin = poptGetOptArg(pc); break; case OPT_NDR64: assume_ndr64 = true; break; case OPT_QUIET: quiet = true; break; case OPT_BASE64_INPUT: base64_input = true; break; case OPT_HEX_INPUT: hex_input = true; break; case OPT_CMDLINE_INPUT: cmdline_input = poptGetOptArg(pc); break; case OPT_PRINT_AFTER_PARSE_FAILURE: print_after_parse_failure = true; break; } } pipe_name = poptGetArg(pc); if (!pipe_name) { poptPrintUsage(pc, stderr, 0); show_pipes(); exit(1); } if (plugin != NULL) { p = load_iface_from_plugin(plugin, pipe_name); } if (!p) { p = ndr_table_by_name(pipe_name); } if (!p) { struct GUID uuid; status = GUID_from_string(pipe_name, &uuid); if (NT_STATUS_IS_OK(status)) { p = ndr_table_by_uuid(&uuid); } } if (!p) { printf("Unknown pipe or UUID '%s'\n", pipe_name); exit(1); } format = poptGetArg(pc); type = poptGetArg(pc); filename = poptGetArg(pc); if (!format || !type) { poptPrintUsage(pc, stderr, 0); show_functions(p); exit(1); } if (strcmp(type, "struct") == 0) { flags = NDR_SCALARS|NDR_BUFFERS; /* neither NDR_IN nor NDR_OUT */ f = find_struct(p, format, &f_buffer); } else { f = find_function(p, format); if (strcmp(type, "in") == 0 || strcmp(type, "request") == 0) { flags |= NDR_IN; } else if (strcmp(type, "out") == 0 || strcmp(type, "response") == 0) { flags |= NDR_OUT; } else { printf("Bad type value '%s'\n", type); exit(1); } } st = talloc_zero_size(mem_ctx, f->struct_size); if (!st) { printf("Unable to allocate %zu bytes for %s structure\n", f->struct_size, f->name); TALLOC_FREE(mem_ctx); exit(1); } v_st = talloc_zero_size(mem_ctx, f->struct_size); if (!v_st) { printf("Unable to allocate %zu bytes for %s validation " "structure\n", f->struct_size, f->name); TALLOC_FREE(mem_ctx); exit(1); } if (ctx_filename) { if (flags & NDR_IN) { printf("Context file can only be used for \"out\" packages\n"); TALLOC_FREE(mem_ctx); exit(1); } data = (uint8_t *)file_load(ctx_filename, &size, 0, mem_ctx); if (!data) { perror(ctx_filename); TALLOC_FREE(mem_ctx); exit(1); } blob = data_blob_const(data, size); ndr_pull = ndr_pull_init_blob(&blob, mem_ctx); if (ndr_pull == NULL) { perror("ndr_pull_init_blob"); TALLOC_FREE(mem_ctx); exit(1); } ndr_pull->flags |= LIBNDR_FLAG_REF_ALLOC; if (assume_ndr64) { ndr_pull->flags |= LIBNDR_FLAG_NDR64; } ndr_err = f->ndr_pull(ndr_pull, NDR_IN, st); if (ndr_pull->offset > ndr_pull->relative_highest_offset) { highest_ofs = ndr_pull->offset; } else { highest_ofs = ndr_pull->relative_highest_offset; } if (highest_ofs != ndr_pull->data_size) { printf("WARNING! %"PRIu32" unread bytes while parsing context file\n", ndr_pull->data_size - highest_ofs); } if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("pull for context file returned %s\n", ndr_map_error2string(ndr_err)); TALLOC_FREE(mem_ctx); exit(2); } memcpy(v_st, st, f->struct_size); } if (filename && cmdline_input) { printf("cannot combine --input with a filename\n"); TALLOC_FREE(mem_ctx); exit(1); } else if (cmdline_input) { data = (const uint8_t *)cmdline_input; size = strlen(cmdline_input); } else if (filename) { data = (uint8_t *)file_load(filename, &size, 0, mem_ctx); } else { data = (uint8_t *)stdin_load(mem_ctx, &size); } if (!data) { if (filename) perror(filename); else perror("stdin"); exit(1); } if (hex_input && base64_input) { printf("cannot combine --hex-input with --base64-input\n"); TALLOC_FREE(mem_ctx); exit(1); } else if (hex_input && size >= 1 && data[0] != '[') { blob = strhex_to_data_blob(mem_ctx, (const char *)data); } else if (hex_input) { blob = hexdump_to_data_blob(mem_ctx, (const char *)data, size); } else if (base64_input) { /* Use talloc_strndup() to ensure null termination */ blob = base64_decode_data_blob_talloc( mem_ctx, talloc_strndup(mem_ctx, (const char *)data, size)); } else { blob = data_blob_const(data, size); } if (data != NULL && blob.data == NULL) { printf("failed to decode input data\n"); TALLOC_FREE(mem_ctx); exit(1); } ndr_pull = ndr_pull_init_blob(&blob, mem_ctx); if (ndr_pull == NULL) { perror("ndr_pull_init_blob"); TALLOC_FREE(mem_ctx); exit(1); } ndr_pull->flags |= LIBNDR_FLAG_REF_ALLOC; if (assume_ndr64) { ndr_pull->flags |= LIBNDR_FLAG_NDR64; } ndr_print = talloc_zero(mem_ctx, struct ndr_print); if (quiet) { ndr_print->print = ndr_print_dummy; } else { ndr_print->print = ndr_print_printf_helper; } ndr_print->depth = 1; ndr_err = ndr_pop_dcerpc_sec_verification_trailer(ndr_pull, mem_ctx, &sec_vt); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("ndr_pop_dcerpc_sec_verification_trailer returned %s\n", ndr_map_error2string(ndr_err)); } if (sec_vt != NULL && sec_vt->count.count > 0) { printf("SEC_VT: consumed %zu bytes\n", blob.length - ndr_pull->data_size); if (dumpdata) { ndrdump_data(blob.data + ndr_pull->data_size, blob.length - ndr_pull->data_size, dumpdata); } ndr_print_dcerpc_sec_verification_trailer(ndr_print, "SEC_VT", sec_vt); } TALLOC_FREE(sec_vt); if (flags & NDR_OUT) { status = ndrdump_pull_and_print_pipes(format, ndr_pull, ndr_print, &f->out_pipes); if (!NT_STATUS_IS_OK(status)) { printf("pull and dump of OUT pipes FAILED: %s\n", nt_errstr(status)); TALLOC_FREE(mem_ctx); exit(2); } } ndr_err = f->ndr_pull(ndr_pull, flags, st); printf("pull returned %s\n", ndr_map_error2string(ndr_err)); if (ndr_pull->offset > ndr_pull->relative_highest_offset) { highest_ofs = ndr_pull->offset; } else { highest_ofs = ndr_pull->relative_highest_offset; } if (dumpdata) { printf("%"PRIu32" bytes consumed\n", highest_ofs); ndrdump_data(blob.data, blob.length, dumpdata); } if (!print_after_parse_failure && !NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { TALLOC_FREE(mem_ctx); exit(2); } if (highest_ofs != ndr_pull->data_size) { printf("WARNING! %"PRIu32" unread bytes\n", ndr_pull->data_size - highest_ofs); ndrdump_data(ndr_pull->data+highest_ofs, ndr_pull->data_size - highest_ofs, dumpdata); } if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("WARNING: pull of %s was incomplete, " "therefore the parse below may SEGFAULT\n", f->name); } f->ndr_print(ndr_print, f->name, flags, st); if (flags & NDR_IN) { status = ndrdump_pull_and_print_pipes(format, ndr_pull, ndr_print, &f->in_pipes); if (!NT_STATUS_IS_OK(status)) { printf("pull and dump of IN pipes FAILED: %s\n", nt_errstr(status)); exit(1); } } /* Do not proceed to validate if we got an error */ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("dump of failed-to-parse %s complete\n", f->name); TALLOC_FREE(mem_ctx); exit(2); } if (validate) { DATA_BLOB v_blob; struct ndr_push *ndr_v_push; struct ndr_pull *ndr_v_pull; struct ndr_print *ndr_v_print; uint32_t highest_v_ofs; uint32_t i; uint8_t byte_a, byte_b; bool differ; ndr_v_push = ndr_push_init_ctx(mem_ctx); if (ndr_v_push == NULL) { printf("No memory\n"); exit(1); } if (assume_ndr64) { ndr_v_push->flags |= LIBNDR_FLAG_NDR64; } ndr_err = f->ndr_push(ndr_v_push, flags, st); printf("push returned %s\n", ndr_map_error2string(ndr_err)); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("validate push FAILED\n"); TALLOC_FREE(mem_ctx); exit(1); } v_blob = ndr_push_blob(ndr_v_push); if (dumpdata) { printf("%zu bytes generated (validate)\n", v_blob.length); ndrdump_data(v_blob.data, v_blob.length, dumpdata); } ndr_v_pull = ndr_pull_init_blob(&v_blob, mem_ctx); if (ndr_v_pull == NULL) { perror("ndr_pull_init_blob"); TALLOC_FREE(mem_ctx); exit(1); } ndr_v_pull->flags |= LIBNDR_FLAG_REF_ALLOC; ndr_err = f->ndr_pull(ndr_v_pull, flags, v_st); printf("pull returned %s\n", ndr_map_error2string(ndr_err)); if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { printf("validate pull FAILED\n"); TALLOC_FREE(mem_ctx); exit(1); } if (ndr_v_pull->offset > ndr_v_pull->relative_highest_offset) { highest_v_ofs = ndr_v_pull->offset; } else { highest_v_ofs = ndr_v_pull->relative_highest_offset; } if (highest_v_ofs != ndr_v_pull->data_size) { printf("WARNING! %"PRIu32" unread bytes in validation\n", ndr_v_pull->data_size - highest_v_ofs); ndrdump_data(ndr_v_pull->data + highest_v_ofs, ndr_v_pull->data_size - highest_v_ofs, dumpdata); } ndr_v_print = talloc_zero(mem_ctx, struct ndr_print); ndr_v_print->print = ndr_print_debug_helper; ndr_v_print->depth = 1; f->ndr_print(ndr_v_print, format, flags, v_st); if (blob.length != v_blob.length) { printf("WARNING! orig bytes:%zu validated pushed bytes:%zu\n", blob.length, v_blob.length); } if (highest_ofs != highest_v_ofs) { printf("WARNING! orig pulled bytes:%"PRIu32" validated pulled bytes:%"PRIu32"\n", highest_ofs, highest_v_ofs); } differ = false; byte_a = 0x00; byte_b = 0x00; for (i=0; i < blob.length; i++) { byte_a = blob.data[i]; if (i == v_blob.length) { byte_b = 0x00; differ = true; break; } byte_b = v_blob.data[i]; if (byte_a != byte_b) { differ = true; break; } } if (differ) { printf("WARNING! orig and validated differ at byte 0x%02"PRIX32" (%"PRIu32")\n", i, i); printf("WARNING! orig byte[0x%02"PRIX32"] = 0x%02"PRIX8" validated byte[0x%02"PRIX32"] = 0x%02"PRIX8"\n", i, byte_a, i, byte_b); ndrdump_data_diff(blob.data, blob.length, v_blob.data, v_blob.length, dumpdata); } } printf("dump OK\n"); TALLOC_FREE(mem_ctx); poptFreeContext(pc); return 0; }