Ghidra Scripting: Annotating Linux system calls

I had some fun this weekend messing around with Ghidra. Having such a powerful tool for free is truly a game changer.

To start scripting in Ghidra, I downloaded the latest Eclipse for Java Developers Version: 2019-09 R (4.13.0), Ghidra and Open JDK, I believe any JDK version 11+ will work.

After downloading the JDK, extract the zip, put it somewhere and modify your PATH and JAVA_HOME environment variables to point to it:

export JAVA_HOME="/home/denis/jdk-13"
export PATH="/home/denis/jdk-13/bin:$PATH"

Next, you should start Ghidra in order to associate the JDK with it, now close it, then start Eclipse. In Eclipse, install the GhidraDev extension from the archive which is found in ghidra_9.1-BETA_DEV/Extensions/Eclipse.

After installing GhidraDev, you may now create a new Ghidra Scripts project.

This is cool because all your scripts are stored in ~/ghidra_scripts and linked to the project, if you change the JDK you may safely delete the project and recreate it without losing files.

If you click Run or Debug, Eclipse will be prompted to start a new Ghidra or Ghidra Headless instance, by clicking Debug, you can set breakpoints in the scripts you want to Debug. Run the scripts by opening Ghidra’s Script Manager and clicking Run. When the code reaches the breakpoints, the script’s execution will stop and you can debug it in Eclipse.

You can then resume the script, cancel it from Ghidra or modify the script without having to restart Ghidra. If you hit the stop button Ghidra will shut down, I’ve encountered some popups complaining that hot code replacement won’t work when I tried to save a script, I ignored it and it worked just fine.

By opening Ghidra Script Manager and right clicking on a script then clicking on Ghidra API Help will open a web browser with the scripting documentation.

To make my script I started with as a base, iterated through all instructions, saved the value of EAX, searched for int 0x80 and added a plate comment with the system call’s name.

The final script:

Please note the ugly systemCallNames.put(1, "sys_exit");, I haven’t used Java that much and I couldn’t find a good way to initialize maps in an inline statement. If you have a suggestion please write it in the comments.

To get the system calls names I used a reference table from Shell Storm and some JavaScript to parse the table.

table = document.getElementsByTagName("table")[0];
tableRows = table.children[0].children;

