asmsh/asm_env.c

491 lines
10 KiB
C

/* Copyright Yann Weber <asmsh@yannweb.net>
This file is part of asmsh.
asmsh 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 any later version.
asmsh 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 asmsh. If not, see <https://www.gnu.org/licenses/>.
*/
#include "asm_env.h"
static int _asmsh_env_spawn(asmsh_env_t *asmenv);
static void _asmsh_env_child(const char *childpath);
/** Return a path (that should be freed) of a temporary executable
* child that can be exec on */
static char *asmsh_env_tmpexec();
/* binary buffer of the child elf */
extern unsigned char _binary_child_start;
extern unsigned char _binary_child_end;
asmsh_env_t* asmsh_env(const char *childpath)
{
asmsh_env_t *res;
int err;
if((res = malloc(sizeof(*res))) == NULL)
{
err = errno;
asmsh_log_perror("Unable to allocate env");
errno = err;
return NULL;
}
child_mmap_init(&(res->mmap));
asmsh_brk_init(&res->brks);
res->childpath = NULL;
if(childpath && (res->childpath = strdup(childpath)) == NULL)
{
err=errno;
goto err_pathdup;
}
if(_asmsh_env_spawn(res) < 0)
{
err = errno;
goto err;
}
if(asmsh_env_update(res) < 0)
{
err=errno;
goto err;
}
res->txt_map_addr = (void*)res->regs.rax;
res->txt_map_sz = res->regs.r14;
res->stack_addr = (void*)res->regs.r15;
res->stack_sz = (size_t)res->stack_addr - res->regs.rsp;
res->code_write_ptr = res->txt_map_addr;
return res;
err:
if(res->childpath)
{
free(res->childpath);
}
err_pathdup:
free(res);
errno = err;
return NULL;
}
void asmsh_env_free(asmsh_env_t *asmenv)
{
if(asmenv->mmap.maps)
{
free(asmenv->mmap.maps);
}
kill(asmenv->pid, SIGKILL);
asmsh_brk_free(&asmenv->brks);
free(asmenv->childpath);
free(asmenv);
}
int asmsh_env_write_mem(asmsh_env_t *env, void *addr, const unsigned char *buf, size_t buf_sz)
{
int err;
u_int64_t data;
unsigned char written;
char bleft = (u_int64_t)addr % 8;
written = 0;
if(bleft)
{
// First write to correct further write allignement
void *wr_addr = (void*)(((u_int64_t)addr / 8) * 8);
char towrite = 8 - bleft;
towrite = (towrite > buf_sz)?buf_sz:towrite;
errno = 0;
data = ptrace(PTRACE_PEEKTEXT, env->pid, wr_addr, NULL);
if(errno)
{
err = errno;
asmsh_log_perror("Unable to peektext in order to allign write");
errno = err;
return -1;
}
memcpy(((unsigned char*)&data)+bleft, &(buf[written]), towrite);
if(ptrace(PTRACE_POKETEXT, env->pid, wr_addr, data) < 0)
{
err = errno;
asmsh_log_perror("Unable to poketext in order to allign write");
errno = err;
return -1;
}
written += towrite;
env->code_write_ptr += towrite;
}
if(written >= buf_sz)
{
return 0;
}
// env->code_write_ptr is now word alligned and "written" bytes are
// allready written
while(written < buf_sz)
{
char towrite = buf_sz - written;
if(towrite >= 8)
{
data = *(u_int64_t*)(&buf[written]);
}
else
{
data = 0;
memcpy(&data, &buf[written], towrite);
}
if(ptrace(PTRACE_POKETEXT, env->pid, env->code_write_ptr, data) < 0)
{
err = errno;
asmsh_log_perror("Unable to poketext");
errno = err;
return -1;
}
written += towrite;
env->code_write_ptr += towrite;
}
return 0;
}
int asmsh_env_write_code(asmsh_env_t *env, asmsh_bytecode_t *bcode)
{
return asmsh_env_write_mem(env, env->code_write_ptr,
bcode->bytes, bcode->size);
}
int asmsh_env_step(asmsh_env_t *env, int *status)
{
int err;
if(status) { *status = 0; }
if(ptrace(PTRACE_SINGLESTEP, env->pid, NULL, 0) < 0)
{
err = errno;
asmsh_log_perror("Unable to ptrace singlestep");
goto err;
}
if(waitpid(env->pid, &env->status, 0) < 0)
{
err = errno;
asmsh_log_perror("Unable to wait for child process to stop on step");
goto err;
}
if(status) { *status = env->status; }
if(!WIFSTOPPED(env->status) || env->status >> 8 != 5)
{
goto err_wstatus;
}
return 0;
/// TODO replace by an utility function that logs ?
err_wstatus:
if(WIFEXITED(env->status))
{
dprintf(2, "Child exited with status %d\n", WEXITSTATUS(env->status));
}
else if(WIFSIGNALED(env->status))
{
if(WCOREDUMP(env->status))
{
dprintf(2, "Child segfault\n");
}
else
{
dprintf(2, "Child killed by sig %d\n", WTERMSIG(env->status));
}
}
else if(WIFSTOPPED(env->status))
{
dprintf(2, "Child stopped by %s(sig#%d)\n",
strsignal(WSTOPSIG(env->status)),
WSTOPSIG(env->status));
}
else
{
dprintf(2, "Unexpected child status 0x%04X\n", env->status);
}
errno = ECHILD;
return 1;
err:
errno = err;
return -1;
}
int asmsh_env_update(asmsh_env_t *asmenv)
{
if(asmsh_env_update_regs(asmenv) < 0)
{
return -1;
}
return child_mmap_get(asmenv->pid, &(asmenv->mmap));
}
int asmsh_env_update_maps(asmsh_env_t *asmenv)
{
if(child_mmap_get(asmenv->pid, &(asmenv->mmap)) < 0)
{
return -1;
}
return 0;
}
int asmsh_env_update_regs(asmsh_env_t *asmenv)
{
bzero(&(asmenv->regs), sizeof(asmenv->regs));
if(ptrace(PTRACE_GETREGS, asmenv->pid, NULL, &(asmenv->regs)) < 0)
{
int err = errno;
asmsh_log_perror("ptrace getregs error");
errno = err;
return -1;
}
return 0;
}
static int _asmsh_env_spawn(asmsh_env_t *env)
{
int err;
int wstatus;
const char *childpath = env->childpath?env->childpath:asmsh_env_tmpexec();
if(!childpath)
{
return -1; // Error in asmsh_env_tmpexec()
}
if((env->pid = fork()) == -1)
{
err = errno;
asmsh_log_perror("Unable to fork!");
goto err_fork;
}
else if(env->pid == 0)
{
_asmsh_env_child(childpath);
}
if(waitpid(env->pid, &wstatus, WUNTRACED) < 0)
{
err=errno;
asmsh_log_perror("Unable to wait for child process");
goto err;
}
if(!WIFSTOPPED(wstatus))
{
dprintf(2, "child didn't stop as expected");
goto err_wstatus;
}
if(ptrace(PTRACE_ATTACH, env->pid, 0, 0) == -1)
{
err=errno;
asmsh_log_perror("Unable to attach to child process");
goto err;
}
if(waitpid(env->pid, &wstatus, 0) < 0)
{
err=errno;
asmsh_log_perror("Unable to wait for child process");
goto err;
}
if(!WIFSTOPPED(wstatus))
{
dprintf(2, "child didn't stop as expected");
goto err_wstatus;
}
// Attached to child
// tell child to stop on exec
if(ptrace(PTRACE_SETOPTIONS, env->pid, NULL, PTRACE_O_TRACEEXEC) < 0)
{
err = errno;
asmsh_log_perror("ptrace setoptions failed");
goto err;
}
for(int i=0; i<2; i++) // cont kill & ptrace
{
if(ptrace(PTRACE_CONT, env->pid, NULL, 0) < 0)
{
err = errno;
asmsh_log_perror("ptrace CONT failed after attach");
goto err;
}
if(waitpid(env->pid, &wstatus, 0) < 0)
{
err = errno;
asmsh_log_perror("Unable to wait for child process to stop after exec");
goto err;
}
if(wstatus >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC<<8)) && \
!WIFSTOPPED(wstatus))
{
goto err_wstatus;
}
}
// next child events are : execve ret, mmap in, mmap out
for(int i=0; i<3; i++)
{
if(ptrace(PTRACE_SYSCALL, env->pid, NULL, 0) < 0)
{
err = errno;
asmsh_log_error("Unable to ptrace syscall on %dth time : %s",
i+1, strerror(errno));
goto err;
}
if(waitpid(env->pid, &wstatus, 0) < 0)
{
err = errno;
asmsh_log_perror("Unable to wait for child process to stop on syscall");
goto err;
}
if(wstatus != 1407 && wstatus != 263551)
{
dprintf(2, "unexpected status after ptrace syscall %d\n", wstatus);
goto err_wstatus;
}
}
// mmap done by child process
// WARNING totally depends on child.s source
// right now there is only 4 instructions (the fourth is the jmp to the txt_map)
// before reaching the start of the code mmap
for(int i=0; i<4; i++)
{
/* // DEBUG to monitor placement in child exec
asmsh_env_update_regs(env);
dprintf(2, "%d) rax: %08X rip : %08X mmap_addr = %08X\n",
i, env->regs.rax, env->regs.rip, env->txt_map_ptr);
*/
if(asmsh_env_step(env, NULL))
{
err=errno;
goto err;
}
}
if(!env->childpath) { unlink(childpath); } // rm tmp child exec
return 0;
/// TODO replace by an utility function that logs ?
err_wstatus:
if(WIFEXITED(wstatus))
{
dprintf(2, "Child exited with status %d\n", WEXITSTATUS(wstatus));
}
else if(WIFSIGNALED(wstatus))
{
if(WCOREDUMP(wstatus))
{
dprintf(2, "Child segfault\n");
}
else
{
dprintf(2, "Child killed by sig %d\n", WTERMSIG(wstatus));
}
}
else if(WIFSTOPPED(wstatus))
{
dprintf(2, "Child stopped by sig %d\n", WSTOPSIG(wstatus));
}
else
{
dprintf(2, "Unexpected child status 0x%04X\n", wstatus);
}
err = ECHILD;
err:
kill(env->pid, SIGKILL);
err_fork:
if(!env->childpath) { unlink(childpath); } // rm tmp child exec
errno = err;
return -1;
}
static void _asmsh_env_child(const char *childpath)
{
char *argv[] = {NULL, NULL};
char *envp[] = {NULL};
if(!(argv[0] = strdupa(childpath)))
{
int err = errno;
perror("Unable to strdupa asmsh childpath :/");
//asmsh_log_perror("Unable to strdupa asmsh childpath :/");
///! @todo dump logs before exit !
exit(err?err:-1);
}
kill(getpid(), SIGSTOP);
execve(childpath, argv, envp);
int err = errno;
perror("Unable to execve");
//asmsh_log_perror("Child is unable to execve");
///! @todo dump logs before exit !
exit(err?err:-1);
}
static char *asmsh_env_tmpexec()
{
char *ret = strdup("asmsh_child_XXXXXXXXX");
int err;
if(!ret)
{
err = errno;
asmsh_log_perror("getting a temporary file name");
errno = err;
return NULL;
}
int tmpfd = mkstemp(ret);
if(tmpfd < 0)
{
err = errno;
asmsh_log_perror("Unable to mk temporary file");
errno = err;
return NULL;
}
const int sz = &_binary_child_end - &_binary_child_start;
int rsz = write(tmpfd, &_binary_child_start, sz);
if(rsz<sz)
{
// TODO : differenciate incomplete write & error !
err = errno;
asmsh_log_perror("Unable to write the child executable");
free(ret);
errno = err;
return NULL;
}
fchmod(tmpfd, 0555);
close(tmpfd);
return ret;
}