2019年9月30日星期一

动态链接(上)

动态链接(上)

首先解释一下,什么叫做动态链接,动态链接是相对于静态链接来说的,静态链接在生成可执行程序时便已经完成了链接操作,将多个目标文件链接成一个整体,链接后得程序无需依赖一起库便可运行,而动态链接则是在程序运行时才开始链接,之后再进行程序执行。

为什么需要动态链接?

相比于动态链接,静态链接有如下几个问题:
  • 浪费内存和磁盘空间:每个程序内部都保留了一份printf()等等常用的公用库函数和他们所需要得辅助数据结构,大量几乎一样得副本占用了机器内存和磁盘
  • 模块更新困难:更新任意模块都需要对整个程序进行重新的链接,而使用者们则需要下载整个程序
为了解决这两个问题,人们想到可以使用动态链接的方式,通过延迟链接的时机来解决这个问题,系统会根据程序需要,动态的将依赖库加载至内存,并且可以只保留一份依赖库的副本,不同的程序只需要有自己单独的数据额外保存即可。同时,这种方式下,程序更新以模块的方式进行,每次更新只需要发布被更新模块,然后让程序在运行时动态加载更新后模块即可。

动态链接存在的问题

  • 必须要操作系统的支持
  • 新旧模块间接口不兼容
  • 相对于静态链接有速度上的损失

动态链接的基本实现

在linux下,ELF动态链接文件被称为动态共享对象,简称共享对象,一般以.so为扩展名。Windows下动态链接文件被称为动态链接库,一般以.dll作为扩展名
由于linux下动态链接机制较为简单,接下这里分析先从Linux下开始,在linux中,常用c语言运行库glibc,他的动态链接形式的版本保存在"/lib/libc.so",整个系统只保留一份libc.so的副本,所有c语言编写的且动态链接的程序都可以在运行时使用它。
当程序被装载时,系统会先调用动态链接器将程序需要的所有动态链接库装载到进程空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作
一个示例程序如下:
在执行gcc -fPIC -shared -o Lib.so Lib.c 后,产生了一个Lib.so文件,这是一个包含了Lib.c的foobar()的共享文件对象,然后我们分别链接program1和program2
使用命令gcc -o program1 program1.c ./Lib.sogcc -o program2 program2.c ./Lib.so
在这个过程中,并不是像静态链接中那样,直接将Lib.o文件链接进入program文件,当foobar是一个定义在动态共享文件的函数时,那么符号链接器就会将这个符号引用标记为一个动态连接的符号,不对他进行地址重定位,把这个过程留到装载时再进行
链接器如何知道引用是一个静态符号还是动态符号?
这实际就是我们要用到Lib.so的原因,Lib.so中保存了完整的的符号信息,把Lib.so也作为链接的输入文件之一,链接器在解析符号时就可以知道:foobar是一个定义在Lib.so的动态文件。这样链接器就可以对foobar的引用做特殊处理,使他成为一个对动态符号的引用
我们用readelf -l Lib.so 查看Lib.so的装载属性,发现其装载首地址为00000000,可猜测共享对象的最终装载地址在编译时是不确定的,而是在装载时由装载器根据当前地址的空闲情况,动态分配一块足够大小的虚拟地址去加载共享对象

链接时/装载时重定位

因为程序的装载位置不确定,为了不使各个共享模块产生地址的冲突问题,必须使这些模块可以在任意地址装载。思路是到实际装载时由操作系统对程序中的指令和数据中对绝对地址的引用进行重定位。在静态链接中的重定位,由于程序是整个装载到内存中的,所以重定位会简单一些,那时的重定位叫做链接时重定位,而现在我们面临的是装载时重定位,在Windows中又被称为基址重置
但是,如果只是装载时重定位,只能解决我们50%的问题,为什么呢? 对于动态链接库中的可修改数据部分对不同进程有多个副本,可以使用装载时重定位的方法来解决,但是对于指令部分,由于装载时重定位的方法会修改指令,所以没办法做到同一份指令被多个进程共享。所以我们就要想一种方法,使得指令也可以在多个进程间共享。

