攻防世界pwn新手训练
小白刚开始接触pwn,做点新手训练了解一下pwn是干啥的。一开始啥漏洞都不知道,很多都是看别人的wp才知道要干嘛,比如才知道原来printf函数也是有漏洞的。 把这些题的思路和做法记录下来,不然我这鱼的记忆肯定过几天就又忘了。
get_shell
题目描述:运行就能拿到shell呢,真的
如题,nc连接,连接直接就是shell,直接cat flag就可以了。
CGfsb
题目描述:菜鸡面对着pringf发愁,他不知道prinf除了输出还有什么作用
首先看源码:

可以看到第23行有个printf,那么就可以知道这个题考查格式化字符串漏洞,关于这个漏洞可以看我的这篇文章《一篇文章搞懂格式化字符串漏洞》。
源码要求pwnme为8,那么就是要我们修改pwnme的值为8,看pwnme的位置:

pwnme在_bss端,说明它是一个全局变量,那我们要修改它,就要利用格式化字符串漏洞中的%n,所以首先找偏移量。

61616161就是我们要找的(a的十六进制ASCII码),可以看到偏移量是10。
我们的思路就是把这个地方改成pwnme的地址(0x0804A068),然后用%n对pwnme赋值为8(printf输出8个字符)。
构造exp:
1 | from pwn import * |
得到flag:

when_did_you_born
题目描述:只要知道你的年龄就能获得flag,但菜鸡发现无论如何输入都不正确,怎么办
首先分析源码:

这是个栈溢出的题,主要是要让v5的值为1926,我们可以利用的值是v4,看一下这两个值的位置:

我们可以看到v4占了8个字节,下面就是v5,所以我们要让v4溢出来修改v5为1926。
构造exp:
1 | from pwn import * |
得到flag:

hello_pwn
题目描述:pwn!,segment fault!菜鸡陷入了深思
分析源码:

第9行执行的就是cat flag,所以我们就要dword_60106C为1853186401,我们可以修改的是unk_601068,看一下它们的位置:

和上一题一样这也是个栈溢出,直接写exp了:
1 | from pwn import * |
得到flag:

level0
题目描述:菜鸡了解了什么是溢出,他相信自己能得到shell
先看源码:

main函数没什么好看的,看vulnerable_function():

好像没啥啊,但这时候我注意到其他的函数:

看到了一个callsystem函数,地址为0x400596。看一看:

哦吼,要是能让程序执行这个函数,那就很不错了。
所以我们要在函数调用返回的时候修改eip,进行rop攻击。
看一下buf的位置,顺便看一下函数返回的位置,ida告诉我们r代表的就是return address:


可以看到buf和r之间偏移量为0x88。
exp如下:
1 | from pwn import * |
获取到shell,得到flag:

level2
题目描述:菜鸡请教大神如何获得flag,大神告诉他‘使用`面向返回的编程`(ROP)就可以了’
先看源码:

和上一题一样,主函数还是啥都没有,看别的函数:

这次我们看到有程序中直接就有系统调用的函数system(),那如果里面的参数是“/bin/sh”就好了,找一下程序中有没有我们想要的“/bin/sh”。搜索方式为IDA里依次点击Search -> text -> 输入你要搜索的字符串 -> ok

找到了,那么我们思路是把函数返回的地址改为上一行的system(),地址为0x0804845c,再把参数变成我们需要的“/bin/sh”。
看一下偏移量:


buf到r的距离是0x88+4。
写exp:
1 | from pwn import * |
拿到shell,得到flag:

guess_num
题目描述:菜鸡在玩一个猜数字的游戏,但他无论如何都银不了,你能帮助他么
先看源码:

发现要猜对十次数字就可以拿到flag(sub_C3E就是cat flag)。而数字是随机数。这里有一个小知识点,实际上所谓的“随机”是“伪随机”,是根据一个数(我们可以称它为种子,也就是代码中的seed)为基准以某个递推公式推算出来的一系列数。所以说只要知道了seed,那么生成的数我们也就能知道了。
因此,思路是要能知道seed的值,我们看第19行的seed是什么:

看来我们是不可能知道seed是什么了,那就只能我们自己修改seed,同样是栈溢出,我们可以控制的是v9,看一下v9:

v9就是var_30,我们可以看到偏移量为0x20。所以我们修改seed的值,然后我们就知道了每次的数是什么,猜对10次得到flag。
写exp:
1 | from pwn import * |
得到flag:

cgpwn2
题目描述:菜鸡认为自己需要一个字符串
首先分析源码:

啥也没有,看一下hello函数:

用户可以控制的是name和s,这应该也是栈溢出。观察到程序中有个pwn函数(pwn中的system地址是0x804855A),看一下:

参数不是/bin/sh,找了一下程序里也没有。
看一下name,发现是全局变量:

那就是把name变成/bin/sh然后直接传到system函数里。
exp如下:
1 | from pwn import * |
取得shell,得到flag:

string
题目描述:菜鸡遇到了Dragon,有一位巫师可以帮助他逃离危险,但似乎需要一些要求
先看主函数:

sub_400D72:

sub_400BB9:

sub_400CA6:

我们分析源码,首先看到sub_400CA6的第17行,
void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
记住只要看见这种句子,就知道是把v1强制转化成一个函数指针,然后调用这个函数,那么我们就可以利用前面的read,把我们想执行的命令(shellcode)写入v1中,程序就可以执行我们的shellcode。
那么想要能输入v1,我们就要让第12句的if语句成真,也就是*a1 == a1[1]。
往上看发现a1是函数传入的参数,再往回看,一直追溯到主函数,发现这个a1实际上是v4,而v4和v3相等,也就是说现在*a1是68,a1[1]是85,我们的目标变成要让*a1为85。
再看到sub_400BB9,发现在第23行存在格式化字符串漏洞,那么就很明确了,用%n赋值。那么*a1的地址是多少呢?发现secret就是*a1的地址。
所以攻击思路如下:
通过格式化字符串漏洞赋值*a1为85,使if条件成真,执行我们传入的shellcode拿到shell。
构造exp:
1 | from pwn import * |
int_overflow
题目描述:菜鸡感觉这题似乎没有办法溢出,真的么?
先看一下保护情况:

看主函数:

没什么东西,看login():

好像也没啥东西,接着看check_passwd():

strlen存在溢出漏洞,因为32位程序中strlen把结果放在al中,而al是八位的,所以能存的最大值为255(1111 1111),如果超过255,就会导致整形溢出。例如261(1 0000 0101)最终输出的结果就是(0000 0101)。
接着看程序,发现有一个what_is_this函数:

那么就是要通过strlen构造栈溢出让程序返回到这个函数,但是要求输入的s长度在(3,8]之间。所以通过整数溢出来越过这个限制。
看一下s在内存中的位置:

构造exp如下:
1 | from pwn import * |
得到flag:

level3
题目描述:libc!libc!这次没有system,你能帮菜鸡解决这个难题么?
先看源码:


首先想到read构造栈溢出返回system地址,参数传入“/bin/sh”地址。但是程序中没有system和/bin/sh。
虽然程序中没有直接给出,但是我们可以通过return2libc攻击间接得到。因为几乎所有程序都会运行libc库函数,而libc库中就有system和/bin/sh,libc库中的函数之间的偏移量都是固定的,只要知道了当前程序运行的libc版本和一个libc函数运行时在内存中的绝对地址(例如read或write),就可以推出system和/bin/sh的地址,也就可以通过栈溢出返回到system的地址。
那么read或write的绝对地址是啥?为啥地址不是固定的?可以看看这篇文章:《聊聊Linux动态链接中的PLT和GOT(1)——何谓PLT与GOT》,大佬写的非常清楚。
所以攻击思路就是先利用程序的write函数输出read运行时候的绝对地址,再通过read找出libc的版本,然后根据偏移量找出system和/bin/sh的地址,再调用vulnerable_function进行栈溢出返回system()。
exp如下:
1 | from pwn import * |
运行exp,如图,由于程序不确定libc版本,需要手动选择,这里我们选择libc版本选0(我也不知道选啥所以选第一个),得到shell,取得flag。
