main_dahuan01
其他类型
这个是大欢老师给我的一道金科师傅的题目,让我做着玩,非常有趣。然后因为这个是前辈写的小玩意,我可能在写wp的时候会骂脏话,不过我会用*号替代大部分的。
先checksec一下看一下大概吧。

一道保护全开的题目,但是一般这种保护全开的题目要么很简单,要么就是难到完全做不了。不过大概率是前者。
大概玩了一下这个程序,是一个类似于银行系统,可以注册登录,存钱取钱。
首先打开IDA之后,要对代码逐步分析。特别是把里面的变量名改一下。就从register存钱的这个函数开始:

根据上下文,读懂代码之后很快就可以把大部分变量名给改好了,然后我们再去看有后门的函数menu,再对里面的一些变量名重新命名,这下我们大概就能看懂代码了。


我们了解一下,后门函数的触发条件是当uid==11451419的时候,就会触发。然后通过reg函数和save函数,大概可以看出来,每次我注册的的时候,uid就会++。也就是当我注册第11451419个账户的时候,我就可以很自然的获得管理员权限。
所以第一种解决方案就是注册11451419个账户。很显然这个非常的愚蠢而且不实际…不过我们可以试试。
第一种解决方案:暴力遍历!
我们先看一下正常注册一个账号的流程:

根据获取和我要输出的内容,构造一个py文件即可。

1 | from pwn import* |
好了这个方法有用是有用,但是1145万次操作,大概要花费一个多月的时间。我们有时间还是别折腾这傻逼代码了。

讲个好消息,我生成到3800多个账户的时候我就后悔了,然后删除这些傻逼文件花了我四分钟。
我是真不理解怎么会有**这么写代码的,**的一个用户生成一条文件真的是太逆天了。
我们来试试第二种方法吧。。。
第二种解决方案:构造巧妙payload
对吗,这才是我们pwn爷优雅解决题目的办法。
我们再次回到题目,除了save,他还有一个读取文件(特别是读取金额)的部分,我们来看看这部分的代码。

在这个read_f中,不难发现里面有一个小漏洞:读取password后,会读取username_len,而接下来的username会读取username_len个长度的内容。
大概的意思就是,如果我的usernamel_len的长度是100,他就会读取100个数据。而username_len是怎么构造出来的呢?

在程序中使用read函数的时候,read函数会有一个int类型的返回值,这个返回值是我输入了多少长度就返回多少长度。于是乎我们就可以画下面这张正常运行时候的图片:

当uid在0的情况下,我可以构造这么一个非常巧妙的字符串存在文件里面等待读取:

这边有几个小知识点,第一个是fwrite和fread函数,它的参数大概是这样的:
1 | size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) |
其中第一个参数当然是我塞入进去要写出字符串啦,第二个是每个元素的大小,第三个是给我写入或者读出的字符串预留的空间,最后一个是程序流,也就是我输入的文本就是要从stream里面获取的。
而每次塞入的长度都是什么6ull,4ull的,所以塞入的时候,值后面一定会有”\0”这种结尾符号,哪怕我存入数字1,到机器里面也是0x00000001,最后被读取出来的时候就只有0x01和0x00两个有效信息了,一个告诉我没有更多的值了(0x00的作用),还有一个告诉我值是多少(0x01的作用)如图所示:

所以我只要创建第一个账户,也就是uid为0的账户,然后在注册username的时候输入一个”字符串+\0+字符串”的构造塞进去,让他把uid应该读取到的第一个数字”0”变成多出来的username末尾的”\0”(这里可能有点不准确,不过无伤大雅,因为哪怕这个0没有变成\0,也会紧随后面的数字变成011451419,还是11451419)。随后程序在读取所谓的字符串后面的内容的时候,就会把uid的内容往后读一格,也就变成了money_save里面的内容。
上述有两种可能的情况,我觉得第二种应该比较对,我也懒得验证了,反正代码能跑就行。不过经过我的思考之后,因为会读取username_len个长度,也就是那个0是被留在后面的。所以后面的数字也就是uid会变成011451419。
然后可能就会有人问”不是,我这么构造,uid不是直接就变成11451419了嘛?”你说的没错,不过还有一个小错误。还记得我删掉三千多条文件嘛,这个臭********,最***的部分就在这里,如下图所示:

看到这个file_uid了嘛,没错,一开始读取uid的时候,读取的是文件的名字…文件创建出来就是叫0,而里面存的uid值是11451419。exp如下:
1 | from pwn import* |