地址无关代码

其实我们的目的很简单,希望程序模块中共享的指令部分在装载时不需要因为装载地址的改变而改变,所以最直接的思路就是:把程序中需要修改的部分拿出来和数据部分放在一起,其他的指令部分就可以保持不变的被各个进程共享,这种技术被称为地址无关代码技术
那么怎么产生地址无关代码呢?不着急,我们先分析模块中各种类型的地址引用方式,这里我们把共享对象模块中的地址引用按照是否跨模块分两类:模块内引用和模块外引用;按照不同的引用方式又可以分为指令引用和数据访问,于是也就是如下4种:
  • 模块内指令引用(函数调用,跳转等)
  • 模块内数据访问(定义的全局变量,静态变量等)
  • 模块外指令引用(函数调用,跳转等)
  • 模块外数据访问(其他模块中定义的全局变量等)
接下来,我们详细解释一下GOT的实现
当指令访问某个变量时,程序会先找到GOT,然后根据GOT中变量所对应的项找到变量的目标地址。每个变量对用一个4字节的地址,链接器在装载模块时会查找每个变量所在的地址,然后填充GOT中的各个项,以确保每个指针所指向的地址正确。由于GOT本身在数据段,所以可以在装载时进行修改,并且每个进程都可以有独立的副本,互相不受影响
那么现在有个问题,怎么找到GOT表呢,其实这时GOT就变成了模块内数据访问了,采用相对地址的方法(PC+偏移)就可了(GOT中每个地址对应的变量由编译器决定,所以程序知道第几个对应谁)
为了方便理解,可看下图:
img
图里面是一个简化的例子,这和实际编译情况不同,但适合说明GOT。 当我要从main()内去调用 shared binary 中的foo()方法的时候,在编译过程中(调用$gcc main.c 的时候)编译器会生成一个可执行文件,假设生成的可执行文件名字为a.out,在这个生成的文件中原先的main.c 中的foo()被替换为 b @GOT+0x14 ,这行代码的作用是,跳转到GOT内所记录的位置上去,地址就是GOT表的起始地址加上0x14,内容是 0x76fc6578,这个地址也就是foo() 在 shared library 的绝对位置
以上就是GOT的实现机制,但是仔细想想,我们是不是还漏了一种情况,那就是模块内的全局变量应该怎么访问,可能你第一反应是跟模块内静态变量一样不就好了,一般来说确实可以,但是存在一种特殊情况: 当一个模块引用了一个定义的共享对象的全局变量时,但是却无法判断该变量是否定义在同一个模块中。
这时,gcc编译器会把他当作是跨模块的数据访问,并且将GOT中的指针指向这个副本(可执行文件中的该变量中的副本,因为在编译中.bss段中肯定会有一个这个变量的副本)
好了,今天就先到这里,关于动态链接剩下的部分,明天接着说。

今日分享:
每一个不曾起舞的日子 都是对生命的辜负!

2019年9月29日星期日

结合攻防世界Web_php_include学习php伪协议

PHP伪协议

参考:

伪协议简介

php伪协议,事实上是其支持的协议与封装协议
支持的种类有这12种
  • file:// — 访问本地文件系统
  • http:// — 访问 HTTP(s) 网址
  • ftp:// — 访问 FTP(s) URLs
  • php:// — 访问各个输入/输出流(I/O streams)
  • zlib:// — 压缩流
  • data:// — 数据(RFC 2397)
  • glob:// — 查找匹配的文件路径模式
  • phar:// — PHP 归档
  • ssh2:// — Secure Shell 2
  • rar:// — RAR
  • ogg:// — 音频流
  • expect:// — 处理交互式的流

先整理一下关于php://的用法

php://

PHP 提供了一些杂项输入/输出(IO)流,允许访问 PHP 的输入输出流、标准输入输出和错误描述符, 内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的 过滤器

php://stdin, php://stdout 和 php://stderr

php://stdin、php://stdout 和 php://stderr 允许直接访问 PHP 进程相应的输入或者输出流。 数据流引用了复制的文件描述符,所以如果你打开php://stdin并在之后关了它, 仅是关闭了复制品,真正被引用的 STDIN 并不受影响。 推荐简单使用常量 STDIN、 STDOUT 和 STDERR 来代替手工打开这些封装器。
php://stdin是只读的,php://stdout 和 php://stderr 是只写的。

php://input

php://input 是个可以访问请求的原始数据的只读流。因为它不依赖于特定的 php.ini 指令。可以将想要执行的php代码放在post的数据中,不用以键值对的形式出现 注:enctype=”multipart/form-data” 的时候 php://input 是无效的。即php://input 不读取表单数据参数
结合一道题来看,源码为:
<!--
$user = $_GET["user"];
$file = $_GET["file"];
$pass = $_GET["pass"];

if(isset($user)&&(file_get_contents($user,'r')==="the user is admin")){
    echo "hello admin!<br>";
    include($file); //class.php
}else{
    echo "you are not admin ! ";
}
 -->

php://output

php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

php://filter

可以说这是最常使用的一个伪协议,一般可以利用进行任意文件读取。 php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
php://filter 参数
名称 描述
resource=<要过滤的数据流> 它指定了你要筛选过滤的数据流。这个参数是必须的。
read=<读链的筛选列表>该参数可选。可以设定一个或多个过滤器名称,这采用一个|或以管道符 /分隔的多个过滤器名称
write=<写链的筛选列表>该参数可选。可以设定一个或多个过滤器名称,以管道符(_
<;两个链的筛选列表>任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。
还是结合上面那道题:
这里写图片描述

过滤器

过滤器有很多种,有字符串过滤器、转换过滤器、压缩过滤器、加密过滤器,这里只说一种转换过滤器中的一种
convert.base64-encode & convert.base64-decode
convert.base64-encode和convert.base64-decode使用这两个过滤器等同于分别用 base64_encode()和 base64_decode()函数处理所有的流数据。 convert.base64-encode支持以一个关联数组给出的参数。如果给出了line-length,base64 输出将被用 line-length个字符为长度而截成块。如果给出了* line-break-chars*,每块将被用给出的字符隔开。这些参数的效果和用 base64_encode()再加上 chunk_split()相同。
为什么要base64编码后再读取呢?
这涉及到include()的特性,如果include的文件不是txt格式会直接输出,如果包含<?php ?>这种格式在,则会执行相关代码。
综上,如果不加编码的话,引入的文件若符合php格式则会自动执行,将结果包含在流中,如果想要读取运行php文件的源码,就可以先base64编码,再传入include函数,这样就不会被认为是php文件,不会执行,会输出文件的base64编码,再解码即可。

file://

file://与php:filter类似,访问本地文件,但是只能传入绝对路径
img

data

data 与input 类似,都是用户可以控制传入的php代码,格式为:data: text/plain,<?php 执行内容 ?>
或者是:
data:text/plain;(注意是分号不是逗号,与前面不同)base64,编码后的php代码
img
有一点要注意的问题,base64编码后的加号和等号要手动的url编码,否则无法识别

http

include中也可以传入外部链接,采用http://,如图img

一道实例:

来自攻防世界 Web_php_include
访问URL,直接出现源码:
<?php
show_source(__FILE__);
echo $_GET['hello'];
$page=$_GET['page'];
while (strstr($page, "php://")) {
    $page=str_replace("php://", "", $page);
}
include($page);
?>
看到源码后,分析一下,过滤了php://有如下3种思路:
  1. data来代替php://
  2. 利用大小写绕过过滤Php://,直接php://input 代码执行

  3. 利用http://127.0.0.1/index.php?的方式来绕过过滤,这种方法比较巧妙,但是也比较巧合,需要该应用可直接在本地根目录去访问/index.php才能达到这个效果http://192.168.100.161:50281/?page=http://127.0.0.1/index.php/?hello=%3C?system(%22ls%22);?%3Eimg
今日分享:
高效的时间管理!!

嘿嘿,我自己说的,今天做的还不错,稍微表扬一下自己~

2019年9月28日星期六

C语言-----指针篇

C语言---指针经典面试题总结与解析



1. char * const p;
  char const * p
  const char *p

  上述三个有什么区别?

  char* const p; //常量指针,p的值不可以修改
  char const* p;//指向常量的指针,指向的常量值不可以改
  const char* p; //和char const *p一样

解析:指向const的指针(指针指向的内容不能被修改)const关健字总是出现在*的左边而const指针(指针本身不能被修改)const关健字总是出现在*的右边,那不用说两个const中间加个*肯定是指针本身和它指向的内容都是不能被改变的。

2.  char str1[] = "abc";
  char str2[] = "abc";

  const char str3[] = "abc";
  const char str4[] = "abc";

  const char *str5 = "abc";
  const char *str6 = "abc";

  char *str7 = "abc";
  char *str8 = "abc";

  cout << ( str1 == str2 ) << endl;
  cout << ( str3 == str4 ) << endl;
  cout << ( str5 == str6 ) << endl;

  cout << ( str7 == str8 ) << endl;

   打印结果是什么?


解答:结果是:0 0 1 1
str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域,编译时会被替换成相应的地址

3.
int main()
  {
   int a[5]={1,2,3,4,5};
   int *ptr=(int *)(&a+1);
   printf("%d,%d",*(a+1),*(ptr-1));
  }

   输出结果是什么?


  答案:输出:2,5

  *(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
  &a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int)
       作为对比a+1代表第二个元素,a是一个int型指针,而&a是一个int[5]型的指针
  int *ptr=(int *)(&a+1);
  则ptr实际是&(a[5]),也就是a+5

4.  int main()
 {
     int a[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
     int *ptr1 = (int *)(&a + 1);
     int *ptr2 = (int *)(*(a + 1));
     printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));


     system("pause");
     return 0;
}
这道题考察了对二维数组的理解,我们知道数组在内存中是线性连续存放的,所以二维数组也是如此。
&a+1:与一维数组同理,所以此时+1指向了下一个数组的起始位置,*(ptr1-1)即就是该数组的最后一个元素10。
(a+1):指向下一个元素,这里需要我们将二维数组看作一维数组(该数组有两个元素,每个元素是一个一维数组),所以a此时表示第一个元素,+1指向下一个元素即就是第二个一维数组的起始,*(ptr2-1)即就是上一个一维数组的最后一个元素5;

