A shell that runs x86_64 assembly
c
x86-64
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

asm_env.c 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /* Copyright Yann Weber <asmsh@yannweb.net>
  2. This file is part of asmsh.
  3. asmsh is free software: you can redistribute it and/or modify it under the
  4. terms of the GNU General Public License as published by the Free Software
  5. Foundation, either version 3 of the License, or any later version.
  6. asmsh is distributed in the hope that it will be useful, but WITHOUT ANY
  7. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  9. details.
  10. You should have received a copy of the GNU General Public License along
  11. with asmsh. If not, see <https://www.gnu.org/licenses/>.
  12. */
  13. #include "asm_env.h"
  14. static int _asmsh_env_spawn(asmsh_env_t *asmenv);
  15. static void _asmsh_env_child(const char *childpath);
  16. /** Return a path (that should be freed) of a temporary executable
  17. * child that can be exec on */
  18. static char *asmsh_env_tmpexec();
  19. /* binary buffer of the child elf */
  20. extern unsigned char _binary_child_start;
  21. extern unsigned char _binary_child_end;
  22. asmsh_env_t* asmsh_env(const char *childpath)
  23. {
  24. asmsh_env_t *res;
  25. int err;
  26. if((res = malloc(sizeof(*res))) == NULL)
  27. {
  28. err = errno;
  29. asmsh_log_perror("Unable to allocate env");
  30. errno = err;
  31. return NULL;
  32. }
  33. child_mmap_init(&(res->mmap));
  34. asmsh_brk_init(&res->brks);
  35. res->childpath = NULL;
  36. if(childpath && (res->childpath = strdup(childpath)) == NULL)
  37. {
  38. err=errno;
  39. goto err_pathdup;
  40. }
  41. if(_asmsh_env_spawn(res) < 0)
  42. {
  43. err = errno;
  44. goto err;
  45. }
  46. if(asmsh_env_update(res) < 0)
  47. {
  48. err=errno;
  49. goto err;
  50. }
  51. res->txt_map_addr = (void*)res->regs.rax;
  52. res->txt_map_sz = res->regs.r14;
  53. res->stack_addr = (void*)res->regs.r15;
  54. res->stack_sz = (size_t)res->stack_addr - res->regs.rsp;
  55. res->code_write_ptr = res->txt_map_addr;
  56. return res;
  57. err:
  58. if(res->childpath)
  59. {
  60. free(res->childpath);
  61. }
  62. err_pathdup:
  63. free(res);
  64. errno = err;
  65. return NULL;
  66. }
  67. void asmsh_env_free(asmsh_env_t *asmenv)
  68. {
  69. if(asmenv->mmap.maps)
  70. {
  71. free(asmenv->mmap.maps);
  72. }
  73. kill(asmenv->pid, SIGKILL);
  74. asmsh_brk_free(&asmenv->brks);
  75. free(asmenv->childpath);
  76. free(asmenv);
  77. }
  78. int asmsh_env_write_mem(asmsh_env_t *env, void *addr, const unsigned char *buf, size_t buf_sz)
  79. {
  80. int err;
  81. u_int64_t data;
  82. unsigned char written;
  83. char bleft = (u_int64_t)addr % 8;
  84. written = 0;
  85. if(bleft)
  86. {
  87. // First write to correct further write allignement
  88. void *wr_addr = (void*)(((u_int64_t)addr / 8) * 8);
  89. char towrite = 8 - bleft;
  90. towrite = (towrite > buf_sz)?buf_sz:towrite;
  91. errno = 0;
  92. data = ptrace(PTRACE_PEEKTEXT, env->pid, wr_addr, NULL);
  93. if(errno)
  94. {
  95. err = errno;
  96. asmsh_log_perror("Unable to peektext in order to allign write");
  97. errno = err;
  98. return -1;
  99. }
  100. memcpy(((unsigned char*)&data)+bleft, &(buf[written]), towrite);
  101. if(ptrace(PTRACE_POKETEXT, env->pid, wr_addr, data) < 0)
  102. {
  103. err = errno;
  104. asmsh_log_perror("Unable to poketext in order to allign write");
  105. errno = err;
  106. return -1;
  107. }
  108. written += towrite;
  109. env->code_write_ptr += towrite;
  110. }
  111. if(written >= buf_sz)
  112. {
  113. return 0;
  114. }
  115. // env->code_write_ptr is now word alligned and "written" bytes are
  116. // allready written
  117. while(written < buf_sz)
  118. {
  119. char towrite = buf_sz - written;
  120. if(towrite >= 8)
  121. {
  122. data = *(u_int64_t*)(&buf[written]);
  123. }
  124. else
  125. {
  126. data = 0;
  127. memcpy(&data, &buf[written], towrite);
  128. }
  129. if(ptrace(PTRACE_POKETEXT, env->pid, env->code_write_ptr, data) < 0)
  130. {
  131. err = errno;
  132. asmsh_log_perror("Unable to poketext");
  133. errno = err;
  134. return -1;
  135. }
  136. written += towrite;
  137. env->code_write_ptr += towrite;
  138. }
  139. return 0;
  140. }
  141. int asmsh_env_write_code(asmsh_env_t *env, asmsh_bytecode_t *bcode)
  142. {
  143. return asmsh_env_write_mem(env, env->code_write_ptr,
  144. bcode->bytes, bcode->size);
  145. }
  146. int asmsh_env_step(asmsh_env_t *env, int *status)
  147. {
  148. int err;
  149. if(status) { *status = 0; }
  150. if(ptrace(PTRACE_SINGLESTEP, env->pid, NULL, 0) < 0)
  151. {
  152. err = errno;
  153. asmsh_log_perror("Unable to ptrace singlestep");
  154. goto err;
  155. }
  156. if(waitpid(env->pid, &env->status, 0) < 0)
  157. {
  158. err = errno;
  159. asmsh_log_perror("Unable to wait for child process to stop on step");
  160. goto err;
  161. }
  162. if(status) { *status = env->status; }
  163. if(!WIFSTOPPED(env->status) || env->status >> 8 != 5)
  164. {
  165. goto err_wstatus;
  166. }
  167. return 0;
  168. /// TODO replace by an utility function that logs ?
  169. err_wstatus:
  170. if(WIFEXITED(env->status))
  171. {
  172. dprintf(2, "Child exited with status %d\n", WEXITSTATUS(env->status));
  173. }
  174. else if(WIFSIGNALED(env->status))
  175. {
  176. if(WCOREDUMP(env->status))
  177. {
  178. dprintf(2, "Child segfault\n");
  179. }
  180. else
  181. {
  182. dprintf(2, "Child killed by sig %d\n", WTERMSIG(env->status));
  183. }
  184. }
  185. else if(WIFSTOPPED(env->status))
  186. {
  187. dprintf(2, "Child stopped by %s(sig#%d)\n",
  188. strsignal(WSTOPSIG(env->status)),
  189. WSTOPSIG(env->status));
  190. }
  191. else
  192. {
  193. dprintf(2, "Unexpected child status 0x%04X\n", env->status);
  194. }
  195. errno = ECHILD;
  196. return 1;
  197. err:
  198. errno = err;
  199. return -1;
  200. }
  201. int asmsh_env_run(asmsh_env_t *env, int *status)
  202. {
  203. while(1)
  204. {
  205. int ret;
  206. if((ret = asmsh_env_update_regs(env)) < 0)
  207. {
  208. return ret;
  209. }
  210. const unsigned long rip = env->regs.rip;
  211. if(asmsh_brk_isset(&(env->brks), rip))
  212. {
  213. asmsh_log_info("Breakpoint %016lx reached", rip);
  214. *status = 0;
  215. return 0;
  216. }
  217. if((ret = asmsh_env_step(env, status)) != 0)
  218. {
  219. return ret;
  220. }
  221. }
  222. }
  223. int asmsh_env_update(asmsh_env_t *asmenv)
  224. {
  225. if(asmsh_env_update_regs(asmenv) < 0)
  226. {
  227. return -1;
  228. }
  229. return child_mmap_get(asmenv->pid, &(asmenv->mmap));
  230. }
  231. int asmsh_env_update_maps(asmsh_env_t *asmenv)
  232. {
  233. if(child_mmap_get(asmenv->pid, &(asmenv->mmap)) < 0)
  234. {
  235. return -1;
  236. }
  237. return 0;
  238. }
  239. int asmsh_env_update_regs(asmsh_env_t *asmenv)
  240. {
  241. bzero(&(asmenv->regs), sizeof(asmenv->regs));
  242. if(ptrace(PTRACE_GETREGS, asmenv->pid, NULL, &(asmenv->regs)) < 0)
  243. {
  244. int err = errno;
  245. asmsh_log_perror("ptrace getregs error");
  246. errno = err;
  247. return -1;
  248. }
  249. return 0;
  250. }
  251. static int _asmsh_env_spawn(asmsh_env_t *env)
  252. {
  253. int err;
  254. int wstatus;
  255. const char *childpath = env->childpath?env->childpath:asmsh_env_tmpexec();
  256. if(!childpath)
  257. {
  258. return -1; // Error in asmsh_env_tmpexec()
  259. }
  260. if((env->pid = fork()) == -1)
  261. {
  262. err = errno;
  263. asmsh_log_perror("Unable to fork!");
  264. goto err_fork;
  265. }
  266. else if(env->pid == 0)
  267. {
  268. _asmsh_env_child(childpath);
  269. }
  270. if(waitpid(env->pid, &wstatus, WUNTRACED) < 0)
  271. {
  272. err=errno;
  273. asmsh_log_perror("Unable to wait for child process");
  274. goto err;
  275. }
  276. if(!WIFSTOPPED(wstatus))
  277. {
  278. dprintf(2, "child didn't stop as expected");
  279. goto err_wstatus;
  280. }
  281. if(ptrace(PTRACE_ATTACH, env->pid, 0, 0) == -1)
  282. {
  283. err=errno;
  284. asmsh_log_perror("Unable to attach to child process");
  285. goto err;
  286. }
  287. if(waitpid(env->pid, &wstatus, 0) < 0)
  288. {
  289. err=errno;
  290. asmsh_log_perror("Unable to wait for child process");
  291. goto err;
  292. }
  293. if(!WIFSTOPPED(wstatus))
  294. {
  295. dprintf(2, "child didn't stop as expected");
  296. goto err_wstatus;
  297. }
  298. // Attached to child
  299. // tell child to stop on exec
  300. if(ptrace(PTRACE_SETOPTIONS, env->pid, NULL, PTRACE_O_TRACEEXEC) < 0)
  301. {
  302. err = errno;
  303. asmsh_log_perror("ptrace setoptions failed");
  304. goto err;
  305. }
  306. for(int i=0; i<2; i++) // cont kill & ptrace
  307. {
  308. if(ptrace(PTRACE_CONT, env->pid, NULL, 0) < 0)
  309. {
  310. err = errno;
  311. asmsh_log_perror("ptrace CONT failed after attach");
  312. goto err;
  313. }
  314. if(waitpid(env->pid, &wstatus, 0) < 0)
  315. {
  316. err = errno;
  317. asmsh_log_perror("Unable to wait for child process to stop after exec");
  318. goto err;
  319. }
  320. if(wstatus >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC<<8)) && \
  321. !WIFSTOPPED(wstatus))
  322. {
  323. goto err_wstatus;
  324. }
  325. }
  326. // next child events are : execve ret, mmap in, mmap out
  327. for(int i=0; i<3; i++)
  328. {
  329. if(ptrace(PTRACE_SYSCALL, env->pid, NULL, 0) < 0)
  330. {
  331. err = errno;
  332. asmsh_log_error("Unable to ptrace syscall on %dth time : %s",
  333. i+1, strerror(errno));
  334. goto err;
  335. }
  336. if(waitpid(env->pid, &wstatus, 0) < 0)
  337. {
  338. err = errno;
  339. asmsh_log_perror("Unable to wait for child process to stop on syscall");
  340. goto err;
  341. }
  342. if(wstatus != 1407 && wstatus != 263551)
  343. {
  344. dprintf(2, "unexpected status after ptrace syscall %d\n", wstatus);
  345. goto err_wstatus;
  346. }
  347. }
  348. // mmap done by child process
  349. // WARNING totally depends on child.s source
  350. // right now there is only 4 instructions (the fourth is the jmp to the txt_map)
  351. // before reaching the start of the code mmap
  352. for(int i=0; i<4; i++)
  353. {
  354. /* // DEBUG to monitor placement in child exec
  355. asmsh_env_update_regs(env);
  356. dprintf(2, "%d) rax: %08X rip : %08X mmap_addr = %08X\n",
  357. i, env->regs.rax, env->regs.rip, env->txt_map_ptr);
  358. */
  359. if(asmsh_env_step(env, NULL))
  360. {
  361. err=errno;
  362. goto err;
  363. }
  364. }
  365. if(!env->childpath) { unlink(childpath); } // rm tmp child exec
  366. return 0;
  367. /// TODO replace by an utility function that logs ?
  368. err_wstatus:
  369. if(WIFEXITED(wstatus))
  370. {
  371. dprintf(2, "Child exited with status %d\n", WEXITSTATUS(wstatus));
  372. }
  373. else if(WIFSIGNALED(wstatus))
  374. {
  375. if(WCOREDUMP(wstatus))
  376. {
  377. dprintf(2, "Child segfault\n");
  378. }
  379. else
  380. {
  381. dprintf(2, "Child killed by sig %d\n", WTERMSIG(wstatus));
  382. }
  383. }
  384. else if(WIFSTOPPED(wstatus))
  385. {
  386. dprintf(2, "Child stopped by sig %d\n", WSTOPSIG(wstatus));
  387. }
  388. else
  389. {
  390. dprintf(2, "Unexpected child status 0x%04X\n", wstatus);
  391. }
  392. err = ECHILD;
  393. err:
  394. kill(env->pid, SIGKILL);
  395. err_fork:
  396. if(!env->childpath) { unlink(childpath); } // rm tmp child exec
  397. errno = err;
  398. return -1;
  399. }
  400. static void _asmsh_env_child(const char *childpath)
  401. {
  402. char *argv[] = {NULL, NULL};
  403. char *envp[] = {NULL};
  404. if(!(argv[0] = strdupa(childpath)))
  405. {
  406. int err = errno;
  407. perror("Unable to strdupa asmsh childpath :/");
  408. //asmsh_log_perror("Unable to strdupa asmsh childpath :/");
  409. ///! @todo dump logs before exit !
  410. exit(err?err:-1);
  411. }
  412. kill(getpid(), SIGSTOP);
  413. execve(childpath, argv, envp);
  414. int err = errno;
  415. perror("Unable to execve");
  416. //asmsh_log_perror("Child is unable to execve");
  417. ///! @todo dump logs before exit !
  418. exit(err?err:-1);
  419. }
  420. static char *asmsh_env_tmpexec()
  421. {
  422. char *ret = strdup("asmsh_child_XXXXXXXXX");
  423. int err;
  424. if(!ret)
  425. {
  426. err = errno;
  427. asmsh_log_perror("getting a temporary file name");
  428. errno = err;
  429. return NULL;
  430. }
  431. int tmpfd = mkstemp(ret);
  432. if(tmpfd < 0)
  433. {
  434. err = errno;
  435. asmsh_log_perror("Unable to mk temporary file");
  436. errno = err;
  437. return NULL;
  438. }
  439. const int sz = &_binary_child_end - &_binary_child_start;
  440. int rsz = write(tmpfd, &_binary_child_start, sz);
  441. if(rsz<sz)
  442. {
  443. // TODO : differenciate incomplete write & error !
  444. err = errno;
  445. asmsh_log_perror("Unable to write the child executable");
  446. free(ret);
  447. errno = err;
  448. return NULL;
  449. }
  450. fchmod(tmpfd, 0555);
  451. close(tmpfd);
  452. return ret;
  453. }