Комментарии 13
Зачем нужен ngrok и альтернативы, если тоже самое можно поднять у себя на nginx + проброс порта через ssh?
Кусок nginx конфига.
На локальном ПК запускаем ssh (под windows отлично работает тот что из коробки git)
Конфиги nginx удобно генерить динамически (например послав комманду по ssh) с помощью шаблонизатора github.com/tests-always-included/mo
При желании все это дело можно завернуть в один sh/bat скрипт и запускать по необходимости на поддоменах сайта.
Кусок 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
В бесплатном варианте ngrok бесполезен из-за динамических поддоменов — это не удобно. А в платном… ну не знаю — стоит как целая vps — а я жмот, особенно для случая «раз в никогда».
$5 за жалкие
Per user limits:
3 reserved domains
1 online ngrok process
8 tunnels/ngrok process
60 connections / minute
Похоже, у вас какое-то своё понимание юз кейса энгрок. Какой порт, куда пробрасывается, какой такой «свой сайт»? Вы задали вопрос «зачем», я расскажу.
Вот я разработчик в офисе и сижу за НАТом, и у меня нет никакого сайта — и платить не буду. Мне нужно, чтобы 2 системы внутри РАЗНЫХ внутренних сетей общались друг с другом, общий у них только интернет. На одной открываю тоннель и получаю публичный временный бесплатный урл. Без секса с регистраторами, обновлениями ДНС/айпи и сертификатами. На другой указываю урл — всё.
(Но админы про нгрок знают и он заблочен и как сайт, и как экзешник клиента.)
Ожидаю поучающих комментов, что организационные проблемы надо решать сбором бумажек, а не техническими средствами. Я знаю.
Вот я разработчик в офисе и сижу за НАТом, и у меня нет никакого сайта — и платить не буду. Мне нужно, чтобы 2 системы внутри РАЗНЫХ внутренних сетей общались друг с другом, общий у них только интернет. На одной открываю тоннель и получаю публичный временный бесплатный урл. Без секса с регистраторами, обновлениями ДНС/айпи и сертификатами. На другой указываю урл — всё.
(Но админы про нгрок знают и он заблочен и как сайт, и как экзешник клиента.)
Ожидаю поучающих комментов, что организационные проблемы надо решать сбором бумажек, а не техническими средствами. Я знаю.
Лично я согласен с тем, что использовать подобные сервисы (особенно на free tier) в промышленных целях, т.е. на постоянной основе — дурной тон. Никто не знает, как скоро сервис закроется или окажется заблокирован, поэтому для использования на постоянной основе, по возможности, лучше поднять свой сервер. Но для однократного соединения — самое то.
Как альтернатива есть еще открытый проект https://github.com/inlets/inlets
Попробуем перенаправить вызовы (*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, ®s);
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, ®s2);
for(int i = 0; i < 2; i++)
{
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, WUNTRACED);
}
ptrace(PTRACE_GETREGS, pid, 0, ®s2);
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, ®s2);
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, ®s2);
if(regs2.rip)
{
fprintf(stderr, "FATAL: shellcode crashed somewhere else\n");
goto kill_it;
}
ptrace(PTRACE_SETREGS, pid, 0, ®s);
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);
}
.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Реверс-инжиниринг протокола ngrok v2