66

在多GPU系统上使用hashcat进行密码破解

 5 years ago
source link: https://www.freebuf.com/articles/system/202537.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

就在几天前我针对Linux服务器池做了一次渗透测试,在我执行这项任务的时候我就知道在这些服务器上存在很大的密码重用概率。我设法获取其中一个服务器上的shell并通过使用privesc内核漏洞来取得root权限,然后我就能够完全的控制这台服务器。而对于其他的服务器,由于我知道密码可能会被重复使用,因此我只要利用已获取的root访问权限来提取/etc/shadow文件就很有可能将它们一举拿下。

/etc/shadow 文件

/etc/shadow文件是用于存放用户帐户及其密码的以密文形式存储使用多种哈希算法之一。每次用户使用其密码登录时系统都会检查该帐户使用的哈希算法并对用户输入的密码执行哈希操作然后将输出与影子文件中的存储值进行比较如果结果是相同的则用户帐户验证成功并允许登录操作

所以我现在要做的第一步是分析我刚刚恢复的影子文件中存储密码的加密哈希类型。

Linux Hashes

在类Linux的系统上你可以通过查看以下文件来了解用于密码散列常用的算法

/etc/pam.d/common-password

UvyyyiE.jpg!web

这里我们可以看到散列算法为sha512。我们也可以通过直接查看存储在影子文件中的散列来识别其使用的散列算法。

例如

q67juey.jpg!web

在该文件中我们在哈希的开头看到一个 $6 的符号。该符号标识用于生成它的散列算法。根据所使用的算法 $ 符号的数字可以从1到6。

另外从该行我们还可以获取到其他一些信息例如算法的选项盐最后是散列密码如下图所示

mIfqMnM.jpg!web

这里真正与我们相关的是其算法和散列密码。salt是用于生成散列的随机字符串因此它可以防止诸如 彩虹表之类的攻击 。其他几个选项也可以在文件中找到我建议你在 这里学习 更多关于它们的知识。下面我们将继续进行密码破解的过程因为文件中的其他选项/参数与密码破解无关。

这里要注意的一个主要问题是散列算法因为根据所使用的散列算法我们可能会花费不同的时间来破解密码但并不是越高级的算法就一定意味着破解效率就低。

$1  = MD5 hashing algorithm.
$2  =Blowfish Algorithm.
$2a =eksblowfish Algorithm
$5  =SHA-256 Algorithm
$6  =SHA-512 Algorithm

正如你看到的数字越大就意味着散列算法越高级。但这并不意味着使用越高级的散列算法哈希就越难被破解。但请注意例如MD5是一种已过时的哈希算法通过碰撞攻击就可以轻易的破解出它但对于sha算法碰撞攻击就会困难许多。

Hash 破解

一个针对影子文件密码破解的标准攻击是检测哈希算法然后根据某些字符组合生成一组哈希并将它们中的每一个与我们希望破解的哈希进行比较以查看是否匹配如果匹配则破解成功。

像john the ripper或hashcat这些破解程序基本上就是这么做的他们会先分析这些哈希然后对其发起某种暴力攻击以获取密码。算法很容易理解我们生成一个candite然后根据散列值对其进行测试如果它们不匹配那么我们重复这个过程。如果我们将字典分成2个相同的文件将它们发送到一个单独的处理器我们可以将攻击的速度提升两倍如果我们有更多的CPU我们就可以加速n倍

但如果你考虑哈希生成和比较操作的本质并结合你的实际经验你会发现通过GPU进行的并行化破解操作可以获得比基于CPU的破解更好的结果。

为什么

因为GPU具有成百上千个核非常适合执行这些操作。以下我引用 Thomas Pornin 的解释

GPU通过使用成百上千个核的大量并行性来实现其卓越的性能。这可以通过流水线操作实现每个单独的操作需要多个周期才能运行但是连续操作可以像高速公路上的卡车一样启动并共享指令解码因为许多核将同时运行相同的指令。

