Root-Me: GB – Basic GameBoy crackme walkthrough


In this article I will describe how I solved the GB – Basic GameBoy crackme challenge from Root-Me.

Before reading this article you should attempt to solve the challenge on your own. Start by reading/skimming through the GameBoy CPU manual then download an emulator such as mGba and play with the ROM. To disassemble the ROM I’ve used Ghidra and mgbdis.

After reading through the GameBoy CPU manual, I’ve opened the file in Ghidra and found these interesting strings:

If you emulate the ROM using mGba and press the Left key then the Enter key, the message “Left” will appear on the screen, for the moment I can assume that the strings are related to the message.

Ghidra doesn’t know about the GameBoy ROM structure and I don’t know either, so I’ve tried to disassemble the ROM with the help of a specialized GameBoy disassembler:

➜  mgbdis git:(master) ✗ ./ 
Loading ""...
ROM MD5 hash: 51ff0d38b93107c38a753d9b0ee1576c
Generating labels...
Generating disassembly..
Disassembly generated in "/home/denis/Downloads/mgbdis/disassembly"

By opening the output of the disassembler and searching for 0459 (the address of the Nope string) I’ve found code that references it at 0415.

I went back to Ghidra and disassembled the block at 0415 by pressing the D key.

This seems to be the block that checks some values from RAM and prints the flag if the values are correct.

The first comparison is done for the Left key. It loads the value from the RAM address 0xc0b0 into the A register.

The A register is then compared to 0x32 and there’s a conditional jump to the NopeBlock. JP NZ,NN ;jumps if Z is reset

        ram:03c0 11 b0 c0        LD         DE,0xc0b0
        ram:03c3 1a              LD         A,(DE=>DAT_ram_c0b0)
        ram:03c4 4f              LD         C,A
        ram:03c5 fe 32           CP         0x32
        ram:03c7 c2 15 04        JP         NZ,NopeBlock
        ram:03ca 18 03           JR         LAB_ram_03cf
        ram:03cc c3 15 04        JP         NopeBlock

CP is a subtraction from A that doesn't update A, only the flags it would have set/reset if it really was subtracted. If A == N, then Z flag is set.

So if A = 0x32 then Z is set and the jump to NopeBlock only happens if Z is reset. Sounds easy to me. To pass the check the value at 0xc0b0 needs to be 0x32.

If we look at the memory view in the mGba emulator, the location at 0xc0b0 has the value 0x39. If we press the Left key the value is decremented.

To get the flag you must solve for the Up, Down and Right keys.

Thanks for reading! ❤

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!