資料介紹
軟件簡(jiǎn)介
我們?cè)谧銎髽I(yè)安全時(shí),弱口令檢測(cè)是系統(tǒng)/網(wǎng)絡(luò)安全的最基礎(chǔ)的部分之一,根據(jù)經(jīng)驗(yàn),經(jīng)常會(huì)出現(xiàn)弱口令的服務(wù)如下:
- FTP
- SSH
- SMB
- MYSQL
- MSSQL
- POSTGRESQL
- REDIS
- MONGODB
- ELASTICSEARCH
那咱們就一起用GO來(lái)寫(xiě)一款常見(jiàn)服務(wù)的弱口令掃描器,且支持以插件的形式增加新的服務(wù)掃描模塊。我們的教程暫定為只掃以上服務(wù)。
給掃描器啟一個(gè)屌炸天的名字x-crack,在$GOPATH/src/中建立一個(gè)x-crack項(xiàng)目后開(kāi)始擼碼,不要給我說(shuō)什么底層原理、框架內(nèi)核,老夫敲代碼就是一把梭。
?
開(kāi)工
數(shù)據(jù)結(jié)構(gòu)定義
- 掃描模塊的輸入內(nèi)容為為IP、端口及協(xié)議的列表,我們需要定義一個(gè)IpAddr的數(shù)據(jù)結(jié)構(gòu);
- 每個(gè)服務(wù)的每次掃描需要傳入的參數(shù)為IP、端口、協(xié)議、用戶(hù)名和密碼,需要定義一個(gè)Service結(jié)構(gòu)來(lái)包括這些內(nèi)容;
- 每條Service的記錄在掃描模塊進(jìn)行嘗試后,會(huì)得出掃描結(jié)果成功與否,我們?cè)俣x一個(gè)ScanResult數(shù)據(jù)結(jié)構(gòu)。
按照開(kāi)發(fā)規(guī)范,數(shù)據(jù)結(jié)構(gòu)的定義統(tǒng)一放到models目錄中,全部的數(shù)據(jù)結(jié)構(gòu)定義如下:
package models
type Service struct {
Ip string
Port int
Protocol string
Username string
Password string
}
type ScanResult struct {
Service Service
Result bool
}
type IpAddr struct {
Ip string
Port int
Protocol string
}
FTP掃描模塊
go語(yǔ)言有現(xiàn)成的FTP模塊,我們找一個(gè)star數(shù)最多的直接go get安裝一下即可使用了:
go get -u github.com/jlaffaye/ftp
我們把所有的掃描模塊放到plugins目錄中,F(xiàn)TP協(xié)議的掃描插件如下所示:
package plugins
import (
"github.com/jlaffaye/ftp"
"x-crack/models"
"x-crack/vars"
"fmt"
)
func ScanFtp(s models.Service) (err error, result models.ScanResult) {
result.Service = s
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", s.Ip, s.Port), vars.TimeOut)
if err == nil {
err = conn.Login(s.Username, s.Password)
if err == nil {
defer conn.Logout()
result.Result = true
}
}
return err, result
}
每個(gè)連接需要設(shè)置超時(shí)時(shí)間,防止因網(wǎng)絡(luò)問(wèn)題導(dǎo)致的阻塞,我們打算通過(guò)程序的命令行來(lái)控制超時(shí)時(shí)間,所以定義了一個(gè)全局變量TimeOut。 放在vars模塊中的原因是防止放在這個(gè)模塊中后會(huì)和其他模塊互相調(diào)用導(dǎo)致的循環(huán)import
寫(xiě)代碼雖然可以一把梭,但是不能等著洋洋灑灑地把幾萬(wàn)行都寫(xiě)完再運(yùn)行,比如我們的目標(biāo)是造一輛豪車(chē),不能等著所有零件設(shè)計(jì)好,都裝上去再發(fā)動(dòng)車(chē)測(cè)試,正確的開(kāi)發(fā)流程是把寫(xiě)邊測(cè),不要等輪子造出來(lái),而是在螺絲、齒輪階段就測(cè)試。
以下為FTP掃描插件這個(gè)齒輪的測(cè)試代碼及結(jié)果。
package plugins_test import ( "x-crack/models" "x-crack/plugins" "testing" ) func TestScanFtp(t *testing.T) { s := models.Service{Ip: "127.0.0.1", Port: 21, Protocol: "ftp", Username: "ftp", Password: "ftp"} t.Log(plugins.ScanFtp(s)) }
測(cè)試結(jié)果滿足預(yù)期,說(shuō)明我們這個(gè)零件不是次品,可以繼續(xù)再造其他零件了。
$ go test -v plugins/ftp_test.go
=== RUN TestScanFtp
--- PASS: TestScanFtp (0.00s)
ftp_test.go:36: dial tcp 127.0.0.1:21: getsockopt: connection refused {{127.0.0.1 21 ftp ftp ftp} false}
PASS
ok command-line-arguments 0.025s
SSH掃描模塊
go的標(biāo)準(zhǔn)庫(kù)中自帶了ssh包,直接調(diào)用即可,完整代碼如下:
package plugins import ( "golang.org/x/crypto/ssh" "x-crack/models" "x-crack/vars" "fmt" "net" ) func ScanSsh(s models.Service) (err error, result models.ScanResult) { result.Service = s config := &ssh.ClientConfig{ User: s.Username, Auth: []ssh.AuthMethod{ ssh.Password(s.Password), }, Timeout: vars.TimeOut, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, } client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", s.Ip, s.Port), config) if err == nil { defer client.Close() session, err := client.NewSession() errRet := session.Run("echo xsec") if err == nil && errRet == nil { defer session.Close() result.Result = true } } return err, result }
同樣,每個(gè)子模塊寫(xiě)好后都需要先用go test跑一下看是否滿足預(yù)期,測(cè)試代碼如下:
package plugins_test
import (
"x-crack/models"
"x-crack/plugins"
"testing"
)
func TestScanSsh(t *testing.T) {
s := models.Service{Ip: "127.0.0.1", Port: 22, Username: "root", Password: "123456", Protocol: "ssh"}
t.Log(plugins.ScanSsh(s))
}
測(cè)試結(jié)果如下:
$ go test -v plugins/ssh_test.go
=== RUN TestScanSsh
--- PASS: TestScanSsh (0.00s)
ssh_test.go:36: dial tcp 127.0.0.1:22: getsockopt: connection refused {{127.0.0.1 22 ssh root 123456} false}
PASS
ok command-line-arguments 0.026s
SMB掃描模塊
SMB弱口令的掃描插件,我們使用了github.com/stacktitan/smb/smb包,同樣直接go get安裝一下即可拿來(lái)使用。 代碼如下:
package plugins
import (
"github.com/stacktitan/smb/smb"
"x-crack/models"
)
func ScanSmb(s models.Service) (err error, result models.ScanResult) {
result.Service = s
options := smb.Options{
Host: s.Ip,
Port: s.Port,
User: s.Username,
Password: s.Password,
Domain: "",
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err == nil {
session.Close()
if session.IsAuthenticated {
result.Result = true
}
}
return err, result
}
同樣也先寫(xiě)測(cè)試用例來(lái)測(cè)試一下,測(cè)試代碼如下:
package plugins_test
import (
"x-crack/models"
"x-crack/plugins"
"testing"
)
func TestScanSmb(t *testing.T) {
s := models.Service{Ip: "share.xsec.io", Port: 445, Protocol: "smb", Username: "xsec", Password: "fsafffdsfdsa"}
t.Log(plugins.ScanSmb(s))
}
測(cè)試結(jié)果:
hartnett at hartnettdeMacBook-Pro in /data/code/golang/src/x-crack (master) $ go test -v plugins/smb_test.go === RUN TestScanSmb --- PASS: TestScanSmb (0.04s) smb_test.go:36: NT Status Error: Logon failed {{share.xsec.io 445 smb xsec fsafffdsfdsa} false} PASS ok command-line-arguments 0.069s
MYSQL、MSSQL和POSTGRESQL掃描模塊
MYSQL、MSSQL和POSTGRESQL的掃描模塊,我使用了第三方的ORM?xorm,當(dāng)然也可以直接使用原生的sql driver來(lái)實(shí)現(xiàn),我們這里圖方便用xorm一把梭了。 對(duì)于xorm來(lái)說(shuō),這3個(gè)掃描插件的實(shí)現(xiàn)方法大同小異,為了節(jié)約篇幅,咱們只看mysql掃描插件的實(shí)現(xiàn),其他2個(gè)插件可以參考github中的完整源碼。 首先還是先go get要用到的包:
go get github.com/netxfly/mysql go get github.com/go-xorm/xorm github.com/go-xorm/core
接下來(lái)我們把需要驗(yàn)證的IP、port、username、password組成datasource傳遞給xorm,完整代碼如下:
package plugins
import (
_ "github.com/netxfly/mysql"
"github.com/go-xorm/xorm"
"github.com/go-xorm/core"
"x-crack/models"
"fmt"
)
func ScanMysql(service models.Service) (err error, result models.ScanResult) {
result.Service = service
dataSourceName := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8", service.Username,
service.Password, service.Ip, service.Port, "mysql")
Engine, err := xorm.NewEngine("mysql", dataSourceName)
if err == nil {
Engine.SetLogLevel(core.LOG_OFF)
// fix "[mysql] packets.go:33: unexpected EOF" error
Engine.SetMaxIdleConns(0)
// Engine.SetConnMaxLifetime(time.Second * 30)
defer Engine.Close()
err = Engine.Ping()
if err == nil {
result.Result = true
}
}
return err, result
}
眼尖的同學(xué)也許發(fā)現(xiàn)了,上面?github.com/netxfly/mysql?這個(gè)mysql包是放在筆者的github下的,這是為什么呢?
因?yàn)橹苯佑胢ysql這個(gè)包的話,在掃描的過(guò)程中會(huì)遇到[mysql] packets.go:33: unexpected EOF" error的異常輸出,影響了我們程序在掃描過(guò)程中輸出UI的美觀性,這對(duì)于帥氣的我是無(wú)法接受的,通過(guò)設(shè)置參數(shù)的方法無(wú)法解決,最后只好直接fork了一份mysql的包,把打印這個(gè)異常的語(yǔ)句注釋掉再提交上去直接使用了。
測(cè)試代碼:
package plugins_test
import (
"testing"
"x-crack/plugins"
"x-crack/models"
)
func TestScanMysql(t *testing.T) {
service := models.Service{Ip: "10.10.10.10", Port: 3306, Protocol: "mysql", Username: "root", Password: "123456"}
t.Log(plugins.ScanMysql(service))
}
測(cè)試結(jié)果:
go test -v plugins/mysql_test.go
=== RUN TestScanMysql
--- PASS: TestScanMysql (0.02s)
mysql_test.go:36: Error 1045: Access denied for user 'root'@'10.10.10.100' (using password: YES) {{10.10.10.10 3306 mysql root 123456} false}
PASS
ok command-line-arguments 0.041s
Redis掃描模塊
go get安裝第三方包github.com/go-redis/redis,完整代碼如下:
package plugins
import (
"github.com/go-redis/redis"
"x-crack/models"
"x-crack/vars"
"fmt"
)
func ScanRedis(s models.Service) (err error, result models.ScanResult) {
result.Service = s
opt := redis.Options{Addr: fmt.Sprintf("%v:%v", s.Ip, s.Port),
Password: s.Password, DB: 0, DialTimeout: vars.TimeOut}
client := redis.NewClient(&opt)
defer client.Close()
_, err = client.Ping().Result()
if err == nil {
result.Result = true
}
return err, result
}
測(cè)試代碼:
package plugins_test
import (
"x-crack/models"
"x-crack/plugins"
"testing"
)
func TestScanRedis(t *testing.T) {
s := models.Service{Ip: "127.0.0.1", Port: 6379, Password: "test"}
t.Log(plugins.ScanRedis(s))
}
測(cè)試結(jié)果:
go test -v plugins/redis_test.go
=== RUN TestScanRedis
--- PASS: TestScanRedis (0.00s)
redis_test.go:36: dial tcp 127.0.0.1:6379: getsockopt: connection refused {{127.0.0.1 6379 test} false}
PASS
ok command-line-arguments 0.025s
MONGODB掃描模塊
mongodb掃描模塊依賴(lài)mgo包,可用go get合令直接安裝。
go get gopkg.in/mgo.v2
完整代碼:
package plugins
import (
"gopkg.in/mgo.v2"
"x-crack/models"
"x-crack/vars"
"fmt"
)
func ScanMongodb(s models.Service) (err error, result models.ScanResult) {
result.Service = s
url := fmt.Sprintf("mongodb://%v:%v@%v:%v/%v", s.Username, s.Password, s.Ip, s.Port, "test")
session, err := mgo.DialWithTimeout(url, vars.TimeOut)
if err == nil {
defer session.Close()
err = session.Ping()
if err == nil {
result.Result = true
}
}
return err, result
}
測(cè)試結(jié)果:
go test -v plugins/mongodb_test.go === RUN TestScanMongodb --- PASS: TestScanMongodb (3.53s) mongodb_test.go:36: no reachable servers {{127.0.0.1 27017 mongodb test test} false} PASS ok command-line-arguments 3.558s
ELASTICSEARCH掃描模塊
ELASTICSEARCH掃描插件依賴(lài)第三方包gopkg.in/olivere/elastic.v3,同樣也是直接go get安裝。 完整代碼如下:
package plugins
import (
"gopkg.in/olivere/elastic.v3"
"x-crack/models"
"fmt"
)
func ScanElastic(s models.Service) (err error, result models.ScanResult) {
result.Service = s
client, err := elastic.NewClient(elastic.SetURL(fmt.Sprintf("http://%v:%v", s.Ip, s.Port)),
elastic.SetMaxRetries(3),
elastic.SetBasicAuth(s.Username, s.Password),
)
if err == nil {
_, _, err = client.Ping(fmt.Sprintf("http://%v:%v", s.Ip, s.Port)).Do()
if err == nil {
result.Result = true
}
}
return err, result
}
測(cè)試代碼:
package plugins_test
import (
"x-crack/models"
"x-crack/plugins"
"testing"
)
func TestScanElastic(t *testing.T) {
s := models.Service{Ip: "127.0.0.1", Port: 9200, Protocol: "elastic", Username: "root", Password: "123456"}
t.Log(plugins.ScanElastic(s))
}
測(cè)試結(jié)果如下:
go test -v plugins/elastic_test.go === RUN TestScanElastic --- PASS: TestScanElastic (5.02s) elastic_test.go:36: no Elasticsearch node available {{127.0.0.1 9200 elastic root 123456} false} PASS ok command-line-arguments 5.061s
掃描模塊插件化
前面我們寫(xiě)好的掃描插件的函數(shù)原始是一致,我們可以將這組函數(shù)放到一個(gè)map中,在掃描的過(guò)程中自動(dòng)化根據(jù)不同的協(xié)議調(diào)用不同的掃描插件。
以后新加的掃描插件,可以按這種方法直接注冊(cè)。
package plugins
import (
"x-crack/models"
)
type ScanFunc func(service models.Service) (err error, result models.ScanResult)
var (
ScanFuncMap map[string]ScanFunc
)
func init() {
ScanFuncMap = make(map[string]ScanFunc)
ScanFuncMap["FTP"] = ScanFtp
ScanFuncMap["SSH"] = ScanSsh
ScanFuncMap["SMB"] = ScanSmb
ScanFuncMap["MSSQL"] = ScanMssql
ScanFuncMap["MYSQL"] = ScanMysql
ScanFuncMap["POSTGRESQL"] = ScanPostgres
ScanFuncMap["REDIS"] = ScanRedis
ScanFuncMap["ELASTICSEARCH"] = ScanElastic
ScanFuncMap["MONGODB"] = ScanMongodb
}
掃描任務(wù)調(diào)度
前面我們寫(xiě)好了一些常見(jiàn)服務(wù)的弱口令掃描插件,也測(cè)試通過(guò)了。 接下來(lái)我們需要實(shí)現(xiàn)從命令行參數(shù)傳遞iplist、用戶(hù)名字典和密碼字典進(jìn)去,并讀取相應(yīng)的信息進(jìn)行掃描調(diào)度的功能,細(xì)分一下,需要做以下幾件事:
- 讀取iplist列表
- 讀取用戶(hù)名字典
- 讀取密碼字典
- 生成掃描任務(wù)
- 掃描任務(wù)調(diào)度
- 掃描任務(wù)執(zhí)行
- 掃描結(jié)果保存
- 命令行調(diào)用外殼
讀取ip\用戶(hù)名和密碼字典
該模塊主要用了標(biāo)準(zhǔn)庫(kù)中的bufio包,逐行讀取文件,進(jìn)行過(guò)濾后直接生成相應(yīng)的slice。其中iplist支持以下格式:
127.0.0.1:3306|mysql 8.8.8.8:22 9.9.9.9:6379 108.61.223.105:2222|ssh
對(duì)于標(biāo)準(zhǔn)的端口,程序可以自動(dòng)判斷其協(xié)議,對(duì)于非標(biāo)準(zhǔn)端口的協(xié)議,需要在后面加一個(gè)字段標(biāo)注一下協(xié)議。
為了防止咱們的程序被腳本小子們?yōu)E用,老夫就不提供端口掃描、協(xié)議識(shí)別等功能了,安全工程師們可以把自己公司的端口掃描器產(chǎn)出的結(jié)果丟到這個(gè)里面來(lái)掃。
package util import ( "x-crack/models" "x-crack/logger" "x-crack/vars" "os" "bufio" "strings" "strconv" ) func ReadIpList(fileName string) (ipList []models.IpAddr) { ipListFile, err := os.Open(fileName) if err != nil { logger.Log.Fatalf("Open ip List file err, %v", err) } defer ipListFile.Close() scanner := bufio.NewScanner(ipListFile) scanner.Split(bufio.ScanLines) for scanner.Scan() { ipPort := strings.TrimSpace(scanner.Text()) t := strings.Split(ipPort, ":") ip := t[0] portProtocol := t[1] tmpPort := strings.Split(portProtocol, "|") // ip列表中指定了端口對(duì)應(yīng)的服務(wù) if len(tmpPort) == 2 { port, _ := strconv.Atoi(tmpPort[0]) protocol := strings.ToUpper(tmpPort[1]) if vars.SupportProtocols[protocol] { addr := models.IpAddr{Ip: ip, Port: port, Protocol: protocol} ipList = append(ipList, addr) } else { logger.Log.Infof("Not support %v, ignore: %v:%v", protocol, ip, port) } } else { // 通過(guò)端口查服務(wù) port, err := strconv.Atoi(tmpPort[0]) if err == nil { protocol, ok := vars.PortNames[port] if ok && vars.SupportProtocols[protocol] { addr := models.IpAddr{Ip: ip, Port: port, Protocol: protocol} ipList = append(ipList, addr) } } } } return ipList } func ReadUserDict(userDict string) (users []string, err error) { file, err := os.Open(userDict) if err != nil { logger.Log.Fatalf("Open user dict file err, %v", err) } defer file.Close() scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) for scanner.Scan() { user := strings.TrimSpace(scanner.Text()) if user != "" { users = append(users, user) } } return users, err } func ReadPasswordDict(passDict string) (password []string, err error) { file, err := os.Open(passDict) if err != nil { logger.Log.Fatalf("Open password dict file err, %v", err) } defer file.Close() scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) for scanner.Scan() { passwd := strings.TrimSpace(scanner.Text()) if passwd != "" { password = append(password, passwd) } } password = append(password, "") return password, err }
IP列表、用戶(hù)名字典與密碼字典讀取的測(cè)試代碼:
package util_test
import (
"x-crack/util"
"testing"
)
func TestReadIpList(t *testing.T) {
ipList := "/tmp/iplist.txt"
t.Log(util.ReadIpList(ipList))
}
func TestReadUserDict(t *testing.T) {
userDict := "/tmp/user.dic"
t.Log(util.ReadUserDict(userDict))
}
func TestReadPasswordDict(t *testing.T) {
passDict := "/tmp/pass.dic"
t.Log(util.ReadPasswordDict(passDict))
}
這個(gè)模塊的測(cè)試結(jié)果如下:
go test -v util/file_test.go
=== RUN TestReadIpList
--- PASS: TestReadIpList (0.00s)
file_test.go:35: [{127.0.0.1 3306 MYSQL} {8.8.8.8 22 SSH} {9.9.9.9 6379 REDIS} {108.61.223.105 2222 SSH}]
=== RUN TestReadUserDict
--- PASS: TestReadUserDict (0.00s)
file_test.go:40: [root admin test guest info adm mysql user administrator ftp sa]
=== RUN TestReadPasswordDict
--- PASS: TestReadPasswordDict (0.00s)
file_test.go:45: [1314520520 135246 135246789 135792468 1357924680 147258369 1472583690 1qaz2wsx 5201314 54321 55555 654321 789456123 88888 888888 88888888 987654321 9876543210 ^%$#@~! a123123 a123456 a12345678 a123456789 aa123456 aa123456789 aaa123456 aaaaa aaaaaa aaaaaaaa abc123 abc123456 abc123456789 abcd123 abcd1234 abcd123456 admin admin888 ]
PASS
ok command-line-arguments 0.022s
其中iplist在加載的過(guò)程中不是無(wú)腦全部讀進(jìn)去的,在正式掃描前會(huì)先過(guò)濾一次,把不通的ip和端口對(duì)剔除掉,以免影響掃描效率,代碼如下:
package util
import (
"gopkg.in/cheggaaa/pb.v2"
"x-crack/models"
"x-crack/logger"
"x-crack/vars"
"net"
"sync"
"fmt"
)
var (
AliveAddr []models.IpAddr
mutex sync.Mutex
)
func init() {
AliveAddr = make([]models.IpAddr, 0)
}
func CheckAlive(ipList []models.IpAddr) ([]models.IpAddr) {
logger.Log.Infoln("checking ip active")
var wg sync.WaitGroup
wg.Add(len(ipList))
for _, addr := range ipList {
go func(addr models.IpAddr) {
defer wg.Done()
SaveAddr(check(addr))
}(addr)
}
wg.Wait()
vars.ProcessBarActive.Finish()
return AliveAddr
}
func check(ipAddr models.IpAddr) (bool, models.IpAddr) {
alive := false
_, err := net.DialTimeout("tcp", fmt.Sprintf("%v:%v", ipAddr.Ip, ipAddr.Port), vars.TimeOut)
if err == nil {
alive = true
}
vars.ProcessBarActive.Increment()
return alive, ipAddr
}
func SaveAddr(alive bool, ipAddr models.IpAddr) {
if alive {
mutex.Lock()
AliveAddr = append(AliveAddr, ipAddr)
mutex.Unlock()
}
}
通過(guò)標(biāo)準(zhǔn)端口查詢(xún)對(duì)應(yīng)服務(wù)的功能在vars包中定義了,為了避免多個(gè)包之間的循環(huán)導(dǎo)入,我們把所有的全局變量都集中到了一個(gè)獨(dú)立的vars包中。
PortNamesmap為標(biāo)準(zhǔn)端口對(duì)應(yīng)的服務(wù),在加了新的掃描插件后,也需要更新這個(gè)map的內(nèi)容。
package vars
import (
"github.com/patrickmn/go-cache"
"gopkg.in/cheggaaa/pb.v2"
"sync"
"time"
"strings"
)
var (
IpList = "iplist.txt"
ResultFile = "x_crack.txt"
UserDict = "user.dic"
PassDict = "pass.dic"
TimeOut = 3 * time.Second
ScanNum = 5000
DebugMode bool
StartTime time.Time
ProgressBar *pb.ProgressBar
ProcessBarActive *pb.ProgressBar
)
var (
CacheService *cache.Cache
Mutex sync.Mutex
PortNames = map[int]string{
21: "FTP",
22: "SSH",
445: "SMB",
1433: "MSSQL",
3306: "MYSQL",
5432: "POSTGRESQL",
6379: "REDIS",
9200: "ELASTICSEARCH",
27017: "MONGODB",
}
// 標(biāo)記特定服務(wù)的特定用戶(hù)是否破解成功,成功的話不再?lài)L試破解該用戶(hù)
SuccessHash map[string]bool
SupportProtocols map[string]bool
)
func init() {
SuccessHash = make(map[string]bool)
CacheService = cache.New(cache.NoExpiration, cache.DefaultExpiration)
SupportProtocols = make(map[string]bool)
for _, proto := range PortNames {
SupportProtocols[strings.ToUpper(proto)] = true
}
}
?
- TCA8424低壓8x16鍵盤(pán)掃描器數(shù)據(jù)表
- Android Things I2C地址掃描器
- I2C掃描器開(kāi)源硬件
- DSP電路板測(cè)試中的邊界掃描技術(shù)研究綜述 9次下載
- OTP動(dòng)態(tài)口令的詳細(xì)資料說(shuō)明
- SR-700系列超小型二維碼掃描器的詳細(xì)介紹和數(shù)據(jù)手冊(cè)免費(fèi)下載 13次下載
- OS32C激光掃描器特點(diǎn)及應(yīng)用 5次下載
- 基于OS32C激光掃描器軟件配置及配線 18次下載
- HR3220的無(wú)線掃描器用戶(hù)手冊(cè) 12次下載
- NLS-HR15XX-30有線式掃描槍用戶(hù)手冊(cè) 27次下載
- 基于NLS-HR15XX-3E手持式條碼掃描器使用方法 19次下載
- ADS2013.06 CRACK 1859次下載
- LS3408ER堅(jiān)固耐用的掃描器
- netTAP網(wǎng)關(guān)在SICK條碼掃描器通訊上的應(yīng)用
- 新的口令認(rèn)證密鑰協(xié)商協(xié)議
- 工業(yè)二維碼條碼掃描器流水線條碼掃描 105次閱讀
- 固定式工業(yè)級(jí)二維碼掃描器選型方法 102次閱讀
- 工業(yè)固定式掃描器怎樣用?固定式工業(yè)條碼掃描器解決方案 237次閱讀
- 固定式工業(yè)條碼掃描器在mes系統(tǒng)中的各個(gè)環(huán)節(jié)應(yīng)用 404次閱讀
- 介紹一款智能Web弱口令爆破工具 2013次閱讀
- 基于C++的網(wǎng)絡(luò)掃描器設(shè)計(jì) 821次閱讀
- NanoBeacon? BLE掃描器教程(第四部分) 608次閱讀
- NanoBeacon? BLE掃描器教程(第二部分) 867次閱讀
- NanoBeacon? BLE掃描器教程(第一部分) 939次閱讀
- TPS61376升壓轉(zhuǎn)換器助力更高集成度條碼掃描器方案 842次閱讀
- 一款支持弱口令爆破的內(nèi)網(wǎng)資產(chǎn)探測(cè)漏洞掃描工具SweetBabyScan 4946次閱讀
- 常見(jiàn)服務(wù)弱口令爆破工具:crack 2409次閱讀
- 基于mPSD3254BV單片機(jī)和MAX604芯片實(shí)現(xiàn)無(wú)線掃描器的設(shè)計(jì) 2316次閱讀
- 條碼掃描器四種技術(shù)工作原理解析 1.9w次閱讀
- 弱密碼是什么?有什么用?應(yīng)用在哪? 9319次閱讀
下載排行
本周
- 1山景DSP芯片AP8248A2數(shù)據(jù)手冊(cè)
- 1.06 MB | 532次下載 | 免費(fèi)
- 2RK3399完整板原理圖(支持平板,盒子VR)
- 3.28 MB | 339次下載 | 免費(fèi)
- 3TC358743XBG評(píng)估板參考手冊(cè)
- 1.36 MB | 330次下載 | 免費(fèi)
- 4DFM軟件使用教程
- 0.84 MB | 295次下載 | 免費(fèi)
- 5元宇宙深度解析—未來(lái)的未來(lái)-風(fēng)口還是泡沫
- 6.40 MB | 227次下載 | 免費(fèi)
- 6迪文DGUS開(kāi)發(fā)指南
- 31.67 MB | 194次下載 | 免費(fèi)
- 7元宇宙底層硬件系列報(bào)告
- 13.42 MB | 182次下載 | 免費(fèi)
- 8FP5207XR-G1中文應(yīng)用手冊(cè)
- 1.09 MB | 178次下載 | 免費(fèi)
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 2555集成電路應(yīng)用800例(新編版)
- 0.00 MB | 33566次下載 | 免費(fèi)
- 3接口電路圖大全
- 未知 | 30323次下載 | 免費(fèi)
- 4開(kāi)關(guān)電源設(shè)計(jì)實(shí)例指南
- 未知 | 21549次下載 | 免費(fèi)
- 5電氣工程師手冊(cè)免費(fèi)下載(新編第二版pdf電子書(shū))
- 0.00 MB | 15349次下載 | 免費(fèi)
- 6數(shù)字電路基礎(chǔ)pdf(下載)
- 未知 | 13750次下載 | 免費(fèi)
- 7電子制作實(shí)例集錦 下載
- 未知 | 8113次下載 | 免費(fèi)
- 8《LED驅(qū)動(dòng)電路設(shè)計(jì)》 溫德?tīng)栔?/a>
- 0.00 MB | 6656次下載 | 免費(fèi)
總榜
- 1matlab軟件下載入口
- 未知 | 935054次下載 | 免費(fèi)
- 2protel99se軟件下載(可英文版轉(zhuǎn)中文版)
- 78.1 MB | 537798次下載 | 免費(fèi)
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420027次下載 | 免費(fèi)
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 5Altium DXP2002下載入口
- 未知 | 233046次下載 | 免費(fèi)
- 6電路仿真軟件multisim 10.0免費(fèi)下載
- 340992 | 191187次下載 | 免費(fèi)
- 7十天學(xué)會(huì)AVR單片機(jī)與C語(yǔ)言視頻教程 下載
- 158M | 183279次下載 | 免費(fèi)
- 8proe5.0野火版下載(中文版免費(fèi)下載)
- 未知 | 138040次下載 | 免費(fèi)
電子發(fā)燒友App






創(chuàng)作
發(fā)文章
發(fā)帖
提問(wèn)
發(fā)資料
發(fā)視頻
上傳資料賺積分
評(píng)論