hashcat 可以使用一个或多个GPU的强大功能来破解许多不同的密码哈希值。

因此我决定使用多个GPU和带有hashcat程序的linux操作系统构建一个专门的破解站。

在本文中我将使用以下文件进行一系列的演示

shadow2 点击下载

[...]
user3122:$1$FxueP4SH$Fn5lpvorz.XGJ0.aNjPs9/:14968:0:99999:7
user6105:$1$GTMXpwtl$nDHwFc7bNPNh5Z0R6Xn2D1:16813:0:99999:7
user7255:$1$HUu26aGL$AC8YVXyqHmAhkQcXzWwze.:14524:0:99999:7
user8167:$1$Y8JtHBbU$EQvnJ3eu14rLvBfvWhCmJ0:15999:0:99999:7
user9880:$1$WuZBV4XE$p95fTpYm4qYrvyFo1QZlg.:15566:0:99999:7
user3133:$1$cx.Ctgl.$2kV5bIfTfanzowVCOQVJ8/:14383:0:99999:7
user6859:$1$YjpXADgu$SZpQEmQ4dsdMMZtQ0vgen0:17338:0:99999:7
user9180:$1$CabG48K.$uP4nUqVpfqlX3.hxm27R/.:17271:0:99999:7
user1795:$1$7uWjhHle$V5S0g0RlNMqxwUNw4PeRy/:15913:0:99999:7
user4566:$1$iTcKPKG/$96bYX9pEx7Exqq66W2NrY1:15279:0:99999:7
user6653:$1$XMY2RA1c$k0zPPbRfjm3kOU3i4FRo0.:15257:0:99999:7
[...]

该文件包含500多个MD5哈希密码

下面的文件则包含了一个SHA512哈希密码

single 点击下载

$6$fQsjcwyB$N/HCQx9xohBVqkqAQFhVpkg2Bp3Ki51MMZPED4CQ9e/FLx0yRwnMVoaPcY7UtZJAlXjMrUgflazaspzvClaUX.

我们将使用这些文件稍微测试一下hashcat检查它的性能并了解它的基本原理。

设置

经过我和团队的考虑构建一个破解站大致需要以下组件

主板 Asus H170 Pro Gaming 

CPU Celeron G3900

内存 Kingston ValueRAM DDR4 2133 PC4-17000 4GB CL15

硬盘 Seagate Barracuda 500GB 7.2 rpm

GPU 6x AMD RX58 0

电源 Seasonic SSR-850PX

总费用大概需要1650 欧

I3EfEb2.jpg!web

以上安装完成后我们决定安装ubuntu 18.04作为操作系统。Ubuntu附带了一些标准的视频驱动程序但为了能够释放我们GPU的全部功能我们需要下载并安装其专有的驱动程序。你可以在 这里 找到它们。

安装驱动程序后剩下的就是下载 hashcat 程序。

设置检测

下载hashcat后我们可以通过简单运行它的主二进制文件来执行它。这里我为hashcat创建了一个别名并指向/fullpath/hashcat64.bin。

你可以使用许多不同的参数从命令行运行Hashcat。首先让我们使用“-I”选项来检查我们的设置如果我们已正确安装了GPU我们应该能够看到它们并会为我们列出其属性和使用的驱动程序信息。

root@Hassium:/# hashcat -I
hashcat (v5.1.0) starting...

OpenCL Info:

Platform ID #1
  Vendor  : Advanced Micro Devices, Inc.
  Name    : AMD Accelerated Parallel Processing
  Version : OpenCL 2.1 AMD-APP (2766.4)

  Device ID #1
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1411
    Memory         : 4048/7867 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

  Device ID #2
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1340
    Memory         : 4048/8155 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

  Device ID #3
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1411
    Memory         : 4048/8155 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

  Device ID #4
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1411
    Memory         : 4048/8155 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

  Device ID #5
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1411
    Memory         : 4048/8155 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

  Device ID #6
    Type           : GPU
    Vendor ID      : 1
    Vendor         : Advanced Micro Devices, Inc.
    Name           : Ellesmere
    Version        : OpenCL 1.2 AMD-APP (2766.4)
    Processor(s)   : 36
    Clock          : 1411
    Memory         : 4048/8155 MB allocatable
    OpenCL Version : OpenCL C 1.2 
    Driver Version : 2766.4

