{"id":27069154,"url":"https://github.com/lucianoscarpaci/ctf-challenge-debugger-flag","last_synced_at":"2025-09-07T21:41:23.055Z","repository":{"id":286343062,"uuid":"927259165","full_name":"lucianoscarpaci/CTF-challenge-Debugger-Flag","owner":"lucianoscarpaci","description":"This project involves capturing a debugger flag by doing various exercises about the basics of debugging assembly code using OpenOCD and GDB server.","archived":false,"fork":false,"pushed_at":"2025-02-05T20:25:35.000Z","size":7,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T21:20:28.631Z","etag":null,"topics":["arm-assembly","capture-the-flag","cybersecurity","dissassembly","ectf","embedded-systems","gdb","max78000","nix-shell","openocd","patching","secure-boot"],"latest_commit_sha":null,"homepage":"","language":"Assembly","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucianoscarpaci.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-04T17:04:48.000Z","updated_at":"2025-03-11T17:00:30.000Z","dependencies_parsed_at":"2025-04-05T21:31:03.840Z","dependency_job_id":null,"html_url":"https://github.com/lucianoscarpaci/CTF-challenge-Debugger-Flag","commit_stats":null,"previous_names":["lucianoscarpaci/ctf-challenge-debugger-flag","lucianoscarpaci/ctf-challenge-hack-the-flag"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lucianoscarpaci/CTF-challenge-Debugger-Flag","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianoscarpaci%2FCTF-challenge-Debugger-Flag","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianoscarpaci%2FCTF-challenge-Debugger-Flag/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianoscarpaci%2FCTF-challenge-Debugger-Flag/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianoscarpaci%2FCTF-challenge-Debugger-Flag/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucianoscarpaci","download_url":"https://codeload.github.com/lucianoscarpaci/CTF-challenge-Debugger-Flag/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucianoscarpaci%2FCTF-challenge-Debugger-Flag/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259935545,"owners_count":22934376,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arm-assembly","capture-the-flag","cybersecurity","dissassembly","ectf","embedded-systems","gdb","max78000","nix-shell","openocd","patching","secure-boot"],"created_at":"2025-04-05T21:19:26.574Z","updated_at":"2025-06-15T07:02:01.225Z","avatar_url":"https://github.com/lucianoscarpaci.png","language":"Assembly","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CTF-challenge-hack-the-flag\nThis project was an attempt to modify an ELF binary to bypass a function `check_flag` by patching ARM thumb assembly instructions.\n\n# Instructions for Getting the Debugger Flag\n\nThis tutorial will show you how to use the GDB debugger to walk through and\ninteract with a MAX78000fhtr board.\n\n## 1. Launch in Debug Mode with Docker and OpenOCD\nUsing a nix-shell start an openocd connection to get the debugger running:\n```bash\nnix-shell\nopenocd -s scripts/ -f interface/cmsis-dap.cfg -f target/max78000.cfg -c \"bindto 0.0.0.0; init\"\n```\nNow use docker to start the debugger in the decoder/ directory of the project and connect to the openocd server:\n```bash\ndocker run --rm -it -p 3333:3333/tcp -v $(pwd)/./build_out:/out --workdir=/root --entrypoint /bin/bash decoder -c \" cp -r /out/* /root/ \u0026\u0026 gdb-multiarch gdb_challenge_25.elf \"\n```\n\n## 2. Getting Your Bearings\nWhen the deployment finishes spinning up, you should see output along the lines of:\n```\nGNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git\nCopyright (C) 2024 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later \u003chttp://gnu.org/licenses/gpl.html\u003e\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\nType \"show copying\" and \"show warranty\" for details.\nThis GDB was configured as \"x86_64-linux-gnu\".\nType \"show configuration\" for configuration details.\nFor bug reporting instructions, please see:\n\u003chttps://www.gnu.org/software/gdb/bugs/\u003e.\nFind the GDB manual and other documentation resources online at:\n    \u003chttp://www.gnu.org/software/gdb/documentation/\u003e.\n\nFor help, type \"help\".\nType \"apropos word\" to search for commands related to \"word\"...\nReading symbols from gdb_challenge_25.elf...\n(gdb) target remote host.docker.internal:3333\nRemote debugging using host.docker.internal:3333\n0x1000ee6e in MXC_Delay (us=\u003coptimized out\u003e)\n    at /root/msdk-2024_02/Libraries/CMSIS/../PeriphDrivers/Source/SYS/mxc_delay.c:233\n233         while (SysTick-\u003eVAL \u003e endtick) {}\n```\n\nYou are now using GDB.\n\nLet's view the current register values with `info`:\n```\n(gdb) info registers\nr0             0x363c7f            3554431\nr1             0xe000e000          -536813568\nr2             0x363c7f            3554431\nr3             0x7b0d32            8064306\nr4             0x20000004          536870916\nr5             0x0                 0\nr6             0x0                 0\nr7             0x2001fff8          537001976\nr8             0x0                 0\nr9             0x0                 0\nr10            0x0                 0\nr11            0x0                 0\nr12            0xf4240000          -198967296\nsp             0x2001ffe8          0x2001ffe8\nlr             0x1000ed1d          268496157\npc             0x1000ee6e          0x1000ee6e \u003cMXC_Delay+46\u003e\nxPSR           0x21000000          553648128\nfpscr          0x0                 0\nmsp            0x2001ffe8          0x2001ffe8\npsp            0x0                 0x0\nprimask        0x0                 0\nbasepri        0x0                 0\nfaultmask      0x0                 0\ncontrol        0x0                 0\n```\n\nWe can also view values in memory with `x` specified by address or symbol:\n```\n(gdb) x gdb_challenge\n0x1000e5d0 \u003cgdb_challenge\u003e:     0xb084b580\n(gdb)  x to_hex\n0x1000e738 \u003cto_hex\u003e:    0x4c09b410\n\n```\nNow because the addresses are read-only there will be a signifcant problem\nlater when modifying registers, the designers did not want the program to be\nmodified. Instead, we will be modifying the memory at the address of the\ninstruction to change the program's behavior.\n\n**Write down the raw value of the instruction in memory at to_hex for later\nas value1**\n\n\nLet's try that with `do_some_math`:\n```\n(gdb) b do_some_math\nBreakpoint 2 at 0x1000e290: file src/debugger_challenge.c, line 39.\n(gdb) info registers sp\nsp             0x2001fff8          0x2001fff8\n(gdb) c\nContinuing.\n\nThread 2 \"max32xxx.cpu\" hit Breakpoint 2, do_some_math (a=0, b=0, c=268496157, d=537001936)\n    at src/debugger_challenge.c:39\n39      in src/debugger_challenge.c\n(gdb) info registers sp\nsp             0x2001ffe0          0x2001ffe0\n(gdb)\n```\n\n**Inspect the registers and write down the value in `sp` as value2**\n\n\n## 3. Stepping Through Code\nLet's skip ahead to a function called `do_some_math` (without skipping the\nfunction prologue):\n```\n(gdb) (gdb) p do_some_math\n$1 = {int (int, int, int, int)} 0x1000e290 \u003cdo_some_math\u003e \n(gdb) b *$1\nNote: breakpoint 2 also set at pc 0x1000e290.\nBreakpoint 3 at 0x1000e290: file src/debugger_challenge.c, line 39.\n(gdb) c\nContinuing.\n\nThread 2 \"max32xxx.cpu\" hit Breakpoint 1, gdb_challenge () at src/debugger_challenge.c:102\n102     in src/debugger_challenge.c\n```\n\nNow, let's inspect the disassembly of this function with `disass`:\n```\n(gdb) disass do_some_math\nDump of assembler code for function do_some_math:\n=\u003e 0x1000e290 \u003c+0\u003e:     push    {r7}\n   0x1000e292 \u003c+2\u003e:     sub     sp, #20\n   0x1000e294 \u003c+4\u003e:     add     r7, sp, #0\n   0x1000e296 \u003c+6\u003e:     str     r0, [r7, #12]\n   0x1000e298 \u003c+8\u003e:     str     r1, [r7, #8]\n   0x1000e29a \u003c+10\u003e:    str     r2, [r7, #4]\n   0x1000e29c \u003c+12\u003e:    str     r3, [r7, #0]\n   0x1000e29e \u003c+14\u003e:    ldr     r2, [r7, #12]\n   0x1000e2a0 \u003c+16\u003e:    ldr     r3, [r7, #8]\n   0x1000e2a2 \u003c+18\u003e:    add     r3, r2\n   0x1000e2a4 \u003c+20\u003e:    ldr     r1, [r7, #8]\n   0x1000e2a6 \u003c+22\u003e:    ldr     r2, [r7, #4]\n   0x1000e2a8 \u003c+24\u003e:    sdiv    r2, r1, r2\n   0x1000e2ac \u003c+28\u003e:    mul.w   r2, r3, r2\n   0x1000e2b0 \u003c+32\u003e:    ldr     r3, [r7, #0]\n   0x1000e2b2 \u003c+34\u003e:    ldr     r1, [r7, #12]\n   0x1000e2b4 \u003c+36\u003e:    sdiv    r1, r3, r1\n   0x1000e2b8 \u003c+40\u003e:    ldr     r0, [r7, #12]\n   0x1000e2ba \u003c+42\u003e:    mul.w   r1, r0, r1\n   0x1000e2be \u003c+46\u003e:    subs    r3, r3, r1\n   0x1000e2c0 \u003c+48\u003e:    mul.w   r3, r2, r3\n   0x1000e2c4 \u003c+52\u003e:    ldr     r1, [r7, #12]\n   0x1000e2c6 \u003c+54\u003e:    ldr     r2, [r7, #8]\n   0x1000e2c8 \u003c+56\u003e:    eors    r1, r2\n```\n\nInstead of using breakpoints, we can instead step through this function\ninstruction by instruction using `si` for step instruction:\n```\n(gdb) si\n0x1000e292      39      in src/debugger_challenge.c\n(gdb) si\n0x1000e294      39      in src/debugger_challenge.c\n(gdb) si\n0x1000e296      39      in src/debugger_challenge.c\n```\n\nYou can see that we are stepping through the instructions of the function.\nIf you run `disass` again, you will see our position has changed:\n```\n(gdb) disass\nDump of assembler code for function do_some_math:\n   0x1000e290 \u003c+0\u003e:     push    {r7}\n   0x1000e292 \u003c+2\u003e:     sub     sp, #20\n   0x1000e294 \u003c+4\u003e:     add     r7, sp, #0\n=\u003e 0x1000e296 \u003c+6\u003e:     str     r0, [r7, #12]\n   0x1000e298 \u003c+8\u003e:     str     r1, [r7, #8]\n   0x1000e29a \u003c+10\u003e:    str     r2, [r7, #4]\n   0x1000e29c \u003c+12\u003e:    str     r3, [r7, #0]\n   0x1000e29e \u003c+14\u003e:    ldr     r2, [r7, #12]\n   0x1000e2a0 \u003c+16\u003e:    ldr     r3, [r7, #8]\n   0x1000e2a2 \u003c+18\u003e:    add     r3, r2\n   0x1000e2a4 \u003c+20\u003e:    ldr     r1, [r7, #8]\n   0x1000e2a6 \u003c+22\u003e:    ldr     r2, [r7, #4]\n   0x1000e2a8 \u003c+24\u003e:    sdiv    r2, r1, r2\n   0x1000e2ac \u003c+28\u003e:    mul.w   r2, r3, r2\n   0x1000e2b0 \u003c+32\u003e:    ldr     r3, [r7, #0]\n   0x1000e2b2 \u003c+34\u003e:    ldr     r1, [r7, #12]\n   0x1000e2b4 \u003c+36\u003e:    sdiv    r1, r3, r1\n   0x1000e2b8 \u003c+40\u003e:    ldr     r0, [r7, #12]\n   0x1000e2ba \u003c+42\u003e:    mul.w   r1, r0, r1\n   0x1000e2be \u003c+46\u003e:    subs    r3, r3, r1\n   0x1000e2c0 \u003c+48\u003e:    mul.w   r3, r2, r3\n   0x1000e2c4 \u003c+52\u003e:    ldr     r1, [r7, #12]\n   0x1000e2c6 \u003c+54\u003e:    ldr     r2, [r7, #8]\n   0x1000e2c8 \u003c+56\u003e:    eors    r1, r2\n   0x1000e2ca \u003c+58\u003e:    ldr     r2, [r7, #0]\n   0x1000e2cc \u003c+60\u003e:    add     r2, r1\n   0x1000e2ce \u003c+62\u003e:    sdiv    r1, r3, r2\n   0x1000e2d2 \u003c+66\u003e:    mul.w   r2, r1, r2\n   0x1000e2d6 \u003c+70\u003e:    subs    r3, r3, r2\n   0x1000e2d8 \u003c+72\u003e:    mov     r0, r3\n   0x1000e2da \u003c+74\u003e:    adds    r7, #20\n   0x1000e2dc \u003c+76\u003e:    mov     sp, r7\n   0x1000e2de \u003c+78\u003e:    pop     {r7}\n   0x1000e2e0 \u003c+80\u003e:    bx      lr\n```\n## 4. Set watchpoints\nWe can set watchpoints to stop the program when a certain memory location is\nmodified. Let's set a watchpoint at the address of the instruction at $r2:\n```\n(gdb) watch $r2\n(gdb) c\nContinuing.\n\nThread 2 \"max32xxx.cpu\" hit Watchpoint 4: $r2\n\nOld value = -1057017071\nNew value = -515934414\ndo_some_math (a=-559038737, b=-17958194, c=-889271554, d=-1057017071) at src/debugger_challenge.c:41\n41      in src/debugger_challenge.c\n(gdb) info registers r2\nr2             0xe13f7732          -515934414\n```\n\n**Step through the instructions until the value of r2 starts with 0xe1 and\nrecord that value as value3**\n\n## 5. Writing to Registers and Memory\nSet a breakpoint at 0x1000e2e0 (the end of `do_some_math` and continue there:\n\n```\n(gdb) b *0x1000e2e0\nBreakpoint 3 at 0x1000e2e0: file src/debugger_challenge.c, line 42.\n(gdb) c\nContinuing.\n\nBreakpoint 3, 0x1000e2e0 in do_some_math (a=-559038737, b=-17958194, c=-889271554, d=-1057017071)\n    at src/debugger_challenge.c:42\n42      in src/debugger_challenge.c\n(gdb) \n```\n\nWith the `set` command we can now modify registers (make sure to reset them):\n```\n(gdb) info registers r0\nr0             0x0                 0\n(gdb) set $r0=111\n(gdb) info registers r0\nr0             0x6f                111\n(gdb) set $r0=0\n```\n\nAnd memory:\n```\n(gdb) x 0x20000000\n0x20000000 \u003cpulStack\u003e:\t0x00000000\n(gdb) set *0x20000000=0x111\n(gdb) x 0x20000000\n0x20000000 \u003cpulStack\u003e:\t0x00000111\n(gdb) set *0x20000000=0\n```\n\n## 6. Capturing the flag\nWith what you've learned, set a breakpoint at the first instruction of the\n`check_flag` function and continue up to there.\n\n`check_flag` has five arguments; let's check them out. The ARM calling convention\nis to place the first four arguments in registers and further arguments are\npushed to the stack.\n\nPrint the registers and then the top value on the stack to view the arguments:\n```\n(gdb) info registers\nr0             0x11111111          286331153\nr1             0x22222222          572662306\nr2             0x33333333          858993459\nr3             0x44444444          1145324612\nr4             0x0                 0\nr5             0x0                 0\nr6             0x0                 0\nr7             0x2001ffe8          537001960\nr8             0x0                 0\nr9             0x0                 0\nr10            0x0                 0\nr11            0x0                 0\nr12            0xf4240000          -198967296\nsp             0x2001ffe0          0x2001ffe0\nlr             0x1000e60b          268494347\npc             0x1000e4b0          0x1000e4b0 \u003ccheck_flag\u003e\nxPSR           0x61000000          1627389952\nfpscr          0x0                 0\nmsp            0x2001ffe0          0x2001ffe0\npsp            0x0                 0x0\nprimask        0x0                 0\nbasepri        0x0                 0\nfaultmask      0x0                 0\ncontrol        0x0                 0\n(gdb) \n```\n\nWe can see that arguments 1-4 (0x11111111, 0x22222222, 0x33333333, and\n0x44444444) are in registers r0 through r3, and the top value of the stack\nhold the fifth argument (0x55555555).\nNow, using what you have learned, change the values of the function so that the\nfirst argument is set to `value1`, the third argument is set to `value2`, and\nthe fifth argument is set to `value3`.\n\n```\n(gdb) set $r0 = 0x4c09b410 \n(gdb) set $r2 = 0x2001ffe0\n(gdb) set *0x2001ffe0 = 0xe13f7732\n(gdb) info registers\nr0             0x1000e738          268494648\nr1             0x22222222          572662306\nr2             0x2001ffe0          537001952\nr3             0x44444444          1145324612\nr4             0x0                 0\nr5             0x0                 0\nr6             0x0                 0\nr7             0x2001ffe8          537001960\nr8             0x0                 0\nr9             0x0                 0\nr10            0x0                 0\nr11            0x0                 0\nr12            0xf4240000          -198967296\nsp             0x2001ffe0          0x2001ffe0\nlr             0x1000e60b          268494347\npc             0x1000e4b0          0x1000e4b0 \u003ccheck_flag\u003e\nxPSR           0x61000000          1627389952\nfpscr          0x0                 0\nmsp            0x2001ffe0          0x2001ffe0\npsp            0x0                 0x0\nprimask        0x0                 0\nbasepri        0x0                 0\nfaultmask      0x0                 0\ncontrol        0x0                 0\n(gdb) x *0x2001ffe0\n0xe13f7732:  0xe13f7732\n```\nWe have successfully set the arguments for the function. Now, continue the\nprogram to see the flag:\n```\n(gdb) c\n```\n\nUsing the screen command set the device and baud rate so we can capture the flag.\n```bash\nscreen /dev/tty.usbmodem14202 115200\n``` \n\nThe flag is:\n```\nectf{debugger_c8bbc4e9dfbb74d5}\n```\n\n\n## 7. Patching the Binary with Hopper Disassembler\nOpen the ELF file in Hopper Disassembler and navigate to the `check_flag`\nfunction. You will see the instructions that are being executed\n```\n1000e4be ldr r3, =Ectfdebuggerni ; 0x1000e5b0, \"ectf{debugger_nicetrybutnotyet}\\\\n\"\n```\nThis is a flag meant to trick you. The real flag is in the `check_flag` function.\n\nAt the address 1000e4e4\n```assembly\nldr        r0, =0x4c09b410\n```\nThis is the address of the instruction that loads the flag into\nmemory.\n\nAt the .text ; argument #1 for method transform, dword_1000e5bc\nThere is an assembly code that loads the flag into memory. We can change the\nvalue of the flag by changing the instruction at 0x1000e5bc.\n\n```assembly\ndword_1000e5bc:\n1000e5bc         dd         0x4c09b410\n```\n\nAt the .text CODE_XREF=check_flag+198 you will see the following address:\n1000e582\nThis is the address of the instruction that loads the flag into memory.\n```assembly\nldr r3, [r7, #0x4]\nldr r2, =0x2001ffe0\ncmp r3, r2\nbeq loc_1000e594\n```\nThe instruction at 0x1000e582 loads the flag into memory. We can change the\nvalue of the flag by changing the instruction at 0x1000e582.\n\nAt the .text CODE_XREF=check_flag+216 you will see the following address:\n1000e594\nThis is the address of the instruction that loads the flag into memory.\n```assembly\nldr r3, [r7, #0x58]\nldr r2, =0xe13f7732\ncmp r3, r2\nbeq loc_1000e5a6\n```\nThe instruction at 0x1000e594 loads the flag into memory. We can change the\nvalue of the flag by changing the instruction at 0x1000e594.\n\nTo always accept the flag i attempted to patch the cmp and beq instructions to\nnop instructions. This will always accept the flag.\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucianoscarpaci%2Fctf-challenge-debugger-flag","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucianoscarpaci%2Fctf-challenge-debugger-flag","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucianoscarpaci%2Fctf-challenge-debugger-flag/lists"}