5.    char* s="AAA";
  printf("%s",s);
  s[0]='B';
  printf("%s",s);

  有什么错?

  答案:
"AAA"是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题。
cosnt char* s="AAA";
然后又因为是常量,所以对是s[0]的赋值操作是不合法的。


6.    有以下表达式:

  int a=248; b=4;
  int const c=21;
  const int *d=&a;
  int *const e=&b;
  int const *f const =&a;

  请问下列表达式哪些会被编译器禁止?为什么?
  *c=32;d=&b;*d=43;e=34;e=&a;f=0x321f;


  答案:
       *c 这是个什么东东,禁止
  *d 说了是const, 禁止
  e = &a 说了是const 禁止
  const *f const =&a; 禁止

7. int MyStrlen(char str[])

{

    return (int)(sizeof(str)-1);

}

呵呵...咱们上当过一次..这个当然也是不对的...不错...这个函数是错的...为什么呢?

首先,可以告诉你,无论何时,返回的总是3...额...是不是很奇怪,为什么不是数组长度呢?str不是char数组指针么?不错...确实是数组的指针,但是,当用函数传递的是数组指针且接收的也是数组的时候就自动退化为指针了(可以用引用的方法来避免指针的退化),而指针的长度是4,你减去1了自然就是3了(32位系统).但是如果按照下面代码就可以得到正常的值.

char str[]="hello world";