root@Hassium:/#

可以看到检测结果一切正常。现在我们就可以利用它们来破解哈希了。

对设置进行基准测试

hashcat -b -D 1,2

这将对我们的系统进行基准测试包括CPU和所有的GPU。

字典攻击

字典攻击是最简单和最容易理解的。在字典攻击中我们使用一个包含多个潜在密码的字典文件我们对其中的每一个进行哈希处理并将其与要破解的散列进行比较。

在该场景中hashcat可以将字典分成N个部分并将每个部分转发给一个GPU这样就可以并行处理多个字典文件并大大提升我们的破解速度。

密码字典里包括许多人们习惯性设置的密码这样可以提高密码破译软件的密码破译成功率和命中率缩短密码破译的时间。

以下是一些密码字典的使用建议:

在针对未知目标的大规模攻击时可以使用一些最常见的密码字典进行攻击。

从SN数据库泄露的密码字典在大规模和有针对性的攻击中非常有用因为我们很有可能会在某些SN中找到我们的目标。

定制的字典根据目标的爱好个性特征等这样的字典非常适用于有针对性的攻击。

使用字典进行散列破解的命令如下

hashcat -a 0 -m 1800 hashes1.txt dic_eng.txt

-a参数是指定攻击模式0代表Straight模式使用字典进行破解尝试-m参数是告诉hashcat解密的Hash类型1800则是指SHA-512(Unix)类型密码最后是准备的密码字典文件。

你可以在 这里 找到一些比较好用的字典

让我们针对我们的single.txt哈希文件尝试这种攻击方案。

