Previously in this series of articles, we ran and stepped through the number printing function in our case study. In this article, we will modify our case study program so that it outputs the ASCII table with octal numbers instead of hexadecimal.

What we’ve learned so far

As a reminder, we have written and compiled the source code of the print_number() in part 2 into an executable, then disassembled this function from the executable in part 3:

void print_number(int num) {
    for (int n = 3; n >= 0; n--) {
        int digit = (num >> (4 * n)) % 16;

        if (digit < 10)
            putchar('0' + digit);
        else
            putchar('a' + digit - 10);
    }
}
004001d0 <print_number>:
  4001d0:       27bdffd0        addiu   sp,sp,-48
  4001d4:       afb30028        sw      s3,40(sp)
  4001d8:       2413000c        li      s3,12
  4001dc:       afb20024        sw      s2,36(sp)
  4001e0:       2412fffc        li      s2,-4
  4001e4:       afb10020        sw      s1,32(sp)
  4001e8:       24110010        li      s1,16
  4001ec:       afb0001c        sw      s0,28(sp)
  4001f0:       00808025        move    s0,a0
  4001f4:       afbf002c        sw      ra,44(sp)
  4001f8:       02701007        srav    v0,s0,s3
  4001fc:       0051001a        div     zero,v0,s1
  400200:       00001010        mfhi    v0
  400204:       304300ff        andi    v1,v0,0xff
  400208:       2842000a        slti    v0,v0,10
  40020c:       10400011        beqz    v0,400254 <print_number+0x84>
  400210:       00000000        nop
  400214:       24630030        addiu   v1,v1,48
  400218:       24060001        li      a2,1
  40021c:       a3a30010        sb      v1,16(sp)
  400220:       27a50010        addiu   a1,sp,16
  400224:       24040001        li      a0,1
  400228:       0c100154        jal     400550 <write>
  40022c:       2673fffc        addiu   s3,s3,-4
  400230:       1672fff2        bne     s3,s2,4001fc <print_number+0x2c>
  400234:       02701007        srav    v0,s0,s3
  400238:       8fbf002c        lw      ra,44(sp)
  40023c:       8fb30028        lw      s3,40(sp)
  400240:       8fb20024        lw      s2,36(sp)
  400244:       8fb10020        lw      s1,32(sp)
  400248:       8fb0001c        lw      s0,28(sp)
  40024c:       03e00008        jr      ra
  400250:       27bd0030        addiu   sp,sp,48
  400254:       1000fff0        b       400218 <print_number+0x48>
  400258:       24630057        addiu   v1,v1,87

Finally, we’ve determined in part 4 through run-time examination the purposes of the following CPU registers used in that function:

Register Purpose Constant?
v0 Set to one if the current digit is less than 10 No
v1 ASCII character of the current digit No
s0 Number to print (num) Yes
s1 Value 16 (radix or base of hexadecimal) Yes
s2 Value -4 (added to counter after an iteration) Yes
s3 Iteration counter No

Modifying the executable

As stated in the introduction, we want to modify the print_number() function so that it prints numbers in octal instead of hexadecimal. We can achieve this by modifying the following four instructions in this function:

  4001d8:       2413000c        li      s3,12
  4001e0:       2412fffc        li      s2,-4
  4001e8:       24110010        li      s1,16
  40022c:       2673fffc        addiu   s3,s3,-4

The encoding of these MIPS I-format instructions have their constants in the bottom two bytes, as a signed (two’s complement) 16-bit integer. Adjusting the constants to output octal numbers results in these four alternative instructions:

  4001d8:       24130009        li      s3,9
  4001e0:       2412fffd        li      s2,-3
  4001e8:       24110008        li      s1,8
  40022c:       2673fffd        addiu   s3,s3,-3

Note that this is a small modification of the function that merely changes some constants inside the existing program flow. More invasive modifications are possible by changing the actual instructions within the function, or even by redirecting the execution somewhere else if the patch doesn’t fit within the original space.

Patching the executable