// for each tableRows starting from 1 skip header
for (index = 1; index < tableRows.length; index++) {
    rowData = tableRows[index].children;
    syscallValue = rowData[0].innerText;
    syscallName = rowData[1].innerText;
    console.log(`systemCallNames.put(${syscallValue}, "${syscallName}");`)
VM66:9 systemCallNames.put(0, "sys_read");
VM66:9 systemCallNames.put(1, "sys_write");
VM66:9 systemCallNames.put(2, "sys_open");
VM66:9 systemCallNames.put(3, "sys_close");
VM66:9 systemCallNames.put(4, "sys_stat");
VM66:9 systemCallNames.put(5, "sys_fstat");
VM66:9 systemCallNames.put(6, "sys_lstat");
VM66:9 systemCallNames.put(7, "sys_poll");
VM66:9 systemCallNames.put(8, "sys_lseek");
VM66:9 systemCallNames.put(9, "sys_mmap");
VM66:9 systemCallNames.put(10, "sys_mprotect");
VM66:9 systemCallNames.put(11, "sys_munmap");
VM66:9 systemCallNames.put(12, "sys_brk");
VM66:9 systemCallNames.put(13, "sys_rt_sigaction");
VM66:9 systemCallNames.put(14, "sys_rt_sigprocmask");
VM66:9 systemCallNames.put(15, "sys_rt_sigreturn");
VM66:9 systemCallNames.put(16, "sys_ioctl");
VM66:9 systemCallNames.put(17, "sys_pread64");
VM66:9 systemCallNames.put(18, "sys_pwrite64");
VM66:9 systemCallNames.put(19, "sys_readv");
VM66:9 systemCallNames.put(20, "sys_writev");
VM66:9 systemCallNames.put(21, "sys_access");
VM66:9 systemCallNames.put(22, "sys_pipe");
VM66:9 systemCallNames.put(23, "sys_select");
VM66:9 systemCallNames.put(24, "sys_sched_yield");
VM66:9 systemCallNames.put(25, "sys_mremap");
VM66:9 systemCallNames.put(26, "sys_msync");
VM66:9 systemCallNames.put(27, "sys_mincore");
VM66:9 systemCallNames.put(28, "sys_madvise");
VM66:9 systemCallNames.put(29, "sys_shmget");
VM66:9 systemCallNames.put(30, "sys_shmat");
VM66:9 systemCallNames.put(31, "sys_shmctl");
VM66:9 systemCallNames.put(32, "sys_dup");
VM66:9 systemCallNames.put(33, "sys_dup2");
VM66:9 systemCallNames.put(34, "sys_pause");
VM66:9 systemCallNames.put(35, "sys_nanosleep");
VM66:9 systemCallNames.put(36, "sys_getitimer");
VM66:9 systemCallNames.put(37, "sys_alarm");
VM66:9 systemCallNames.put(38, "sys_setitimer");
VM66:9 systemCallNames.put(39, "sys_getpid");
VM66:9 systemCallNames.put(40, "sys_sendfile");
VM66:9 systemCallNames.put(41, "sys_socket");
VM66:9 systemCallNames.put(42, "sys_connect");
VM66:9 systemCallNames.put(43, "sys_accept");
VM66:9 systemCallNames.put(44, "sys_sendto");
VM66:9 systemCallNames.put(45, "sys_recvfrom");
VM66:9 systemCallNames.put(46, "sys_sendmsg");
VM66:9 systemCallNames.put(47, "sys_recvmsg");
VM66:9 systemCallNames.put(48, "sys_shutdown");
VM66:9 systemCallNames.put(49, "sys_bind");
VM66:9 systemCallNames.put(50, "sys_listen");
VM66:9 systemCallNames.put(51, "sys_getsockname");
VM66:9 systemCallNames.put(52, "sys_getpeername");
VM66:9 systemCallNames.put(53, "sys_socketpair");
VM66:9 systemCallNames.put(54, "sys_setsockopt");
VM66:9 systemCallNames.put(55, "sys_getsockopt");
VM66:9 systemCallNames.put(56, "sys_clone");
VM66:9 systemCallNames.put(57, "sys_fork");
VM66:9 systemCallNames.put(58, "sys_vfork");
VM66:9 systemCallNames.put(59, "sys_execve");
VM66:9 systemCallNames.put(60, "sys_exit");
VM66:9 systemCallNames.put(61, "sys_wait4");
VM66:9 systemCallNames.put(62, "sys_kill");
VM66:9 systemCallNames.put(63, "sys_uname");
VM66:9 systemCallNames.put(64, "sys_semget");
VM66:9 systemCallNames.put(65, "sys_semop");
VM66:9 systemCallNames.put(66, "sys_semctl");
VM66:9 systemCallNames.put(67, "sys_shmdt");
VM66:9 systemCallNames.put(68, "sys_msgget");
VM66:9 systemCallNames.put(69, "sys_msgsnd");
VM66:9 systemCallNames.put(70, "sys_msgrcv");
VM66:9 systemCallNames.put(71, "sys_msgctl");
VM66:9 systemCallNames.put(72, "sys_fcntl");
VM66:9 systemCallNames.put(73, "sys_flock");
VM66:9 systemCallNames.put(74, "sys_fsync");
VM66:9 systemCallNames.put(75, "sys_fdatasync");
VM66:9 systemCallNames.put(76, "sys_truncate");
VM66:9 systemCallNames.put(77, "sys_ftruncate");
VM66:9 systemCallNames.put(78, "sys_getdents");
VM66:9 systemCallNames.put(79, "sys_getcwd");
VM66:9 systemCallNames.put(80, "sys_chdir");
VM66:9 systemCallNames.put(81, "sys_fchdir");
VM66:9 systemCallNames.put(82, "sys_rename");
VM66:9 systemCallNames.put(83, "sys_mkdir");
VM66:9 systemCallNames.put(84, "sys_rmdir");
VM66:9 systemCallNames.put(85, "sys_creat");
VM66:9 systemCallNames.put(86, "sys_link");
VM66:9 systemCallNames.put(87, "sys_unlink");
VM66:9 systemCallNames.put(88, "sys_symlink");
VM66:9 systemCallNames.put(89, "sys_readlink");
VM66:9 systemCallNames.put(90, "sys_chmod");
VM66:9 systemCallNames.put(91, "sys_fchmod");
VM66:9 systemCallNames.put(92, "sys_chown");
VM66:9 systemCallNames.put(93, "sys_fchown");
VM66:9 systemCallNames.put(94, "sys_lchown");
VM66:9 systemCallNames.put(95, "sys_umask");
VM66:9 systemCallNames.put(96, "sys_gettimeofday");
VM66:9 systemCallNames.put(97, "sys_getrlimit");
VM66:9 systemCallNames.put(98, "sys_getrusage");
VM66:9 systemCallNames.put(99, "sys_sysinfo");
VM66:9 systemCallNames.put(100, "sys_times");
VM66:9 systemCallNames.put(101, "sys_ptrace");
VM66:9 systemCallNames.put(102, "sys_getuid");
VM66:9 systemCallNames.put(103, "sys_syslog");
VM66:9 systemCallNames.put(104, "sys_getgid");
VM66:9 systemCallNames.put(105, "sys_setuid");
VM66:9 systemCallNames.put(106, "sys_setgid");
VM66:9 systemCallNames.put(107, "sys_geteuid");
VM66:9 systemCallNames.put(108, "sys_getegid");
VM66:9 systemCallNames.put(109, "sys_setpgid");
VM66:9 systemCallNames.put(110, "sys_getppid");
VM66:9 systemCallNames.put(111, "sys_getpgrp");
VM66:9 systemCallNames.put(112, "sys_setsid");
VM66:9 systemCallNames.put(113, "sys_setreuid");
VM66:9 systemCallNames.put(114, "sys_setregid");
VM66:9 systemCallNames.put(115, "sys_getgroups");
VM66:9 systemCallNames.put(116, "sys_setgroups");
VM66:9 systemCallNames.put(117, "sys_setresuid");
VM66:9 systemCallNames.put(118, "sys_getresuid");
VM66:9 systemCallNames.put(119, "sys_setresgid");
VM66:9 systemCallNames.put(120, "sys_getresgid");
VM66:9 systemCallNames.put(121, "sys_getpgid");
VM66:9 systemCallNames.put(122, "sys_setfsuid");
VM66:9 systemCallNames.put(123, "sys_setfsgid");
VM66:9 systemCallNames.put(124, "sys_getsid");
VM66:9 systemCallNames.put(125, "sys_capget");
VM66:9 systemCallNames.put(126, "sys_capset");
VM66:9 systemCallNames.put(127, "sys_rt_sigpending");
VM66:9 systemCallNames.put(128, "sys_rt_sigtimedwait");
VM66:9 systemCallNames.put(129, "sys_rt_sigqueueinfo");
VM66:9 systemCallNames.put(130, "sys_rt_sigsuspend");
VM66:9 systemCallNames.put(131, "sys_sigaltstack");
VM66:9 systemCallNames.put(132, "sys_utime");
VM66:9 systemCallNames.put(133, "sys_mknod");
VM66:9 systemCallNames.put(134, "sys_uselib");
VM66:9 systemCallNames.put(135, "sys_personality");
VM66:9 systemCallNames.put(136, "sys_ustat");
VM66:9 systemCallNames.put(137, "sys_statfs");
VM66:9 systemCallNames.put(138, "sys_fstatfs");
VM66:9 systemCallNames.put(139, "sys_sysfs");
VM66:9 systemCallNames.put(140, "sys_getpriority");
VM66:9 systemCallNames.put(141, "sys_setpriority");
VM66:9 systemCallNames.put(142, "sys_sched_setparam");
VM66:9 systemCallNames.put(143, "sys_sched_getparam");
VM66:9 systemCallNames.put(144, "sys_sched_setscheduler");
VM66:9 systemCallNames.put(145, "sys_sched_getscheduler");
VM66:9 systemCallNames.put(146, "sys_sched_get_priority_max");
VM66:9 systemCallNames.put(147, "sys_sched_get_priority_min");
VM66:9 systemCallNames.put(148, "sys_sched_rr_get_interval");
VM66:9 systemCallNames.put(149, "sys_mlock");
VM66:9 systemCallNames.put(150, "sys_munlock");
VM66:9 systemCallNames.put(151, "sys_mlockall");
VM66:9 systemCallNames.put(152, "sys_munlockall");
VM66:9 systemCallNames.put(153, "sys_vhangup");
VM66:9 systemCallNames.put(154, "sys_modify_ldt");
VM66:9 systemCallNames.put(155, "sys_pivot_root");
VM66:9 systemCallNames.put(156, "sys__sysctl");
VM66:9 systemCallNames.put(157, "sys_prctl");
VM66:9 systemCallNames.put(158, "sys_arch_prctl");
VM66:9 systemCallNames.put(159, "sys_adjtimex");
VM66:9 systemCallNames.put(160, "sys_setrlimit");
VM66:9 systemCallNames.put(161, "sys_chroot");
VM66:9 systemCallNames.put(162, "sys_sync");
VM66:9 systemCallNames.put(163, "sys_acct");
VM66:9 systemCallNames.put(164, "sys_settimeofday");
VM66:9 systemCallNames.put(165, "sys_mount");
VM66:9 systemCallNames.put(166, "sys_umount2");
VM66:9 systemCallNames.put(167, "sys_swapon");
VM66:9 systemCallNames.put(168, "sys_swapoff");
VM66:9 systemCallNames.put(169, "sys_reboot");
VM66:9 systemCallNames.put(170, "sys_sethostname");
VM66:9 systemCallNames.put(171, "sys_setdomainname");
VM66:9 systemCallNames.put(172, "sys_iopl");
VM66:9 systemCallNames.put(173, "sys_ioperm");
VM66:9 systemCallNames.put(174, "sys_create_module");
VM66:9 systemCallNames.put(175, "sys_init_module");
VM66:9 systemCallNames.put(176, "sys_delete_module");
VM66:9 systemCallNames.put(177, "sys_get_kernel_syms");
VM66:9 systemCallNames.put(178, "sys_query_module");
VM66:9 systemCallNames.put(179, "sys_quotactl");
VM66:9 systemCallNames.put(180, "sys_nfsservctl");
VM66:9 systemCallNames.put(181, "sys_getpmsg");
VM66:9 systemCallNames.put(182, "sys_putpmsg");
VM66:9 systemCallNames.put(183, "sys_afs_syscall");
VM66:9 systemCallNames.put(184, "sys_tuxcall");
VM66:9 systemCallNames.put(185, "sys_security");
VM66:9 systemCallNames.put(186, "sys_gettid");
VM66:9 systemCallNames.put(187, "sys_readahead");
VM66:9 systemCallNames.put(188, "sys_setxattr");
VM66:9 systemCallNames.put(189, "sys_lsetxattr");
VM66:9 systemCallNames.put(190, "sys_fsetxattr");
VM66:9 systemCallNames.put(191, "sys_getxattr");
VM66:9 systemCallNames.put(192, "sys_lgetxattr");
VM66:9 systemCallNames.put(193, "sys_fgetxattr");
VM66:9 systemCallNames.put(194, "sys_listxattr");
VM66:9 systemCallNames.put(195, "sys_llistxattr");
VM66:9 systemCallNames.put(196, "sys_flistxattr");
VM66:9 systemCallNames.put(197, "sys_removexattr");
VM66:9 systemCallNames.put(198, "sys_lremovexattr");
VM66:9 systemCallNames.put(199, "sys_fremovexattr");
VM66:9 systemCallNames.put(200, "sys_tkill");
VM66:9 systemCallNames.put(201, "sys_time");
VM66:9 systemCallNames.put(202, "sys_futex");
VM66:9 systemCallNames.put(203, "sys_sched_setaffinity");
VM66:9 systemCallNames.put(204, "sys_sched_getaffinity");
VM66:9 systemCallNames.put(205, "sys_set_thread_area");
VM66:9 systemCallNames.put(206, "sys_io_setup");
VM66:9 systemCallNames.put(207, "sys_io_destroy");
VM66:9 systemCallNames.put(208, "sys_io_getevents");
VM66:9 systemCallNames.put(209, "sys_io_submit");
VM66:9 systemCallNames.put(210, "sys_io_cancel");
VM66:9 systemCallNames.put(211, "sys_get_thread_area");
VM66:9 systemCallNames.put(212, "sys_lookup_dcookie");
VM66:9 systemCallNames.put(213, "sys_epoll_create");
VM66:9 systemCallNames.put(214, "sys_epoll_ctl_old");
VM66:9 systemCallNames.put(215, "sys_epoll_wait_old");
VM66:9 systemCallNames.put(216, "sys_remap_file_pages");
VM66:9 systemCallNames.put(217, "sys_getdents64");
VM66:9 systemCallNames.put(218, "sys_set_tid_address");
VM66:9 systemCallNames.put(219, "sys_restart_syscall");
VM66:9 systemCallNames.put(220, "sys_semtimedop");
VM66:9 systemCallNames.put(221, "sys_fadvise64");
VM66:9 systemCallNames.put(222, "sys_timer_create");
VM66:9 systemCallNames.put(223, "sys_timer_settime");
VM66:9 systemCallNames.put(224, "sys_timer_gettime");
VM66:9 systemCallNames.put(225, "sys_timer_getoverrun");
VM66:9 systemCallNames.put(226, "sys_timer_delete");
VM66:9 systemCallNames.put(227, "sys_clock_settime");
VM66:9 systemCallNames.put(228, "sys_clock_gettime");
VM66:9 systemCallNames.put(229, "sys_clock_getres");
VM66:9 systemCallNames.put(230, "sys_clock_nanosleep");
VM66:9 systemCallNames.put(231, "sys_exit_group");
VM66:9 systemCallNames.put(232, "sys_epoll_wait");
VM66:9 systemCallNames.put(233, "sys_epoll_ctl");
VM66:9 systemCallNames.put(234, "sys_tgkill");
VM66:9 systemCallNames.put(235, "sys_utimes");
VM66:9 systemCallNames.put(236, "sys_vserver");
VM66:9 systemCallNames.put(237, "sys_mbind");
VM66:9 systemCallNames.put(238, "sys_set_mempolicy");
VM66:9 systemCallNames.put(239, "sys_get_mempolicy");
VM66:9 systemCallNames.put(240, "sys_mq_open");
VM66:9 systemCallNames.put(241, "sys_mq_unlink");
VM66:9 systemCallNames.put(242, "sys_mq_timedsend");
VM66:9 systemCallNames.put(243, "sys_mq_timedreceive");
VM66:9 systemCallNames.put(244, "sys_mq_notify");
VM66:9 systemCallNames.put(245, "sys_mq_getsetattr");
VM66:9 systemCallNames.put(246, "sys_kexec_load");
VM66:9 systemCallNames.put(247, "sys_waitid");
VM66:9 systemCallNames.put(248, "sys_add_key");
VM66:9 systemCallNames.put(249, "sys_request_key");
VM66:9 systemCallNames.put(250, "sys_keyctl");
VM66:9 systemCallNames.put(251, "sys_ioprio_set");
VM66:9 systemCallNames.put(252, "sys_ioprio_get");
VM66:9 systemCallNames.put(253, "sys_inotify_init");
VM66:9 systemCallNames.put(254, "sys_inotify_add_watch");
VM66:9 systemCallNames.put(255, "sys_inotify_rm_watch");
VM66:9 systemCallNames.put(256, "sys_migrate_pages");
VM66:9 systemCallNames.put(257, "sys_openat");
VM66:9 systemCallNames.put(258, "sys_mkdirat");
VM66:9 systemCallNames.put(259, "sys_mknodat");
VM66:9 systemCallNames.put(260, "sys_fchownat");
VM66:9 systemCallNames.put(261, "sys_futimesat");
VM66:9 systemCallNames.put(262, "sys_newfstatat");
VM66:9 systemCallNames.put(263, "sys_unlinkat");
VM66:9 systemCallNames.put(264, "sys_renameat");
VM66:9 systemCallNames.put(265, "sys_linkat");
VM66:9 systemCallNames.put(266, "sys_symlinkat");
VM66:9 systemCallNames.put(267, "sys_readlinkat");
VM66:9 systemCallNames.put(268, "sys_fchmodat");
VM66:9 systemCallNames.put(269, "sys_faccessat");
VM66:9 systemCallNames.put(270, "sys_pselect6");
VM66:9 systemCallNames.put(271, "sys_ppoll");
VM66:9 systemCallNames.put(272, "sys_unshare");
VM66:9 systemCallNames.put(273, "sys_set_robust_list");
VM66:9 systemCallNames.put(274, "sys_get_robust_list");
VM66:9 systemCallNames.put(275, "sys_splice");
VM66:9 systemCallNames.put(276, "sys_tee");
VM66:9 systemCallNames.put(277, "sys_sync_file_range");
VM66:9 systemCallNames.put(278, "sys_vmsplice");
VM66:9 systemCallNames.put(279, "sys_move_pages");
VM66:9 systemCallNames.put(280, "sys_utimensat");
VM66:9 systemCallNames.put(281, "sys_epoll_pwait");
VM66:9 systemCallNames.put(282, "sys_signalfd");
VM66:9 systemCallNames.put(283, "sys_timerfd_create");
VM66:9 systemCallNames.put(284, "sys_eventfd");
VM66:9 systemCallNames.put(285, "sys_fallocate");
VM66:9 systemCallNames.put(286, "sys_timerfd_settime");
VM66:9 systemCallNames.put(287, "sys_timerfd_gettime");
VM66:9 systemCallNames.put(288, "sys_accept4");
VM66:9 systemCallNames.put(289, "sys_signalfd4");
VM66:9 systemCallNames.put(290, "sys_eventfd2");
VM66:9 systemCallNames.put(291, "sys_epoll_create1");
VM66:9 systemCallNames.put(292, "sys_dup3");
VM66:9 systemCallNames.put(293, "sys_pipe2");
VM66:9 systemCallNames.put(294, "sys_inotify_init1");
VM66:9 systemCallNames.put(295, "sys_preadv");
VM66:9 systemCallNames.put(296, "sys_pwritev");
VM66:9 systemCallNames.put(297, "sys_rt_tgsigqueueinfo");
VM66:9 systemCallNames.put(298, "sys_perf_event_open");
VM66:9 systemCallNames.put(299, "sys_recvmmsg");
VM66:9 systemCallNames.put(300, "sys_fanotify_init");
VM66:9 systemCallNames.put(301, "sys_fanotify_mark");
VM66:9 systemCallNames.put(302, "sys_prlimit64");
VM66:9 systemCallNames.put(303, "sys_name_to_handle_at");
VM66:9 systemCallNames.put(304, "sys_open_by_handle_at");
VM66:9 systemCallNames.put(305, "sys_clock_adjtime");
VM66:9 systemCallNames.put(306, "sys_syncfs");
VM66:9 systemCallNames.put(307, "sys_sendmmsg");
VM66:9 systemCallNames.put(308, "sys_setns");
VM66:9 systemCallNames.put(309, "sys_getcpu");
VM66:9 systemCallNames.put(310, "sys_process_vm_readv");
VM66:9 systemCallNames.put(311, "sys_process_vm_writev");
VM66:9 systemCallNames.put(312, "sys_kcmp");
VM66:9 systemCallNames.put(313, "sys_finit_module");
VM66:9 systemCallNames.put(314, "sys_sched_setattr");
VM66:9 systemCallNames.put(315, "sys_sched_getattr");
VM66:9 systemCallNames.put(316, "sys_renameat2");
VM66:9 systemCallNames.put(317, "sys_seccomp");
VM66:9 systemCallNames.put(318, "sys_getrandom");
VM66:9 systemCallNames.put(319, "sys_memfd_create");
VM66:9 systemCallNames.put(320, "sys_kexec_file_load");
VM66:9 systemCallNames.put(321, "sys_bpf");
VM66:9 systemCallNames.put(322, "stub_execveat");
VM66:9 systemCallNames.put(323, "userfaultfd");
VM66:9 systemCallNames.put(324, "membarrier");
VM66:9 systemCallNames.put(325, "mlock2");
VM66:9 systemCallNames.put(326, "copy_file_range");
VM66:9 systemCallNames.put(327, "preadv2");
VM66:9 systemCallNames.put(328, "pwritev2");

Running the script on a binary will annotate the system calls it finds by adding a plate comment.

In conclusion, creating my first Ghidra script wasn’t that hard and once I’ve figured out how to setup Eclipse and link it properly the development experience was a bliss. I hope more and more people will adopt Ghidra and contribute to it. Some Ghidra trainings and exercises can be found in ghidra_9.1-BETA_DEV/docs.

Thanks for reading and have a great day!

Root Me – Xor Madness – Walkthrough


In this article I will describe how I solved the PE x86 – Xor Madness challenge from Root-Me

This challenge will ask you for a password and the password is also used to validate the flag. What makes this challenge interesting is that it only uses xorsubcall and ret.

Here’s how I approached the challenge:

  • Since the binary had a few function and some strings were in plain text, I tried to figure out the big picture and labeled all the function accordingly: winlosecheck and so on.
  • I’ve figured out that there are 6 stages, each stage tries to process a part of the password. The start function is also a stage and other stages are similar.
  • Next, I’ve spend a few hours debugging this challenge and documenting everything it does in order to see what it wants.

To solve this challenge you need to know how the XOR operation works, please check that you know this before moving forward.

The behaviour of the challenge is simple, it computes an addr, let’s call this address SOLADDR, and it makes it point to the value 1. [SOLADDR] = 1.

It takes a byte of the password you’ve gave and XORs it two times. The resulting value will be an address, we can call it PASSADDR. The trick is to look at how your password is XORed and make it equal to SOLADDR.

The function that will let you proceed to the next stage only does so when the location pointed to by PASSADDR has the value 1 assigned to it. This is stored into edx. edx = [PASSADDR].

The following function lets you proceed to the next stage if and only if EDX is 1:

weird_ret_addr_change proc near

arg2_next_stage_address= dword ptr  4

xor     eax, eax
xor     eax, [esp+arg2_next_stage_address]
sub     eax, [esp+0]    ; subtract return address from EAX
mul     dl              ; multiply eax with DL. Lower part of EDX
add     eax, [esp+0]    ; add back the return address
xor     ecx, ecx        ; reset ecx
xor     ecx, [esp+0]    ; set ecx to [esp]
xor     [esp+0], ecx    ; reset [esp]; [esp] = 0
xor     [esp+0], eax    ; set [esp] to eax
retn    4

We already know that EDX is set to [PASSADDR], which is EDX = *PASSADDR. Each stages performs this step.

In the main function, scanf scans for a maximum 63 characters (don’t trust anything you read, verify!) and stores the result in EDI, then it moved it into ESI and gets the first byte of the password_input into EBX.

call    ds:scanf        
xor     esp, esp
xor     esp, edi
xor     esi, esi
xor     esi, esp
xor     edi, edi
xor     edi, esi
sub     esp, 100h
xor     ebx, ebx
xor     bl, [esi] ; ebx contains the first byte of the password

EBX is then xored with a value, the first 3 bytes are just for show, we only care about the resulting last byte.

   xor     ebx, 59307554h  ; ebx contains first byte
.text:004010C0                                         ;
.text:004010C0                                         ; Example:
.text:004010C0                                         ;
                                                       ;  in     xor     res
.text:004010C0                                         ; 0x30 ^ 0x54 -> 0x64 test case
.text:004010C0                                         ; 0x6C ^ 0x54 -> 0x38 attempt1
.text:004010C0                                         ; 0x24 ^ 0x54 -> 0x70 attempt2 - success
.text:004010C0                                         ;
.text:004010C0                                         ; The First Xor

The next operations loads ESP into EBX, XORs them with the last resulting byte from the previous operation and saves it somewhere.

.text:004010D0                 xor     eax, esp        ; eax = 0x0019FD74 
.text:004010D2                 xor     eax, ebx        ; eax is now xored with the our byte 0x64 (res)
.text:004010D2                                         ;
.text:004010D2                                         ; 0x0019FD74 ^ 0x64 = 0x0019FD10
.text:004010D2                                         ; the resulting address is used when setting EDX
.text:004010D2                                         ; in this case it is invalid, the correct address is
.text:004010D2                                         ; === 0x0019FD04
.text:004010D4                 xor     edx, edx 
.text:004010D6                 xor     edx, eax        ; edx = 0x0019FD10 (Addr resulting our xored byte)
.text:004010D8                 xor     ecx, ecx        ; ecx = 0
.text:004010DA                 xor     ecx, [eax]      ; try to get value from [eax]
.text:004010DC                 xor     [eax], ecx      ; [EAX] = 0xFFFFFFFE
.text:004010DC                                         ; eax = 0

Note that EDX now contains the address that we control 19FD10, which we can call PASSADDR. We obtained 19FD10 after running the program and giving it 0 as the input.

The next piece of code computes the SOLADDR by XORing ESP with two hardcoded values and makes it point to the value of 1.

.text:004010E0                 xor     eax, esp        ; esp = 0x0019FD74
.text:004010E2                 xor     eax, 0F2h       ; 0x0019FD74 ^ 0xF2 = 0x0019FD86
.text:004010E7                 xor     eax, 82h        ; 0x0019FD86 ^ 0x82 = 0x0019FD04
.text:004010EC                 xor     ecx, ecx        ; ecx = 0
.text:004010EE                 xor     ecx, [eax]      ; ecx = *(0x0019FD04) 
.text:004010F0                 xor     [eax], ecx      ; [eax] = [eax] ; eax = 0
.text:004010F2                 xor     byte ptr [eax], 1 [eax] = 1

Now, the code assigns the value pointed by PASSADDR to EDX. EDX = [PASSADDR].

.text:004010F5                 xor     eax, eax        ; eax = 0
.text:004010F7                 xor     eax, edx        ; eax = 0x0019FD10; edx is set above; PASSADDR
.text:004010F9                 xor     edx, edx        ; edx = 0
.text:004010FB                 xor     edx, [eax]      ; edx = [0x0019FD10]; edx = [PASSADDR]

This is the only information you need to know to solve this challenge. You can now find the password by statically analyzing the next stages.

Thanks for reading!

Practical Binary Analysis – CTF Walkthrough – Level 3, 4


In this article I’ll present you my solution on the Chapter 5 CTF from the book Practical Binary Analysis.

For this binary, the hint is to fix four broken things.

Running file gives us the following response:

binary@binary-VirtualBox:~/ctf$ file ./lvl3 
./lvl3: ERROR: ELF 64-bit LSB executable, Motorola Coldfire, version 1 (Novell Modesto) error reading (Invalid argument)

And the readelf command gives us:

binary@binary-VirtualBox:~/ctf$ readelf -h ./lvl3 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 0b 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            Novell - Modesto
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Motorola Coldfire
  Version:                           0x1
  Entry point address:               0x4005d0
  Start of program headers:          4022250974 (bytes into file)
  Start of section headers:          4480 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28
readelf: Error: Reading 0x1f8 bytes extends past end of file for program headers

At this moment, it was clear that the ELF header is broken, in order to fix it I opened up Wikipedia and the elf specification.

As I went through each field manually, with Binary Ninj. As I was checking the offset of the current byte, at 0x07, Wikipedia says: It is often set to 0 regardless of the target platform. I’ve changed it to 0x00. (Note: I think this field was probably ok as it is)

At offset 0x12, the value Specifies target instruction set architecture and is currently invalid. From googling, I found an article titled: Novell's Next Generation OS Will Natively Support Intel's Future IA-64 Architecture so I set the value to 0x3E.


At offset 0x20 we have the e_phoff which Points to the start of the program header table. It usually follows the file header immediately, making the offset 0x34 or 0x40 for 32- and 64-bit ELF executables, respectively. The value de ad be ef is clearly invalid. I replaced the value with 40 00 00 00.

At this moment I thought I fixed the binary and ran it, it ran and it gave me an invalid flag.

If you run the following command:

binary@binary-VirtualBox:/media/sf_Dropzone$ readelf -S ./lvl3 | grep .text
  [14] .text             NOBITS           0000000000400550  00000550

You’ll see that the .text section is marked as 0x8 - NOBITS and it should be 0x1 - PROGBITS. To make the change I’ve used Binary Ninja as a hex editor, opening the binary in raw mode.

From the readelf command:

  Start of section headers:          4480 (bytes into file)

The start of the section header is 4480 bytes. A section header has the length of 0x40 bytes. 4480 to hex -> 0x1180. 0x40 * 14 + 0x1180 = 0x1500.

At offset 0x1504 we change the type from SHT_NOBITS to SHT_PROGBITS.

After we run the binary we get the valid flag:

3a5c381e40d2fffd95ba4452a0fb4a40  ./lvl3

After finishing level 3 I wanted to go to sleep and instead I thought of running ltrace, strace on the binary and I got this:

binary@binary-VirtualBox:~/ctf$ ltrace ./lvl4
__libc_start_main(0x4004a0, 1, 0x7ffd6fb460e8, 0x400650 <unfinished ...>
setenv("FLAG", "656cf8aecb76113a4dece1688c61d0e7"..., 1)             = 0
+++ exited (status 0) +++

Didn’t expect this, very nice tho.

Thanks for reading!

Introduction to Angr

I always wanted to play around with a binary analysis framework but most of the time I was turned off by how difficult it was to install and use it. Just recently I’ve thought to give angr a try and now I want to share my experience with you! I will present you a two scripts that solve two challenges, if you wish to dig deeper and learn Angr then you should visit it’s official documentation.

angr is a python framework for analyzing binaries. It combines both static and dynamic symbolic (“concolic”) analysis, making it applicable to a variety of tasks.

For me the easiest way to install Angr and get it working on the first try was to download Kali linux, install it in VirtualBox (make sure you have at least 12 GB space for the disk) and execute: pip install angr
From here you can setup your Python dev environment in Kali as you please.

For the first challenge we have the following source code:

//written by bla
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char **argv)

        int count = atoi(argv[1]);
        int buf[10];

        if(count >= 10 ) 
                return 1;

        //printf("%lx\n", (size_t)(count * sizeof(int)));
        memcpy(buf, argv[2], count * sizeof(int));
        if(count == 0x574f4c46) {
                //execl("/bin/sh", "sh" ,NULL);
    } else
                printf("Not today son\n");

        return 0;

Challenge source: level-7

The goal is to find two arguments to give to the program in order to overflow buf into count and display WIN. We can attempt to solve this with trial and error, debugging, do some computation or we can make Angr solve it for us with the following Python script.

import angr
import claripy

def resolve_win(state):
    # if the bytes of "WIN" are found in stdout it returns true
    return  b"WIN" in state.posix.dumps(1)

if __name__ == '__main__':

    # Declare project, load the binary
    proj = angr.Project('./lab-13/0-tutorial/level07')

    # Create a 32-bit symbolic bitvector named "password"
    arg1 = claripy.BVS('sym_arg', 8 * 11)  # maximum 11 * 8 bits
    arg2 = claripy.BVS('sym_arg', 8 * 44)  # maximum 44 * 8 bits

    # We construct an entry_state passing the two arguments
    st = proj.factory.entry_state(args=['./level07', arg1, arg2])
    # he st.libc.max_strtol_len tweak tells the atoi/strtol symbolic representation to
    # resolve strings that are of at most 11 bytes length (the default is 10)
    st.libc.max_strtol_len = 11

    # Now we will create what in angr terms is called a simulation manager.
    pg = proj.factory.simgr(st)

    # This can be read as: explore looking for the path p for which the current state
    # p.state contains the string "WIN" in its standard output (p.state.posix.dumps(1),
    # where 1 is the file descriptor for stdout).

    print("solution found")
    s = pg.found[0]
    print(s.posix.dumps(1)) # dump stdout

    # Print and eval the fist argument
    print("Arg1: ", s.solver.eval(arg1, cast_to=bytes))
    # Print and eval the second argument
    print("Arg2: ", s.solver.eval(arg2, cast_to=bytes))

Running the script will give us the solution for this binary, if the binary would change slightly (the count) we can still run the script and get a solution.

The next challenge is easier, the binary is called multiple-styles and it can be downloaded from here:

By looking at it’s disassembly output:

multiple-styles disassembly

We can see that the program does the following things:

  1. Calls read which reads the ‘password’ from stdin into a buffer.
  2. Loads the string “myvnvsuowsxs}ynk” into a buffer.
  3. Loops through the buffer byte by byte adds 10 00400a27 add dword [rbp-0x54 {var_5c_2} {var_5c_1}], 0xa to it and compares it with the previously loaded string.
  4. If they match it will jump to 0x00400a6c and print “you got it!”

At this point we can google for online caesar cipher, paste the string that got loaded and decipher it with an offset of -10, but we’re going to let angr decipher the password for us.

import angr
import claripy

if __name__ == '__main__':
    proj = angr.Project("./multiple-styles", auto_load_libs=False)

    # Create a 32-bit symbolic bitvector named "password"
    password = claripy.BVS('password', 20*8)

    # We construct a blank_state with the address of main and we pass password to stdin
    st = proj.factory.blank_state(addr=0x004009ae, stdin=password)

    # We create a simulation manager
    pg = proj.factory.simulation_manager(st)

    # We tell angr to look for 0x00400a6c which is the starting address of the green block
    # that prints "you got it!" while telling him to avoid the address 0x00400a40
    pg.explore(find=(0x00400a6c), avoid=(0x00400a40))

    print("solution found")
    # We grab the solution.
    s = pg.found[0]

    # We can print the contents of stdin - 0:
    print("Flag: ", s.posix.dumps(0))

    # We can also get the password from our symbolic bitvector
    print("Pass: ", s.solver.eval(password, cast_to=bytes))

While writing the scripts I’ve used angr version ` Please consult Angr’s official documentation if you wish to learn more!

Thank you for reading! 😀