int len=sizeof(str)-1;   //记得减1哦,最后有'\0'结尾

cout<<len;

这样输出的是正常值,也就是你所希望的11;


OK,今天就这样了,这是我搜集到的一些关于C语言指针部分的面试题,还有答案,如果有哪里理解的不对敬请指出~

今日分享:
人生天地之间,若白驹过隙,忽然而已。
这是庄子的一句话,其实仔细想想,人生的前18年不受自己控制,人生的后10年自己没能力控制,在加上各种琐事,本就没有太多时间留下,如果不能很好地控制自己的时间,可能真就会混混沌沌的过完一生吧,现在已经大二了,时间其实非常的紧迫啊,到现在连个挖洞的能力都没有。。。

2019年9月27日星期五

FTP流量审计

FTP流量审计

wireshark对于这个FTP的流量包会解析成三种协议来显示:TCP,FTP和FTP-DATA。wireshark把带有FTP控制信息的解析为FTP,对于至传输了文件内容或者命令执行结果的FTP数据包解析称FTP-DATA。
从FTP开始建立,先是3个包,完成三次握手过程,当服务器端准备好了,会主动发送response code:220 代表:Service ready for new user,意思是服务器端做好了用户登录的准备。
上图打开的数据包是客户端向服务端发送的命令,FTP命令传输的格式均为:command arg命令 参数。可以看到客户端发送了USER ftp,使用用户名ftp登录这台FTP服务器,同理下面发送密码也是如此。
客户端发送请求以FTP用户名登录后,服务端回复,要求密码。返回码331,用户是ftp,要求密码
注意:FTP服务无论这个用户是否存在,都会要求输入密码,客户端发送密码后,认证成功,服务器端回复230用户登录成功,并返回当前目录/在FTP根目录
PASV:
为了解决服务器发起到客户的连接的问题(防火墙问题),人们开发了一种不同的FTP连接方式。这就是所谓的被动方式,或者叫做PASV,当客户端通知服务器它处于被动模式时才启用。
在被动方式FTP中,命令连接和数据连接都由客户端,这样就可以解决从服务器到客户端的数据端口的入方向连接被防火墙过滤掉的问题。当开启一个FTP连接时,客户端打开两个任意的非特权本地端口(N >; 1024和N+1)。第一个端口连接服务器的21端口,但与主动方式的FTP不同,客户端不会提交PORT命令并允许服务器来回连它的数据端口,而是提交PASV命令。这样做的结果是服务器会开启一个任意的非特权端口(P >; 1024),并发送PORT P命令给客户端。然后客户端发起从本地端口N+1到服务器的端口P的连接用来传送数据。
227 FTP Response code
This is the response given by the server to the PASV command. It indicates that the server is ready for the client to connect to it for the purpose of establishing a data connection. The format of this response is important because the client software must be capable of parsing out the connection information it contains. The values h1 to h4 are the IP addresses that the server is listening on. The values p1 to p2 are used to calculate the port that the server is listening on using the following formula: PASV port = (p1 * 256) + p2.
Example response
227 Entering Passive Mode (h1, h2, h3, h4, p1, p2)

RETR FTP command
A client issues the RETR command after successfully establishing a data connection when it wishes to download a copy of a file on the server. The client provides the file name it wishes to download along with the RETR command. The server will send a copy of the file to the client. This command does not affect the contents of the server's copy of the file.

150 FTP Response code
The server may use this reply code in response to a command initiating a file transfer before establishing the data connection over which the file transfer will occur. A PASV or PORT command should have been issued prior to the command receiving the 150 response code. After sending or receiving this response, the server/client may begin sending data over the data connection.

XSS速查表

今天继续XSS的进一步学习,以下内容转载自Freebuf上的文章,原文章来自OWASP的xss备忘录 1.介绍 这篇文章的主要目的是给专业安全测试人员提供一份跨站脚本漏洞检测指南。文章的初始内容是由RSnake提供给 OWASP,内容基于他的XSS备忘录: http://...