We will be using GDB to patch our executable. First, let’s make a copy of it and invoke GDB in a manner that will let us patch the file:

$ cp ascii-table.elf ascii-table.patched.elf
$ gdb-multiarch
GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) set architecture mips:isa32r2
The target architecture is set to "mips:isa32r2".
(gdb) set write on
(gdb) exec-file ascii-table.patched.elf
(gdb) 

We will patch the first instruction, checking its disassembly before and after the modification:

(gdb) x/i 0x4001d8
   0x4001d8:    li      s3,12
(gdb) set {unsigned int}0x4001d8 = 0x24130009
(gdb) x/i 0x4001d8
   0x4001d8:    li      s3,9
(gdb) 

Let’s fast forward the rest of the patch:

(gdb) set {unsigned int}0x4001e0 = 0x2412fffd
(gdb) set {unsigned int}0x4001e8 = 0x24110008
(gdb) set {unsigned int}0x40022c = 0x2673fffd
(gdb) quit

The executable file has been binary patched. We could’ve achieved the same result with a hex editor, if we computed by hand the file offset within the ELF executable to modify based on the patch’s virtual address like we did in part 3. GDB performed this operation automatically when we instructed it to modify memory within the executable’s virtual address space.

Results

After modifying the executable artifact, executing it yields the following output:

$ qemu-mipsel ascii-table.elf
0000     c              0020    p s             0040 @ gp  !            0060 ` gp  !     
0001     c              0021 ! gp  !            0041 A gp   Aa U        0061 a gp   Aa  l
0002     c              0022 " gp  !            0042 B gp   Aa U        0062 b gp   Aa  l
0003     c              0023 # gp  !            0043 C gp   Aa U        0063 c gp   Aa  l
0004     c              0024 $ gp  !            0044 D gp   Aa U        0064 d gp   Aa  l
0005     c              0025 % gp  !            0045 E gp   Aa U        0065 e gp   Aa  l
0006     c              0026 & gp  !            0046 F gp   Aa U        0066 f gp   Aa  l
0007     c              0027 ' gp  !            0047 G gp   Aa U        0067 g gp   Aa  l
0008     c              0028 ( gp  !            0048 H gp   Aa U        0068 h gp   Aa  l
0009     cs             0029 ) gp  !            0049 I gp   Aa U        0069 i gp   Aa  l
000a     cs             002a * gp  !            004a J gp   Aa U        006a j gp   Aa  l
000b     cs             002b + gp  !            004b K gp   Aa U        006b k gp   Aa  l
000c     cs             002c , gp  !            004c L gp   Aa U        006c l gp   Aa  l
000d     cs             002d - gp  !            004d M gp   Aa U        006d m gp   Aa  l
000e     c              002e . gp  !            004e N gp   Aa U        006e n gp   Aa  l
000f     c              002f / gp  !            004f O gp   Aa U        006f o gp   Aa  l
0010     c              0030 0 gp   A d         0050 P gp   Aa U        0070 p gp   Aa  l
0011     c              0031 1 gp   A d         0051 Q gp   Aa U        0071 q gp   Aa  l
0012     c              0032 2 gp   A d         0052 R gp   Aa U        0072 r gp   Aa  l
0013     c              0033 3 gp   A d         0053 S gp   Aa U        0073 s gp   Aa  l
0014     c              0034 4 gp   A d         0054 T gp   Aa U        0074 t gp   Aa  l
0015     c              0035 5 gp   A d         0055 U gp   Aa U        0075 u gp   Aa  l
0016     c              0036 6 gp   A d         0056 V gp   Aa U        0076 v gp   Aa  l
0017     c              0037 7 gp   A d         0057 W gp   Aa U        0077 w gp   Aa  l
0018     c              0038 8 gp   A d         0058 X gp   Aa U        0078 x gp   Aa  l
0019     c              0039 9 gp   A d         0059 Y gp   Aa U        0079 y gp   Aa  l
001a     c              003a : gp  !            005a Z gp   Aa U        007a z gp   Aa  l
001b     c              003b ; gp  !            005b [ gp  !            007b { gp  !     
001c     c              003c < gp  !            005c \ gp  !            007c | gp  !     
001d     c              003d = gp  !            005d ] gp  !            007d } gp  !     
001e     c              003e > gp  !            005e ^ gp  !            007e ~ gp  !     
001f     c              003f ? gp  !            005f _ gp  !            007f     c       

$ chmod +x ascii-table.patched.elf 
$ qemu-mipsel ascii-table.patched.elf 
0000     c              0040    p s             0100 @ gp  !            0140 ` gp  !     
0001     c              0041 ! gp  !            0101 A gp   Aa U        0141 a gp   Aa  l
0002     c              0042 " gp  !            0102 B gp   Aa U        0142 b gp   Aa  l
0003     c              0043 # gp  !            0103 C gp   Aa U        0143 c gp   Aa  l
0004     c              0044 $ gp  !            0104 D gp   Aa U        0144 d gp   Aa  l
0005     c              0045 % gp  !            0105 E gp   Aa U        0145 e gp   Aa  l
0006     c              0046 & gp  !            0106 F gp   Aa U        0146 f gp   Aa  l
0007     c              0047 ' gp  !            0107 G gp   Aa U        0147 g gp   Aa  l
0010     c              0050 ( gp  !            0110 H gp   Aa U        0150 h gp   Aa  l
0011     cs             0051 ) gp  !            0111 I gp   Aa U        0151 i gp   Aa  l
0012     cs             0052 * gp  !            0112 J gp   Aa U        0152 j gp   Aa  l
0013     cs             0053 + gp  !            0113 K gp   Aa U        0153 k gp   Aa  l
0014     cs             0054 , gp  !            0114 L gp   Aa U        0154 l gp   Aa  l
0015     cs             0055 - gp  !            0115 M gp   Aa U        0155 m gp   Aa  l
0016     c              0056 . gp  !            0116 N gp   Aa U        0156 n gp   Aa  l
0017     c              0057 / gp  !            0117 O gp   Aa U        0157 o gp   Aa  l
0020     c              0060 0 gp   A d         0120 P gp   Aa U        0160 p gp   Aa  l
0021     c              0061 1 gp   A d         0121 Q gp   Aa U        0161 q gp   Aa  l
0022     c              0062 2 gp   A d         0122 R gp   Aa U        0162 r gp   Aa  l
0023     c              0063 3 gp   A d         0123 S gp   Aa U        0163 s gp   Aa  l
0024     c              0064 4 gp   A d         0124 T gp   Aa U        0164 t gp   Aa  l
0025     c              0065 5 gp   A d         0125 U gp   Aa U        0165 u gp   Aa  l
0026     c              0066 6 gp   A d         0126 V gp   Aa U        0166 v gp   Aa  l
0027     c              0067 7 gp   A d         0127 W gp   Aa U        0167 w gp   Aa  l
0030     c              0070 8 gp   A d         0130 X gp   Aa U        0170 x gp   Aa  l
0031     c              0071 9 gp   A d         0131 Y gp   Aa U        0171 y gp   Aa  l
0032     c              0072 : gp  !            0132 Z gp   Aa U        0172 z gp   Aa  l
0033     c              0073 ; gp  !            0133 [ gp  !            0173 { gp  !     
0034     c              0074 < gp  !            0134 \ gp  !            0174 | gp  !     
0035     c              0075 = gp  !            0135 ] gp  !            0175 } gp  !     
0036     c              0076 > gp  !            0136 ^ gp  !            0176 ~ gp  !     
0037     c              0077 ? gp  !            0137 _ gp  !            0177     c       

We can observe that the number columns are now in octal, instead of hexadecimal as previously.

The files for this case study can be found here: case-study.tar.gz

Conclusion

We have successfully modified the executable file from our case study to alter its behavior through the process of binary patching, without recompiling or relinking it, by leveraging the toolchain to achieve our objective. Next time, we will use a dedicated reverse-engineering program named Ghidra on the executable file, stripped of its debugging symbols, to perform the same operation.