hashcat64.bin -a 0 -m 1800 /hashes/single.txt /dicts/rockyou-75.txt
cat hashcat.potfile
Started: Mon Apr 15 19:19:15 2019
[2K
Stopped: Mon Apr 15 19:19:44 2019

可以看到从19:19:15 到 19:19:44之并没有任何结果。

让我们用shadow2.txt文件试一下

echo "" > /home/cherrysan/hashcat-5.1.0/hashcat.potfile
/home/cherrysan/hashcat-5.1.0/hashcat64.bin -a 0 -m 500 /hashes/shadow2 /dicts/dic_eng.txt
cat /home/cherrysan/hashcat-5.1.0/hashcat.potfile
date
Started: Mon Apr 15 19:59:40 2019
[2K
Stopped: Mon Apr 15 20:03:13 2019

同样也没有任何的结果。

组合攻击

相较于单个字典文件的攻击组合攻击的优势在于它们可以使用两个文件组合来创建新的单词然后使用它们生成哈希并执行攻击。

hashcat -a 1 -m 1800 hashes2.txt english_names.txt years.txt

例如在我们的例子中我们可以将common_english_names字典中的所有单词与1900年到2018年的所有年份组合在一起这将产生如下组合

james1999
john1982
maria1978
[...]

这将非常有用因为很多人倾向于使用诸如nameYear之类的密码特别是当他们注册的页面/系统要求使用字母数字的组合密码时。

让我们再次针对我们的single.txt哈希文件尝试这种攻击方案。

hashcat64.bin -a 1 -m 500 /hashes/shadow2 /dicts/noms_sense_accents.txt /dicts/anys.txt
cat hashcat.potfile
date
Started: Mon Apr 15 20:03:13 2019
[2K
Stopped: Mon Apr 15 20:23:15 2019

$1$B15.1/Vy$wF4/SG.DtqdYhSPwge4gf.:Joan1995
$1$ssG4kpwe$knWPdtFJ7S2YrtxMd.lcI.:Lluis1999
$1$dha8Ks6T$8YMKv7.bxz0SXMa1dpUZG.:Laura2008
$1$LXoiQGrz$JGh7n4IrXgpW/jspXye3m/:Eduard1986
$1$zqmt75IJ$8iWYtnAczWg0AfVPir1A0.:Xavier2013

可以看到密码被成功破解了出来

爆破/掩码攻击

如果我们运气不好密码字典种并不包含目标的密码那么破解程序仍会对哈希进行爆破攻击生成所有字符组合并一个接一个地测试它们。例如:

a
b
c
d
[...]

然后是

[...]
aa
bb
ab
cc
c1
[...]

直到找到密码或达到死点。

这将非常的繁琐和耗时。如果我们知道我们的目标密码是6个字符呢 如果我们已经知道第一个字符是什么

hashcat -a 3 -m 500 hashes1.txt ?l?l?l

以下命令将对包含md5哈希的hashes1.txt文件发起攻击测试每3个字母的组合。

我们可以使用以下参数作为掩码

?l = abcdefghijklmnopqrstuvwxyz
?u = ABCDEFGHIJKLMNOPQRSTUVWXYZ
?d = 0123456789
?h = 0123456789abcdef
?H = 0123456789ABCDEF
?s = «space»!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
?a = ?l?u?d?s
?b = 0x00 - 0xff

请注意我们可以在掩码中使用固定字符例如

?1?l?l?l?l?l19?d?d

该掩码将生成以下keyspace键空间

aaaaaa1900 - Zzzzzz1999
hashcat64.bin -a 3 -m 1800 /hashes/single.txt ?l?l?l?l?d?d?d
cat hashcat.potfile
Started: Mon Apr 15 19:19:49 2019
[2K
Stopped: Mon Apr 15 19:21:15 2019

$6$fQsjcwyB$N/HCQx9xohBVqkqAQFhVpkg2Bp3Ki51MMZPED4CQ9e/FLx0yRwnMVoaPcY7UtZJAlXjMrUgflazaspzvClaUX.:hack123

可以看到我们在4分钟不到的时间里就找到了哈希

让我们在shadow2文件中尝试一下

echo "" > /home/cherrysan/hashcat-5.1.0/hashcat.potfile
/home/cherrysan/hashcat-5.1.0/hashcat64.bin -a 3 -m 500 /hashes/shadow2 ?l?l?l
cat /home/cherrysan/hashcat-5.1.0/hashcat.potfile
date
Started: Mon Apr 15 20:23:15 2019
[2K
Stopped: Mon Apr 15 20:32:52 2019

三字符的组合种并没有找到密码。

混合攻击

Hashcat还为我们提供了一个名为“hybryd attack”的有趣攻击场景。在混合攻击中我们可以使用掩码将字典中的单词与某些字符组合在一起。

hashcat -a 6 -m 500 hashes1.txt dic_eng.txt ?d?d -i

在该攻击中我们通过将dict_eng.txt中的每个单词与2位数组合来攻击存储在hashes1.txt中的md5哈希值。我们将生成以下单词:

[...]
chair12
tree99
cat11
[...]

注意我们可以在字典名称之后或之前附加掩码具体取决于在每个单词之后或之前使用掩码生成其他字符的位置。

下面我将使用一个包含500多个MD5密码哈希值的文件你可以在这里下载到它。

我们可以使用以下命令尝试混合攻击。

hashcat -a 6 -m 500 /hashes/shadow2 /dicts/rockyou-75.txt ?d?d -i
root@Hassium:/# hashcat -a 6 -m 500 /hashes/shadow2 /dicts/rockyou-75.txt ?d?d -i
hashcat (v5.1.0) starting...

OpenCL Platform #1: Advanced Micro Devices, Inc.
================================================
* Device #1: Ellesmere, 4048/7864 MB allocatable, 36MCU
* Device #2: Ellesmere, 4048/8155 MB allocatable, 36MCU
* Device #3: Ellesmere, 4048/8155 MB allocatable, 36MCU
* Device #4: Ellesmere, 4048/8155 MB allocatable, 36MCU
* Device #5: Ellesmere, 4048/8155 MB allocatable, 36MCU
* Device #6: Ellesmere, 4048/8155 MB allocatable, 36MCU
[...]
Session..........: hashcat
Status...........: Running
Hash.Type........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: /hashes/shadow2
Time.Started.....: Sun Apr 14 23:15:14 2019 (13 secs)
Time.Estimated...: Sun Apr 14 23:19:14 2019 (3 mins, 47 secs)
Guess.Base.......: File (/dicts/rockyou-75.txt), Left Side
Guess.Mod........: Mask (?d) [1], Right Side
Guess.Queue.Base.: 1/1 (100.00%)
Guess.Queue.Mod..: 1/2 (50.00%)
Speed.#1.........:   241.6 kH/s (0.88ms) @ Accel:32 Loops:31 Thr:64 Vec:1
Speed.#2.........:   173.8 kH/s (0.48ms) @ Accel:16 Loops:15 Thr:64 Vec:1
Speed.#3.........:   231.3 kH/s (0.90ms) @ Accel:32 Loops:31 Thr:64 Vec:1
Speed.#4.........:   241.3 kH/s (0.84ms) @ Accel:32 Loops:31 Thr:64 Vec:1
Speed.#5.........:   237.2 kH/s (0.84ms) @ Accel:32 Loops:31 Thr:64 Vec:1
Speed.#6.........:   237.9 kH/s (0.85ms) @ Accel:32 Loops:31 Thr:64 Vec:1
Speed.#*.........:  1363.2 kH/s

在我们的6-GPU破解站中攻击持续了大约一个小时我们破解出了15个密码。

Hardware.Mon.#1..: Temp: 36c Fan: 16% Core: 300MHz Mem: 300MHz Bus:0
Hardware.Mon.#2..: Temp: 72c Fan: 40% Core:1340MHz Mem:2000MHz Bus:0
Hardware.Mon.#3..: Temp: 36c Fan: 16% Core: 300MHz Mem: 300MHz Bus:0
Hardware.Mon.#4..: Temp: 40c Fan: 16% Core: 300MHz Mem: 300MHz Bus:0
Hardware.Mon.#5..: Temp: 37c Fan: 16% Core: 300MHz Mem: 300MHz Bus:0
Hardware.Mon.#6..: Temp: 43c Fan: 16% Core: 300MHz Mem: 300MHz Bus:0

Started: Sun Apr 14 22:15:36 2019
Stopped: Sun Apr 14 23:13:59 2019

如果你仔细观察你会发现所有密码都是以两位数字结尾的

root@Hassium:/# results
$1$JrP2iymU$eYDr3NGC5oeC5KXzJ5sOS1:formula1
$1$orXTfMnG$aEjNn4oiGnkfiShgrmQnL.:porter12
$1$mL..oCwu$smQSyDwZ8rSp9lHMAfT0C.:selina23
$1$/Vz5N88k$VDbyNM8Wi2a8cyxoRn/Fw0:Carlos19
$1$cfu8erpl$oZqC.eoJenwKCHTtpAh8B/:christian97
$1$HXWAWpWX$D/aYEUMoYY0MbHAz9GeU10:Gerard92
$1$p3nC5zRF$JJCUomPP/OW8eSKF3T9Wq.:christian97
$1$vN.IdLhf$bgYb/A.BL3kQlVvw9F/uV1:Cristina89
$1$XUESKy2m$2HSDdaa16bUMF.4uA4C1a1:daring98
$1$YjpXADgu$SZpQEmQ4dsdMMZtQ0vgen0:fenix15
$1$R1Rei8kR$8cv/GcdJLp6ju/pNjIsUR0:triplet09
$1$7uWjhHle$V5S0g0RlNMqxwUNw4PeRy/:tennis00
$1$R7Jo6E5t$8THfWXDbNyq1JYWHHNKRf/:alexito31
$1$NWevu8U1$MpsKwxPEhWltwI7GOP9qI0:toledo00
$1$JAJl4tUI$Ir2DCRoPNFIeFgecgzAgy1:Mustang69
root@Hassium:/#

基于规则的攻击

hashcat的规则引擎加上它的GPU破解能力使它超越了许多其他同类的工具如john the ripper。

基于规则的攻击流程非常简单。首先我们选择一个单词字典作为攻击的“基点”然后选择一个包含一个或多个规则的文件。该程序将使用这些规则对字典中提供的每个单词执行转换以增强我们的攻击并最大化我们成功的机会。

所以规则基本上改变了字典中的单词。规则系统背后的关键思想是例如你可能有一个包含最常用英文名称的文件

[...]
paul
anne
john
jane
david
[...]

当然这些名称可以用作密码但将“david”直接作为密码的并不常见。人们更倾向于使用以下密码

[...]
Paul12
aNne9
999daviD
j0hn1
[...]

正如你所看到的基于字典和掩码的攻击不足以基于我们的英语字典破解这些密码。我们可以生成某种脚本来转换字典添加那些密码或类似的转换但这将是一个非常冗长的过程。

hashcat的规则引擎可以很轻松地完成这类工作。hashcat附带的“best64.rules”文件的开头如下

# nothing, reverse, case... base stuff
:
r
u
T0

## simple number append
$0
$1
$2
$3
$4
$5
$6
$7
$8
$9

## special number append
$0 $0
$0 $1
$0 $2
$1 $1
$1 $2

正如你所看到的典型的规则文件包含一个或多个规则。规则可以对单词执行不同的变换例如添加数字大写/小写字母等。

下表列出了一些规则及其含义

符号 含义 : 什么都不做 l 将所有字母转为小写 c 大写首字母小写其余字母 t 改变单词中所有字母的大小写 TN 改变单词中第N个字母的大小写 C 小写首字母大写其余部分 u 将所有字母转为大写

我们可能想在单词的开头或结尾添加一些符号我们可以使用这些符号

符号 含义 $X 在单词最后添加一个字符 ^X 在单词前面添加一个字符

使用best64规则文件和英文字典t对shado2文件进行简单攻击如下所示

hashcat -a 0 -m 500 hashes1.txt dic_eng.txt -r rules\best64.rule

结果如下

hashcat64.bin -a 0 -m 500 /hashes/shadow2 /dicts/dic_eng.txt -r /rules/best64.rule
cat hashcat.potfile

Started: Tue Apr 16 02:47:06 2019
[2K
Stopped: Tue Apr 16 07:08:17 2019

$1$JrP2iymU$eYDr3NGC5oeC5KXzJ5sOS1:formula1
$1$orXTfMnG$aEjNn4oiGnkfiShgrmQnL.:porter12
$1$u1b.Wvye$TVvT1Xnl7FeJY9kxyUB3J1:vaina
$1$7uWjhHle$V5S0g0RlNMqxwUNw4PeRy/:tennis00
$1$NWevu8U1$MpsKwxPEhWltwI7GOP9qI0:toledo00

正如你所看到的基于规则的攻击可能需要花费大量时间具体取决于你所使用的规则和字典。我们的攻击持续了6个多小时破解出了5个密码。注意这些密码是多样性的有大小写字母、结尾数字以及简单的英语单词。

密码设置建议

使用更长的字符串

使用更大的字符集字母、数字、符号

不要使用任何可能与你有关的字符作为密码或密码的一部分使用

另外你也可以选择一个你容易记住的长短语来作为密并使用密码管理器和双因素身份验证

以下图表是对我建议的补充且非常的有用

m67VZ3V.jpg!web

如果我有更多空闲的时间我会保持这篇文章的更新,因为hashcat确实是一个非常出色的程序。

*参考来源: paumunoz ,FB小编secist编译转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK