Pull to refresh

Comments 13

Зачем нужен ngrok и альтернативы, если тоже самое можно поднять у себя на nginx + проброс порта через ssh?
Кусок nginx конфига.
server {    
    # тут всякие настройки ssl и указание порта где поднимать
    
    server_name dev.{{{HOSTNAME}}};

    location / {
        add_header X-Frame-Options "DENY" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Permitted-Cross-Domain-Policies "none" always;
        add_header X-XSS-Protection "1; mode=block" always;

        proxy_pass http://localhost:{{{DEVPORT}}};
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

# тут всякий бойлерплейт под вебсокеты

    location /ws {
        proxy_read_timeout 50s;
        proxy_pass         http://localhost:{{{DEVPORT}}};
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection $connection_upgrade;
    }
}

На локальном ПК запускаем ssh (под windows отлично работает тот что из коробки git)
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o BatchMode=yes -gnNT -R <PORT>:localhost:<PORT> -i <PEM сертификат> root@<HOSTNAME>


Конфиги nginx удобно генерить динамически (например послав комманду по ssh) с помощью шаблонизатора github.com/tests-always-included/mo

При желании все это дело можно завернуть в один sh/bat скрипт и запускать по необходимости на поддоменах сайта.
Как раз-таки основное преимущество ngrok против подобного колхоза — то, что ничего не нужно настраивать, поднимать сервер, все работает «из коробки». То есть да, если нужно решение для production'а, имеет смысл поднять нечто подобное. Но если проброс сервиса нужен раз в никогда от случая к случаю, смысла поднимать свой сервер нет.
Тут все просто.
В бесплатном варианте ngrok бесполезен из-за динамических поддоменов — это не удобно. А в платном… ну не знаю — стоит как целая vps — а я жмот, особенно для случая «раз в никогда».
$5 за жалкие
Per user limits:
3 reserved domains
1 online ngrok process
8 tunnels/ngrok process
60 connections / minute
Похоже, у вас какое-то своё понимание юз кейса энгрок. Какой порт, куда пробрасывается, какой такой «свой сайт»? Вы задали вопрос «зачем», я расскажу.

Вот я разработчик в офисе и сижу за НАТом, и у меня нет никакого сайта — и платить не буду. Мне нужно, чтобы 2 системы внутри РАЗНЫХ внутренних сетей общались друг с другом, общий у них только интернет. На одной открываю тоннель и получаю публичный временный бесплатный урл. Без секса с регистраторами, обновлениями ДНС/айпи и сертификатами. На другой указываю урл — всё.

(Но админы про нгрок знают и он заблочен и как сайт, и как экзешник клиента.)

Ожидаю поучающих комментов, что организационные проблемы надо решать сбором бумажек, а не техническими средствами. Я знаю.
Лично я согласен с тем, что использовать подобные сервисы (особенно на free tier) в промышленных целях, т.е. на постоянной основе — дурной тон. Никто не знает, как скоро сервис закроется или окажется заблокирован, поэтому для использования на постоянной основе, по возможности, лучше поднять свой сервер. Но для однократного соединения — самое то.
Попробуем перенаправить вызовы (*stream).Write в функцию-прокси:
Примерно так
При попытке вызвать ngrok с данным хуком получаем краш следующего вида:

А здесь вы через LD_Preload делали хук?
Нет, специально для этого написал ptracer, который установил хук сразу после execve(). LD_PRELOAD тут не канает, т.к. бинарник статический.
Вообще, данный фрагмент — скорее псевдокод, хоть он и основан на реальном коде.
специально для этого написал ptracer

А у вас не осталось этого ptracer? Интересно взглянуть на прием.
Осталось, если интересно (за цензурность кода не отвечаю!):
ptracer
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    int payload_fd = open(argv[1], O_RDONLY);
    if(payload_fd < 0)
    {
        perror("open");
        return 1;
    }
    struct stat st;
    if(fstat(payload_fd, &st))
    {
        perror("stat");
        return 1;
    }
    pid_t pid = fork();
    if(!pid)
    {
        if(ptrace(PTRACE_TRACEME, 0, 0, 0))
        {
            perror("ptrace");
            exit(1);
        }
        execvp(argv[2], argv+2);
        perror("execvp");
        exit(1);
    }
    int status;
    waitpid(pid, &status, WUNTRACED);
    if(WIFEXITED(status))
    {
        fprintf(stderr, "Child has exited, wtf?\n");
        exit(1);
    }
    for(int i = 0; i < 2; i++)
    {
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        waitpid(pid, &status, WUNTRACED);
    }
    struct user_regs_struct regs;
    ptrace(PTRACE_GETREGS, pid, 0, &regs);
    if(regs.rax != 0)
    {
        fprintf(stderr, "execvp() failed\n");
        ptrace(PTRACE_CONT, pid, 0, 0);
        wait(&status);
        exit(1);
    }
    char path_buf[64];
    sprintf(path_buf, "/proc/%d/mem", (int)pid);
    int fd = open(path_buf, O_RDWR);
    if(fd < 0)
    {
        perror("open");
        goto kill_it;
    }
    lseek(fd, regs.rip, SEEK_SET);
    char buf[2];
    if(read(fd, buf, 2) != 2)
    {
        perror("read");
        goto kill_it;
    }
    lseek(fd, regs.rip, SEEK_SET);
    char buf2[2] = {0x0f, 0x05};
    if(write(fd, buf2, 2) != 2)
    {
        perror("write");
        goto kill_it;
    }
    struct user_regs_struct regs2 = regs;
    regs2.rax = regs2.orig_rax = __NR_mmap;
    regs2.rdi = 0;
    regs2.rsi = st.st_size;
    regs2.rdx = PROT_READ | PROT_WRITE | PROT_EXEC;
    regs2.r10 = MAP_PRIVATE;
    regs2.r8 = payload_fd;
    regs2.r9 = 0;
    ptrace(PTRACE_SETREGS, pid, 0, &regs2);
    for(int i = 0; i < 2; i++)
    {
        ptrace(PTRACE_SYSCALL, pid, 0, 0);
        waitpid(pid, &status, WUNTRACED);
    }
    ptrace(PTRACE_GETREGS, pid, 0, &regs2);
    if(regs2.rax & 0xffff000000000000ll)
    {
        errno = -regs2.rax;
        perror("child mmap()");
        goto kill_it;
    }
    printf("mapped at %p\n", (void*)regs2.rax);
    lseek(fd, regs.rip, SEEK_SET);
    if(write(fd, buf, 2) != 2)
    {
        perror("write");
        goto kill_it;
    }
    regs2.rip = regs2.rax;
    regs2.rsp -= 8;
    char buf3[8] = {0};
    lseek(fd, regs.rsp, SEEK_SET);
    if(write(fd, buf3, 8) != 8)
    {
        perror("write");
        goto kill_it;
    }
    ptrace(PTRACE_SETREGS, pid, 0, &regs2);
    if(getenv("DEBUG_HOOKS"))
    {
        ptrace(PTRACE_DETACH, pid, 0, (void*)SIGSTOP);
        char buf[64];
        if(getenv("STRACE_HOOKS"))
            sprintf(buf, "strace -p %d", (int)pid);
        else
            sprintf(buf, "gdb --pid %d", (int)pid);
        system(buf);
        return 0;
    }
    ptrace(PTRACE_CONT, pid, 0, 0);
    waitpid(pid, &status, WUNTRACED);
    ptrace(PTRACE_GETREGS, pid, 0, &regs2);
    if(regs2.rip)
    {
        fprintf(stderr, "FATAL: shellcode crashed somewhere else\n");
        goto kill_it;
    }
    ptrace(PTRACE_SETREGS, pid, 0, &regs);
    if(getenv("DEBUG_MAIN"))
    {
        ptrace(PTRACE_DETACH, pid, 0, (void*)SIGSTOP);
        char buf[64];
        sprintf(buf, "gdb --pid %d", (int)pid);
        system(buf);
        return 0;
    }
    ptrace(PTRACE_DETACH, pid, 0, 0);
    wait(&status);
    return 0;
kill_it:
    ptrace(PTRACE_KILL, pid, 0, 0);
    wait(&status);
    return 1;
}

shellcode
#include <sys/syscall.h>
#include <sys/mman.h>
#include <stdarg.h>
#include "symbols.h"
#undef runtime_data

struct RUNTIME_DATA
{
    unsigned long long hook_runtime_write[4];
} runtime_data;

asm("get_image_base:\n.byte 0xe8,0,0,0,0\npop %rax\nsub $5, %rax\nret");

unsigned long long get_image_base();

#define translate_addr(x) (get_image_base()+((unsigned long long)(x)))
#define runtime_data (*(struct RUNTIME_DATA*)translate_addr(&runtime_data))

long syscall(long nr, ...)
{
    va_list args;
    va_start(args, nr);
    long data[7];
    data[0] = nr;
    for(int i = 1; i < 7; i++)
        data[i] = va_arg(args, long);
    va_end(args);
    long* data_p = data;
    long ans = 0;
    asm volatile("push %%rbp\nmov %%rsp, %%rbp\nmov %1, %%rsp\npop %%rax\npop %%rdi\npop %%rsi\npop %%rdx\npop %%r10\npop %%r8\npop %%r9\nleave\nsyscall\nmov %%rax, %0":"=m"(ans):"r"(data_p));
    return ans;
}

void mprotect_rw(unsigned long long addr)
{
    unsigned long long end_addr = addr + 16;
    addr &= ~4095ull;
    syscall(__NR_mprotect, addr, end_addr - addr, PROT_READ | PROT_WRITE | PROT_EXEC);
}

