<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>GDB on Rebel Zhang&#39;s Blog</title>
        <link>https://rebel1725.codeberg.page/blog/en/tags/gdb/</link>
        <description>Recent content in GDB on Rebel Zhang&#39;s Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-GB</language>
        <managingEditor>rebel1725@tilde.club (Rebel Zhang)</managingEditor>
        <webMaster>rebel1725@tilde.club (Rebel Zhang)</webMaster>
        <lastBuildDate>Wed, 27 May 2026 23:56:34 +0800</lastBuildDate><atom:link href="https://rebel1725.codeberg.page/blog/en/tags/gdb/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>A Reverse-Engineering Example With GDB and Iaito/Radare2</title>
            <link>https://rebel1725.codeberg.page/blog/en/post/a-reverse-engineering-example-with-gdb-and-iaito-radare2/</link>
            <pubDate>Wed, 27 May 2026 23:56:34 +0800</pubDate><author>rebel1725@tilde.club (Rebel Zhang)</author>
            <guid>https://rebel1725.codeberg.page/blog/en/post/a-reverse-engineering-example-with-gdb-and-iaito-radare2/</guid>
            <description>&lt;p&gt;Over the past several weeks I have been learning reverse engineering, and I have &lt;a class=&#34;link&#34; href=&#34;../a-brief-introduction-into-reverse-engineering/&#34; &gt;written a blog post&lt;/a&gt; to share my very first experience. Lately, I have been diving a little deeper, solving a CTF challenge using a combination of static analysis and dynamic analysis, and I found it to be a great example for explaining reverse engineering in a bit more depth.&lt;/p&gt;&#xA;&lt;p&gt;I had previously planned to write an article about dynamic analysis with GDB. However, I found that this challenge was also a great example for explaining GDB. To solve this challenge, I will also use another tool, &lt;strong&gt;iaito&lt;/strong&gt;, which is the graphical front-end of radare2.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-challenge&#34;&gt;The challenge&#xA;&lt;/h2&gt;&lt;p&gt;cIMG is a kind of image format used to represent an image made up of characters in different colours in the terminal. It consists of:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;the magic number, which is &lt;code&gt;cIMG&lt;/code&gt;;&lt;/li&gt;&#xA;&lt;li&gt;the version number;&lt;/li&gt;&#xA;&lt;li&gt;the width and height;&lt;/li&gt;&#xA;&lt;li&gt;the data, with each pixel containing R, G, B, and the ASCII character.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This challenge is about feeding a cIMG file to an executable that will give you the flag only if the cIMG meets certain conditions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-source-code&#34;&gt;The source code&#xA;&lt;/h2&gt;&lt;p&gt;Obtain the &lt;a class=&#34;link&#34; href=&#34;https://raw.githubusercontent.com/pwncollege/intro-to-cybersecurity-dojo/refs/heads/main/reverse-engineering/cimg-framebuffer-mini-c/_0/cimg.c&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;&#xA;    &gt;source&lt;/a&gt; of the challenge here. This source code seems to be licensed under the BSD 2-Clause Licence.&lt;/p&gt;&#xA;&lt;p&gt;Compile it using GCC:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ gcc -O3 challenge.c -o challenge&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Open Python and create an example cIMG file:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ python3&#xA;Python 3.13.5 (main, Jun 25 2025, 18:55:22) [GCC 14.2.0] on linux&#xA;Type &amp;#34;help&amp;#34;, &amp;#34;copyright&amp;#34;, &amp;#34;credits&amp;#34; or &amp;#34;license&amp;#34; for more information.&#xA;&amp;gt;&amp;gt;&amp;gt; with open(&amp;#34;example.cimg&amp;#34;, &amp;#34;wb&amp;#34;) as file:&#xA;...     file.write(b&amp;#34;cIMG\x02\0\x0a\x0a&amp;#34;)&#xA;...     file.write(b&amp;#34;\x50&amp;#34;*(10*10*4))&#xA;...     &#xA;8&#xA;400&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use the challenge binary to open the example cIMG file:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;user@learnaarch64asm:~$ ./challenge ./example.cimg &#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This file prints, but it does not give us the flag.&lt;/p&gt;&#xA;&lt;h2 id=&#34;static-analysis&#34;&gt;Static analysis&#xA;&lt;/h2&gt;&lt;p&gt;We need to make up a cIMG file that &lt;em&gt;gives the flag&lt;/em&gt;. Let’s do a static analysis first.&lt;/p&gt;&#xA;&lt;p&gt;Open the file in iaito. Perform an &lt;code&gt;aaa&lt;/code&gt; analysis and open the graph of the &lt;code&gt;main&lt;/code&gt; function. You will get:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_001_417726312315916099.png&#34;&#xA;&#x9;width=&#34;3532&#34;&#xA;&#x9;height=&#34;5056&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;the graph of the main function&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;69&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;167px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;We can find the file header read at &lt;code&gt;0x00000cac&lt;/code&gt;, the magic number check at &lt;code&gt;0x00000cc4&lt;/code&gt;, the version check at &lt;code&gt;0x00000cd0&lt;/code&gt;, and the width/height read at &lt;code&gt;0x00000ce0&lt;/code&gt;. Then it uses &lt;code&gt;malloc&lt;/code&gt; to allocate a piece of memory for the image data, and reads the data into that memory.&lt;/p&gt;&#xA;&lt;p&gt;The magic number is &lt;code&gt;cIMG&lt;/code&gt;; its bytes are &lt;code&gt;63 49 4d 47&lt;/code&gt; in hex. The version is a 32-bit little-endian integer; both the height and the width are 8-bit integers.&lt;/p&gt;&#xA;&lt;h2 id=&#34;flag-condition&#34;&gt;Flag condition&#xA;&lt;/h2&gt;&lt;p&gt;Let’s head to the end of the function. Obviously, &lt;code&gt;sym.win&lt;/code&gt; is the function that will give us the flag. It is called at &lt;code&gt;0x00000e8c&lt;/code&gt;, and this is executed after a check at &lt;code&gt;0x00000e58&lt;/code&gt;. This check requires &lt;code&gt;w19&lt;/code&gt; not to be zero.&lt;/p&gt;&#xA;&lt;h2 id=&#34;tampering-with-the-control-flow&#34;&gt;Tampering with the control flow&#xA;&lt;/h2&gt;&lt;p&gt;It is time to fire up GDB.&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ gdb --args ./challenge ./example.cimg &#xA;GNU gdb (Debian 16.3-1) 16.3&#xA;Copyright (C) 2024 Free Software Foundation, Inc.&#xA;License GPLv3+ or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;&#xA;This is free software: you are free to change and redistribute it.&#xA;There is NO WARRANTY, to the extent permitted by law.&#xA;Type &amp;#34;show copying&amp;#34; and &amp;#34;show warranty&amp;#34; for details.&#xA;This GDB was configured as &amp;#34;aarch64-linux-gnu&amp;#34;.&#xA;Type &amp;#34;show configuration&amp;#34; for configuration details.&#xA;For bug reporting instructions, please see:&#xA;&amp;lt;https://www.gnu.org/software/gdb/bugs/&amp;gt;.&#xA;Find the GDB manual and other documentation resources online at:&#xA;    &amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.&#xA;&#xA;For help, type &amp;#34;help&amp;#34;.&#xA;Type &amp;#34;apropos word&amp;#34; to search for commands related to &amp;#34;word&amp;#34;...&#xA;Reading symbols from ./challenge...&#xA;(No debugging symbols found in ./challenge)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Break at the main function and start the programme:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *main&#xA;Breakpoint 1 at 0xc44&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db enabled]&#xA;Using host libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1&amp;#34;.&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We note that GDB’s data addresses have an offset of &lt;code&gt;0xaaaaaaaa0000&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Break just before the flag-condition check and continue:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0x0000aaaaaaaa0e58&#xA;Breakpoint 2 at 0xaaaaaaaa0e58&#xA;(gdb) continue&#xA;Continuing.&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&#xA;Breakpoint 2, 0x0000aaaaaaaa0e58 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alter the content of the register &lt;code&gt;w19&lt;/code&gt; and continue:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) set $w19 = 1&#xA;(gdb) continue&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then the executable will give us the flag. Now the task is finished, and have a good day :-p&lt;/p&gt;&#xA;&lt;p&gt;Just kidding. If we really concluded the challenge this way, the joy of RE would be gone. Let’s dive deeper.&lt;/p&gt;&#xA;&lt;h2 id=&#34;data-check&#34;&gt;Data check&#xA;&lt;/h2&gt;&lt;p&gt;Go back to iaito. Click &lt;code&gt;w19&lt;/code&gt; in the graph. This will highlight that register in all instructions in the graph. Scroll the graph backwards to find a clue.&lt;/p&gt;&#xA;&lt;p&gt;We can see some calls to &lt;code&gt;sym.imp.memcmp&lt;/code&gt; in the graph.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;memcmp&lt;/code&gt; is a standard C library function declared in &lt;code&gt;&amp;lt;string.h&amp;gt;&lt;/code&gt; that compares two blocks of memory byte by byte:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;int memcmp(const void *ptr1, const void *ptr2, size_t n);&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;ptr1&lt;/code&gt; and &lt;code&gt;ptr2&lt;/code&gt; point to the two blocks respectively, and &lt;code&gt;n&lt;/code&gt; specifies the byte count to compare. If the two blocks are identical, the function returns &lt;code&gt;0&lt;/code&gt;; otherwise, it returns a non-zero number.&lt;/p&gt;&#xA;&lt;p&gt;There are four &lt;code&gt;memcmp&lt;/code&gt; calls. Each call is followed by &lt;code&gt;cmp w0, 0&lt;/code&gt;. Except for the first call, there is also a &lt;code&gt;cset w0, eq&lt;/code&gt; and an &lt;code&gt;and w19, w19, w0&lt;/code&gt; after &lt;code&gt;cmp&lt;/code&gt;. We can conclude that if &lt;code&gt;memcmp&lt;/code&gt; finds a difference, &lt;code&gt;w19&lt;/code&gt; will be cleared, since &lt;code&gt;w0&lt;/code&gt; will be zero. Therefore, we guess that we need to make the four pairs of memory blocks the same.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;x0&lt;/code&gt; and &lt;code&gt;x1&lt;/code&gt; are the two addresses to compare. &lt;code&gt;x2&lt;/code&gt; contains the size; &lt;code&gt;mov x2, 0x18&lt;/code&gt; tells us the size is 24 bytes.&lt;/p&gt;&#xA;&lt;p&gt;Open GDB again and inspect the memory before the first &lt;code&gt;memcmp&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0x0000aaaaaaaa0e78&#xA;Breakpoint 1 at 0xaaaaaaaa0e78&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;PPPPPPPPPP&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0e78 in main ()&#xA;(gdb) info registers x0 x1&#xA;x0             0xaaaaaaac12a0      187649984565920&#xA;x1             0xaaaaaaac00c0      187649984561344&#xA;(gdb) x/24xb $x0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;(gdb) x/24xb $x1&#xA;0xaaaaaaac00c0 &amp;lt;desired_output&amp;gt;:        0x1b    0x5b    0x33    0x38    0x3b   0x32     0x3b    0x32&#xA;0xaaaaaaac00c8 &amp;lt;desired_output+8&amp;gt;:      0x33    0x31    0x3b    0x30    0x31   0x37     0x3b    0x31&#xA;0xaaaaaaac00d0 &amp;lt;desired_output+16&amp;gt;:     0x33    0x30    0x6d    0x63    0x1b   0x5b     0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Random bytes. I cannot figure out what they mean.&lt;/p&gt;&#xA;&lt;p&gt;Go back to iaito. The first piece of memory is loaded from &lt;code&gt;x22&lt;/code&gt;, and &lt;code&gt;x22&lt;/code&gt; is loaded from the address &lt;code&gt;var_48h&lt;/code&gt;. &lt;code&gt;afv&lt;/code&gt; tells us it is &lt;code&gt;0x48&lt;/code&gt; from &lt;code&gt;sp&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[0x00000e70]&amp;gt; afv&#xA;arg signed int argc @ x0&#xA;arg char ** s @ x1&#xA;var int64_t var_50h @ sp+0x0&#xA;var int64_t var_50h_2 @ sp+0x8&#xA;var int64_t var_10h @ sp+0x10&#xA;var int64_t var_10h_2 @ sp+0x18&#xA;var int64_t var_20h @ sp+0x20&#xA;var int64_t var_20h_2 @ sp+0x28&#xA;var void * buf @ sp+0x38&#xA;var int64_t var_3ch @ sp+0x3c&#xA;var int64_t var_3eh @ sp+0x3e&#xA;var int64_t var_3fh @ sp+0x3f&#xA;var int64_t var_40h @ sp+0x40&#xA;var int64_t var_48h @ sp+0x48&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restart GDB and skip the function prologue after &lt;code&gt;main&lt;/code&gt; is invoked:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *main&#xA;Breakpoint 1 at 0xaaaaaaaa0c44&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa0c44 in main ()&#xA;(gdb) si&#xA;0x0000aaaaaaaa0c48 in main ()&#xA;(gdb) si&#xA;0x0000aaaaaaaa0c4c in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check the value of the stack pointer and set a watchpoint:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers sp&#xA;sp             0xfffffffff290      0xfffffffff290&#xA;(gdb) watch *(long long *)0xfffffffff2d8&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Continue:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&#xA;Old value = 281474840286680&#xA;New value = 0&#xA;0x0000aaaaaaaa0c5c in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Locate that address in iaito. We can see that at &lt;code&gt;0x00000c58&lt;/code&gt; (the instruction before &lt;code&gt;0x00000c5c&lt;/code&gt;), &lt;code&gt;var_48h&lt;/code&gt; is cleared by a &lt;code&gt;str&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Proceed further to find out where the variable is set:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(long long *)0xfffffffff2d8&#xA;&#xA;Old value = 0&#xA;New value = 187649984565920&#xA;0x0000aaaaaaaa1358 in initialize_framebuffer ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can see a new function we have not yet explored: &lt;code&gt;initialize_framebuffer&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In iaito, go to that function:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_002_13426986448128896865.png&#34;&#xA;&#x9;width=&#34;1775&#34;&#xA;&#x9;height=&#34;1384&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;the graph of the initialize\_framebuffer function&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;128&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;307px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;We can see that a memory address allocated by &lt;code&gt;malloc&lt;/code&gt; is stored in the variable by the instruction at &lt;code&gt;0x00001354&lt;/code&gt;. However, it is represented as &lt;code&gt;[x22, 0x10]&lt;/code&gt;. Let’s check the content of &lt;code&gt;x22&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers $x22&#xA;x22            0xfffffffff2c8      281474976707272&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;0xfffffffff2c8 + 0x10 = 0xfffffffff2d8&lt;/code&gt;. Exactly the address of &lt;code&gt;var_48h&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Additionally, the size passed to &lt;code&gt;malloc&lt;/code&gt; is interesting. It is computed by multiplying the values at offsets 6 and 7 together, then multiplying the result by &lt;code&gt;w1&lt;/code&gt;, which is &lt;code&gt;0x18&lt;/code&gt; (24), and then adding &lt;code&gt;x0&lt;/code&gt;, which is 1.&lt;/p&gt;&#xA;&lt;p&gt;Let’s inspect the values at those offsets as well:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/1xb 0xfffffffff2ce&#xA;0xfffffffff2ce: 0x0a&#xA;(gdb) x/1xb 0xfffffffff2cf&#xA;0xfffffffff2cf: 0x0a&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both are 10. They seem to be the image width and height we specified. Let’s check by setting hardware watchpoints. Since AArch64 requires alignment, let’s set the hardware watchpoint at &lt;code&gt;0xfffffffff2cc&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) watch *(unsigned int *)0xfffffffff2cc&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Skip the GNU C Library triggers:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 0&#xA;New value = 65535&#xA;0x0000fffff7fccd08 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 65535&#xA;New value = 0&#xA;0x0000fffff7fd5668 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;[Thread debugging using libthread_db enabled]&#xA;Using host libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1&amp;#34;.&#xA;&#xA;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 0&#xA;New value = 65535&#xA;0x0000fffff7fcc3a4 in ?? () from /lib/ld-linux-aarch64.so.1&#xA;(gdb) continue&#xA;Continuing.&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here we see that the memory is cleared at &lt;code&gt;0x00000c54&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hardware watchpoint 1: *(unsigned int *)0xfffffffff2cc&#xA;&#xA;Old value = 65535&#xA;New value = 0&#xA;0x0000aaaaaaaa0c58 in main ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We see a &lt;code&gt;stp xzr, xzr, [buf]&lt;/code&gt;. From the &lt;code&gt;afv&lt;/code&gt; above, we know &lt;code&gt;buf&lt;/code&gt; is &lt;code&gt;0x38&lt;/code&gt; from the stack pointer.&lt;/p&gt;&#xA;&lt;p&gt;At &lt;code&gt;0x00000cb8&lt;/code&gt; we can find a &lt;code&gt;ldr&lt;/code&gt; from &lt;code&gt;buf&lt;/code&gt;, and then a magic number check.&lt;/p&gt;&#xA;&lt;p&gt;At &lt;code&gt;0x00000c9c&lt;/code&gt; we can find something interesting:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0x00000c9c      add      x21,    sp,     0x38&#xA;0x00000ca0      mov      x2,     8                          ; size_t nbyte&#xA;0x00000ca4      mov      x1,     x21                        ; void *buf&#xA;0x00000ca8      mov      w0,     0&#xA;0x00000cac      bl       sym.imp.read                       ; ssize_t read(int fildes, void *buf, size_t nbyte)&#xA;; ssize_t read(0, 0x0000000000000000, 0x00000000)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Therefore, we have confirmed that the two values at those offsets are the width and the height.&lt;/p&gt;&#xA;&lt;p&gt;Go back to the &lt;code&gt;initialize_framebuffer&lt;/code&gt; function in both iaito and GDB. Set a watchpoint and inspect the memory:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1354&#xA;Breakpoint 1 at 0xaaaaaaaa1354&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1354 in initialize_framebuffer ()&#xA;(gdb) info registers x0&#xA;x0             0xaaaaaaac12a0      187649984565920&#xA;(gdb) watch *(char *)0xaaaaaaac12a0&#xA;Hardware watchpoint 2: *(char *)0xaaaaaaac12a0&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 2: *(char *)0xaaaaaaac12a0&#xA;&#xA;Old value = 0 &amp;#39;\000&amp;#39;&#xA;New value = 27 &amp;#39;\033&amp;#39;&#xA;0x0000aaaaaaaa13bc in initialize_framebuffer ()&#xA;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x32&#xA;0xaaaaaaac12a8: 0x35    0x35    0x3b    0x32    0x35    0x35    0x3b    0x32&#xA;0xaaaaaaac12b0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Compare that with the data above:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb $x0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;0xaaaaaaac12a7&lt;/code&gt; is the first byte that differs. We can set up a watchpoint:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) watch *(char *)0xaaaaaaac12a7&#xA;Hardware watchpoint 3: *(char *)0xaaaaaaac12a7&#xA;(gdb) continue&#xA;Continuing.&#xA;&#xA;Hardware watchpoint 3: *(char *)0xaaaaaaac12a7&#xA;&#xA;Old value = 50 &amp;#39;2&amp;#39;&#xA;New value = 48 &amp;#39;0&amp;#39;&#xA;0x0000aaaaaaaa128c in display ()&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A new function to explore. Open it in iaito.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://rebel1725.codeberg.page/blog/pic_20260527_003_2825852626928846418.png&#34;&#xA;&#x9;width=&#34;1129&#34;&#xA;&#x9;height=&#34;2426&#34;&#xA;&#x9;loading=&#34;lazy&#34;&#xA;&#x9;&#xA;&#x9;&#x9;alt=&#34;the graph of the initialize\_framebuffer function&#34;&#xA;&#x9;&#xA;&#x9;&#xA;&#x9;&#x9;class=&#34;gallery-image&#34; &#xA;&#x9;&#x9;data-flex-grow=&#34;46&#34;&#xA;&#x9;&#x9;data-flex-basis=&#34;111px&#34;&#xA;&#x9;&#xA;&gt;&lt;/p&gt;&#xA;&lt;p&gt;We can see the data change taking place at &lt;code&gt;0x00001288&lt;/code&gt;, in a &lt;code&gt;stp&lt;/code&gt; instruction. The &lt;code&gt;str&lt;/code&gt; instruction at &lt;code&gt;0x0000128c&lt;/code&gt; is interesting as well; let’s inspect the memory here:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x35    0x35    0x6d    0x20    0x1b    0x5b    0x30    0x6d&#xA;(gdb) si&#xA;0x0000aaaaaaaa1290 in display ()&#xA;(gdb) x/24xb 0xaaaaaaac12a0&#xA;0xaaaaaaac12a0: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xaaaaaaac12a8: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xaaaaaaac12b0: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The 8-byte block from &lt;code&gt;0xaaaaaaac12b0&lt;/code&gt; has been changed by the &lt;code&gt;str&lt;/code&gt; instruction. The register tells the story:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers x2&#xA;x2             0xaaaaaaac12a0      187649984565920&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We do not need to care about &lt;code&gt;x2&lt;/code&gt; too much. Let’s focus on &lt;code&gt;x4&lt;/code&gt;, &lt;code&gt;x5&lt;/code&gt;, and &lt;code&gt;x1&lt;/code&gt;. At &lt;code&gt;0x00001268&lt;/code&gt; and &lt;code&gt;0x00001270&lt;/code&gt; we see a very important register, &lt;code&gt;x25&lt;/code&gt;. It contains the address of the memory holding the data written to the memory located by &lt;code&gt;x2&lt;/code&gt;. Inspect that memory:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) x/24xb $x25&#xA;0xfffffffff270: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x30&#xA;0xfffffffff278: 0x38    0x30    0x3b    0x30    0x38    0x30    0x3b    0x30&#xA;0xfffffffff280: 0x38    0x30    0x6d    0x50    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At &lt;code&gt;0x00001238&lt;/code&gt;, the address in &lt;code&gt;x25&lt;/code&gt; is copied to &lt;code&gt;x0&lt;/code&gt;. Then pieces of data are copied to the following registers. Then there is a call to &lt;code&gt;snprintf&lt;/code&gt;. &lt;code&gt;snprintf&lt;/code&gt; writes formatted output to a character array:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;int snprintf(char *str, size_t size, const char *format, ...);&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is writing a formatted string to the memory at &lt;code&gt;x25&lt;/code&gt;. Let’s restart GDB and inspect the memory pointed to by &lt;code&gt;x2&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1258&#xA;Breakpoint 1 at 0xaaaaaaaa1258&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1258 in display ()&#xA;(gdb) x/s $x2&#xA;0xaaaaaaaa1510: &amp;#34;\033[38;2;%03d;%03d;%03dm%c\033[0m&amp;#34;&#xA;(gdb) x/29xb $x2&#xA;0xaaaaaaaa1510: 0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    0x25&#xA;0xaaaaaaaa1518: 0x30    0x33    0x64    0x3b    0x25    0x30    0x33    0x64&#xA;0xaaaaaaaa1520: 0x3b    0x25    0x30    0x33    0x64    0x6d    0x25    0x63&#xA;0xaaaaaaaa1528: 0x1b    0x5b    0x30    0x6d    0x00&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the string format. If you are not comfortable with the string returned by &lt;code&gt;x/s&lt;/code&gt;, you can use Python to print the string byte by byte:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(b&amp;#34;\x1b\x5b\x33\x38\x3b\x32\x3b\x25\x30\x33\x64\x3b\x25\x30\x33\x64\x3\&#xA;b\x25\x30\x33\x64\x6d\x25\x63\x1b\x5b\x30\x6d\x00&amp;#34;)&#xA;b&amp;#39;\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00&amp;#39;&#xA;&amp;gt;&amp;gt;&amp;gt; blob = b&amp;#39;\x1b[38;2;%03d;%03d;%03dm%c\x1b[0m\x00&amp;#39;&#xA;&amp;gt;&amp;gt;&amp;gt; for i1 in range(len(blob)):&#xA;...     blob[i1:i1+1]&#xA;...     &#xA;b&amp;#39;\x1b&amp;#39;&#xA;b&amp;#39;[&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;8&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;2&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;;&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;3&amp;#39;&#xA;b&amp;#39;d&amp;#39;&#xA;b&amp;#39;m&amp;#39;&#xA;b&amp;#39;%&amp;#39;&#xA;b&amp;#39;c&amp;#39;&#xA;b&amp;#39;\x1b&amp;#39;&#xA;b&amp;#39;[&amp;#39;&#xA;b&amp;#39;0&amp;#39;&#xA;b&amp;#39;m&amp;#39;&#xA;b&amp;#39;\x00&amp;#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This format string has four placeholders. The first three are &lt;code&gt;%03d&lt;/code&gt;, which formats an integer as a decimal string containing at least three digits (for example, 7 becomes &lt;code&gt;007&lt;/code&gt;, and 15 becomes &lt;code&gt;015&lt;/code&gt;). The last one is &lt;code&gt;%c&lt;/code&gt;, which matches just one character.&lt;/p&gt;&#xA;&lt;p&gt;Now we have the byte pattern:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0x1b    0x5b    0x33    0x38    0x3b    0x32    0x3b    [N1]&#xA;[N1]    [N1]    0x3b    [N2]    [N2]    [N2]    0x3b    [N3]&#xA;[N3]    [N3]    0x6d    [CH]    0x1b    0x5b    0x30    0x6d&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can now inspect what fills the placeholders:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) info registers x3 x4 x5 x6&#xA;x3             0x50                80&#xA;x4             0x50                80&#xA;x5             0x50                80&#xA;x6             0x50                80&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;All &lt;code&gt;0x50&lt;/code&gt;, which seems to be the data we use in the example cIMG. Now let’s recreate the cIMG to make that clearer:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; with open(&amp;#34;example.cimg&amp;#34;, &amp;#34;wb&amp;#34;) as file:&#xA;...     file.write(b&amp;#34;cIMG\x02\0\x0a\x0a\x12\x34\x56\x78&amp;#34;)&#xA;...     file.write(b&amp;#34;\x50&amp;#34;*(10*10*4-4))&#xA;...     &#xA;12&#xA;396&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(gdb) break *0xaaaaaaaa1258&#xA;Breakpoint 1 at 0xaaaaaaaa1258&#xA;(gdb) run&#xA;Starting program: /home/user/challenge ./example.cimg&#xA;[Thread debugging using libthread_db library &amp;#34;/lib/aarch64-linux-gnu/libthread_db.so.1].&#xA;&#xA;Breakpoint 1, 0x0000aaaaaaaa1258 in display ()&#xA;(gdb) info registers x3 x4 x5 x6&#xA;x3             0x12                18&#xA;x4             0x34                52&#xA;x5             0x56                86&#xA;x6             0x78                120&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Thus, the N1, N2, and N3 in the hex pattern above correspond to R, G, and B respectively, and CH is just the character we are trying to fill in.&lt;/p&gt;&#xA;&lt;p&gt;Now we can go back to GDB, break before each &lt;code&gt;memcmp&lt;/code&gt;, run &lt;code&gt;x/24xb $x1&lt;/code&gt; to find out the expected framebuffers, and use those framebuffers to recover the expected cIMG data. Then fill in the data using a hex editor.&lt;/p&gt;&#xA;&lt;h2 id=&#34;size-check&#34;&gt;Size check&#xA;&lt;/h2&gt;&lt;p&gt;However, after filling in the expected data, the executable still does not give us the flag. After using GDB to inspect the memory, we can see that the &lt;code&gt;memcmp&lt;/code&gt;s are returning zeros as expected. Therefore, there must be other conditions that are not fulfilled.&lt;/p&gt;&#xA;&lt;p&gt;Go back to iaito and scroll backwards from the first &lt;code&gt;memcmp&lt;/code&gt;. We can see a &lt;code&gt;cmp&lt;/code&gt; at &lt;code&gt;0x00000d60&lt;/code&gt; that compares &lt;code&gt;var_40h&lt;/code&gt; with 4. At &lt;code&gt;0x00001348&lt;/code&gt; in &lt;code&gt;initialize_framebuffer&lt;/code&gt; we can find a &lt;code&gt;str&lt;/code&gt; that stores the product of the width and the height.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;cmp&lt;/code&gt; at &lt;code&gt;0x00000d60&lt;/code&gt; then decides the value of &lt;code&gt;w0&lt;/code&gt;. If &lt;code&gt;var_40h&lt;/code&gt; equals 4, &lt;code&gt;w0&lt;/code&gt; will become 1; otherwise, it will become 0.&lt;/p&gt;&#xA;&lt;p&gt;At &lt;code&gt;0x00000d78&lt;/code&gt; &lt;code&gt;w0&lt;/code&gt; is compared with 0, which affects the behaviour of the &lt;code&gt;ccmp&lt;/code&gt; instruction at &lt;code&gt;0x00000d84&lt;/code&gt;. The content of &lt;code&gt;[x22, 0x13]&lt;/code&gt; is loaded into the &lt;code&gt;w0&lt;/code&gt; register, and &lt;code&gt;[x1, 0x13]&lt;/code&gt; is loaded into &lt;code&gt;w2&lt;/code&gt;. According to the &lt;code&gt;memcmp&lt;/code&gt;, we can easily tell that &lt;code&gt;x22&lt;/code&gt; is the actual framebuffer address, and &lt;code&gt;x1&lt;/code&gt; is the expected framebuffer address. Given that we have already made the data the same as expected, and offset 19 is an ASCII character, &lt;code&gt;w0&lt;/code&gt; and &lt;code&gt;w2&lt;/code&gt; should be the same and should be non-zero.&lt;/p&gt;&#xA;&lt;p&gt;We can conclude that if &lt;code&gt;var_40h&lt;/code&gt; equals 4, &lt;code&gt;w2&lt;/code&gt; will be compared against &lt;code&gt;w0&lt;/code&gt;, which will set &lt;code&gt;w19&lt;/code&gt; to 1. Otherwise, &lt;code&gt;w2&lt;/code&gt; will be compared against 0, rendering &lt;code&gt;w19&lt;/code&gt; zero.&lt;/p&gt;&#xA;&lt;p&gt;After the first &lt;code&gt;memcmp&lt;/code&gt;, &lt;code&gt;w19&lt;/code&gt; will be compared against 0 since &lt;code&gt;w0&lt;/code&gt; equals zero at &lt;code&gt;0x00000e7c&lt;/code&gt;. Then &lt;code&gt;cset&lt;/code&gt; will set &lt;code&gt;w19&lt;/code&gt; to 1 if not equal, otherwise to 0.&lt;/p&gt;&#xA;&lt;p&gt;Now alter the cIMG with a hex editor, changing the height and the width to values that multiply to 4. For example, we can set &lt;code&gt;0202&lt;/code&gt;. Now the executable should give us the flag.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&#xA;&lt;/h2&gt;&lt;p&gt;The procedure above involves:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;static analysis with radare2 and iaito;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;dynamic analysis with GDB involving:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;control-flow tampering;&lt;/li&gt;&#xA;&lt;li&gt;memory inspection;&lt;/li&gt;&#xA;&lt;li&gt;integer inspection.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This is a very typical procedure in reverse engineering.&lt;/p&gt;&#xA;&lt;p&gt;By the way, there may be other approaches to reverse this executable. However, no matter what approach is used, it usually involves both static analysis and dynamic analysis.&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