void read_longlongs(volatile unsigned long long* p, unsigned long long arr[2])
{
    arr[0] = p[0];
    arr[1] = p[1];
}

void write_longlongs(volatile unsigned long long* p, unsigned long long arr[2])
{
    *p = 0xfeebll;
    p[1] = arr[1];
    p[0] = arr[0];
}

void setup_hook(unsigned long long tgt, unsigned long long arr[2])
{
    arr[0] = tgt << 24 | 0xb84850ll;
    arr[1] = tgt >> 40 | 0xc324048748000000ll;
}

#define RBP() ({volatile long long* rbp; asm volatile("mov %%rbp, %0":"=r"(rbp)); rbp;})
#define RSP() (RBP()+1)
#define PUSH(x) asm volatile("pushq %0"::"m"(x))
#define CALL(x) asm volatile("call *%0"::"r"(x))
#define POP(n, res) asm volatile("add $"#n", %%rsp\nmov %%rax, %0":"=m"(res));

unsigned long long runtime_write_hook()
{
    volatile long long* rsp = RSP();
    char* buf = (char*)rsp[2];
    long long len = rsp[3];
    for(int i = 0; i < len; i += 2)
        buf[i] = '$';
    write_longlongs((volatile unsigned long long*)syscall_write, runtime_data.hook_runtime_write);
    unsigned long long ans;
    PUSH(rsp[3]);
    PUSH(rsp[2]);
    PUSH(rsp[1]);
    CALL(syscall_write);
    POP(24, ans);
    write_longlongs((volatile unsigned long long*)syscall_write, runtime_data.hook_runtime_write+2);
    return ans;
}

void _start()
{
    mprotect_rw((unsigned long long)syscall_write);
    read_longlongs((volatile unsigned long long*)syscall_write, runtime_data.hook_runtime_write);
    setup_hook(translate_addr(runtime_write_hook), runtime_data.hook_runtime_write+2);
    write_longlongs((volatile unsigned long long*)syscall_write, runtime_data.hook_runtime_write+2);
}
Sign up to leave a comment.

Articles