pwnable.kr_思路

写在前面

学二进制、pwn,pwnable.kr是一个必须要刷的网站,所以开一篇文章专门记录自己的刷题历程

其实网上很多都有,但我这里主要记录各题的思路,不写详细的解法,也主要用于自己的归纳总结,do it yourself,才能学到更多

对我所说的思路有不了解或有不同的看法的,欢迎email我 lin.giglf@gmail.com 一起讨论

不定期更新


Toddler’s Bottle

这个章节做完有点久了。。然后才来补思路,如果有什么错误欢迎联系我 ↑

fd

知道linux 下stdin=0 stdout=1 stderr=2

collision

学会怎么用python构造非ascii字符的输入

bof

一个简单的溢出,覆盖相邻变量的值

flag

这其实是一道逆向题。。直接逆就好

passcode

scanf没有加取地址符,debug发现能控制passcode1的值,然后可以通过覆写plt达到目的

random

随机数种子固定,每次生成随机数一样

input

这题挺有意思的,写程序执行实现每个阶段的要求,需要了解一些socket相关的知识还有Linux软连接,很有趣!

leg

考察arm汇编的阅读,要知道一点,pc的值为当前地址+8

mistake

打开password给fd赋值时没有用括号,优先级问题从读文件变成了stdin

shellshock

给了一个特定版本的bash,这是一个cve,(CVE-2014-6271),bash是不会继承定义的函数,但是会继承定义的环境变量。而在该版本及以下的bash当中,对环境变量函数的解析在函数定义完毕后并不会停止。便导致了这个漏洞。

coin1

通过二分法找出那枚硬币,关键其实是怎么编写与服务器通信的脚本。It’s a good way to learn pwntools.

blackjack

阅读代码,发现输入bet money时能溢出,You just need to win one time.

lotto

看检测代码部分,双层循环,则只要其中一位能在random中找到就行了

cmd1

程序过滤了sh, tmp, flag

于是/bin/cat fla* ,可以用通配符来解决过滤的flag

cmd2

过滤了/ ,还删除了对应的环境变量,无法直接cat了,flag依旧可以通过通配符解决。

网上搜wp看到了很多种思路

  1. cd 到根目录,通过$(pwd)构造/
  2. 把命令编码成八进制,通过echo解决的

uaf

use after free。在调用的时候,没有检测是否对象已经free,free了后的内存可以任意写,此时写入getshell的地址,则再次调用时就会调用到getshell的函数。

codemap

让学习的是ida的IDC脚本编写,当然可以选择使用python,已经提示了注意某个地址寄存器的值,只要在循环每次读入寄存器的值,找出符合的那几个数即可。

memcpy

这题关键是要让程序跑通,主要就是那段内嵌汇编要正常运作不能崩。

内嵌汇编中,movntps是对xmm进行操作的,xmm是128bits寄存器,16字节,则要求空间要16字节对齐。每次输入满足对齐规则的空间大小。

asm

考shellcode的编写,嗯,pwntools的确是个好东西

链表中,在unlink一个单位时,很容易出问题

例如,有一链表 A <-> B <-> C

unlink(B)时,

1
2
3
4
bk = B->bk;
fd = B->fd;
fd->bk = bk;
bk->fd = fd;

如果把B->bk、B->fd改写了,则可以改写任意地址的内容。

例如,假设bk指针在结构体第8字节,改写fd = B->fd = address-8

fd->bk = address-8+8 = bk = B->bk

可以把address地址改写,即可以改写任意地址的内容

blukat

password是没有权限去读的,但是在运行的blukat有读的权限并且我们可以运行blukat,在调试中直接就可以看到读到buf中的password内容,而且这个password内容还伪装了一把permission denied。。

horcruxes

说明需要rop,很容易找到溢出点,并且没开canary,第一想法是溢出gets直接跳到open flag那里就可以得到flag了。但是试了好一会都没成功,后来才发现在ropme的函数里地址都包含0x0A,即\n ,gets是会截断的。因此只能反复跳到ABCDEFG并计算出sum值最后再call ropme去满足条件获取flag

Rookiss

brain fuck

这题属于GOT表替换,brainfuck的指令能随意移动指针,可以移动到GOT表做函数替换

md5 calculator

这题的关键点,找到溢出点,base64编码跟原码长度为4:3,存在溢出

但开了Canary保护,能看到在开头验证码生成时用了对应的cookie,所以可以通过验证码反推出cookie,绕过Canary保护

simple login

溢出限制了长度,只能溢出到ebp,ret处理不了,但这里在auth函数的ret后,紧接着是main函数的ret,理清思路后可以利用

otp

这个题。。。没接触过根本想不到,ulimite -f 0限制文件大小,使得密码为0

ascii_easy

这题是我花时间最长的一题了,上网搜了wp,然后发现题目改过,虽然思路差不多,就是构造ascii的shellcode,但一直也没成功。后来github搜到一个解法,发现是函数调用问题,对于系统调用的跟通常的函数调用有点不一样。

例如call execve,可以参考 http://shell-storm.org/blog/Return-Oriented-Programming-and-ROPgadget-tool/

然后就是,想方设法构造在ascii范围内的shellcode,并且可以通过环境变量的方式做一个/bin/sh的别名链接,使得我们不需要构造出/bin/sh的地址。但实际想要构造也能成功,通过地址的变换,再找几个rop_chain实现

tiny_easy

一个只包含几条指令的binary,简单调试可得,最后call的是argv[0],并且栈可执行,但是有aslr

通常,直接启动执行argv[0]是程序执行路径。那么这里有个知识点,可以通过execl进行启动,l参数启动的时候argv[0]是可以自定义的,于是我们可以通过argv往栈上写shellcode,但是call的地址不确定,我们可以通过栈喷的方式,在shellcode前填充大量的slide code(例如nop),于是便可以跑到shellcode上getshell。

**技巧:**调试通过exec系列启动的程序,gdb中set follow-fork-mode child可令调试程序执行跟进到子进程,catch exec可以挂接gdb到通过exec启动的程序上,在该程序上下断点。

fsb

简单分析即可知道存在format string (格式化字符串)漏洞

emmm printf不仅可以用来泄漏地址,还可以用来写入地址,printf包含一个%n的占位符,这个参数的作用是

不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。

于是可以通过这个这个方式向栈上写入地址,而因为key的地址是可以获得的(通过printf泄漏或者直接获取都可以,因为key位于.bss段 并且程序没开启aslr),便可以向栈上写入key的地址

接下来便可以用同样方法覆写key的地址或者直接泄漏key的值了

dragon

RPG游戏,试验了一下怎么都不可能正常的打败dragon的,但是我们发现dragon每一轮都会给自己加血,并且dragon的HP储存是个char,那么就恰好存在一个溢出,溢出到负数便可以触发打败dragon

那么怎么利用?dragon被打败后会free掉,同时malloc一个同样大小的buf,而后面居然还调用free掉的dragon里的函数,这就是一个UAF,复写dragon中的func到system(“/bin/sh”)就OK了

syscall

linux kernel pwn,实现了一个kernel module,而这个kernel module包含一个bug,任意地址写。

因为在kernel对任意地址写可以达到进行任意的系统调用,例如通过commit_creds(prepare_kernel_cred(0))进行提权。通过 cat /proc/kallsyms便可以查看两个系统调用的符号地址。

最后有一个需要绕过的点,kernel module实现的系统调用会对小写字符大写,而符号地址当中便包含一个0x6c,我用了别人wp的一个方法,向该地址前面的内存填充nop指令,然后跳转到0x60之前的地址,从而绕过该限制。另外也有师傅通过汇编实现了真正的全地址任意写,看了一下网上的wp,简直就是各显神通。

最后提权后直接cat /root/flag就OK了

Crypto1

密码学题目,关于AES128CBC的使用漏洞,题目会给出由个人提供输入AES128_CBC加密结果,key、IV不知

因为在CBC模式下,16byte的分组有,当前n * 16 byte的信息前缀相同时,加密出来的前n * 16也是相等的

这个设计到分组密码与CBC模式的弱点

因此当有段不明信息secretmessage,可以通过比较

AES128Encode(---------------s)

AES128Encode(---------------*)

构造最后一位*,进行爆破比对,从而得出最终明文

echo1

超大的栈溢出,什么保护都没有,直接往栈上写shellcode然后再通过bss作为跳板jmp rsp就可以getshell

要注意一点就是pwntools写shellcode时,shellcraft.sh是32位的shellcraft.amd64.sh是64位的

echo2

shellcode不能用shellcraft的了,因为无论哪种利用方式,输入长度都限制在24以下,所以可以从exploitdb上找shellcode,用fsb泄漏出shellcode地址,然后通过uaf利用调用shellcode

这题实际也很简单,感觉自己花了很多时间是在找bug和复习fsb上了T_T

rsa_calculator

需要比较耐心的逆向才能找到bug。。。
这题关键漏洞都在rsa_decrypt函数里,包含有fmtstr和stack overflow
程序只开了canary
所以,思路是通过fmtstr leak canary,然后通过stack overflow写ret
虽然binary提供了system函数,但是最简单还是直接用shellcode了
这题需要注意的点是decrypt的时候几个buffer 从hex to byte的操作
由于1024的buf overflow到了520的buf,520的buf再stack overflow
而520的buf是由1024的buf hex2byte生成的,所以overflow到520buf的部分需要进行两次encode(‘hex’)的操作,然后就可以构造溢出data了

note

这题我一开始的思路不太对,mmap分配出来的空间是rwx的,并且没有canary保护,也没有ASLR,那么一个比较明显的思路为mmap到栈上,栈地址是可预测的,修改掉ret指向shellcode,但是mmap的地址是通过/dev/random随机生成的,想要命中栈空间几率比较小。这是因为我没有发现另外一个漏洞。

这题还存在一个漏洞,select_menu函数递归调用,这会导致栈一直在增长,随着栈在增涨,要想mmap到栈空间几率就会随着循环次数增大而变大。当程序退出的时候,会依次从最内层函数返回,因此这样mmap命中栈空间后,只需要把栈上填上大量的shellcode地址就行了。

alloca

很容易发现size可以小于0,但是怎么利用呢?
一个思路,先寻找有哪些操作可以往栈上写
当发现只有g_canary可以时,去找写到栈上的地方与ebp-4的关系
能够列出公式,最后求解出size
最后通过env上填充callme的地址,跳转过去,多次启动一定几率下可以getshell

loveletter

protect函数存在溢出,只需要把后面栈上v6覆盖成0,那么第二次memcpy将会把用户输入拷贝到loveletter的0起始位置

,这种情况下就不用再用分号等吧shell语句分割开了,直接cat flag

这题很简单,甚至都不用调试就出来了

Grotesque

rootkit

这题,一开始想了很久从shell命令上时候有什么组合可以绕过syscall的hook呢?但实际上跟我想的不同,这题完全有更方便的做法,因为我们本来就是root权限,不仅可以insmod,也可以做很多别的东西。

实际上这里有两种做法,1. 自己编写LKM,把系统调用表中的sys_open改掉。2. 把现成的rootkit.ko patch一下。

我这里用的是第二种。一开始参考了网上的做法,把flag patch成flaa之类的,再重新insmod一下, 那么就不会拦截到flag文件名了。但是,我忽视了其中一点,就是在这之前系统调用表已经被修改过了。这样patch的效果只会让flagflaa都被拦截

简单来说,我们需要把LKM中的sys_open变量修改为系统原来的sys_open

而其中一个问题便是地址的获取,这里便不明说了,提示一下,跟获取syscall_table是同样原理

最后,只需要三行,无需写代码,便能获取flag

1
2
3
cp rootkit.ko rootktt.ko
sed -i -e "**************" rootktt.ko
printf '********' | dd of=rootktt.ko bs=1 seek=*** count=*** conv=notrunc

wtf

这题bug也很容易找,只要输入长度是负数,就能栈溢出。

但是这题涉及到一个知识点,因为wtf.py这个交互脚本只会向程序发送一次payload,而题目则需要接受多次输入。

我尝试过-1\n-1\x0d,都不管用

这里涉及到知识点 The buffer size of scanf is 4096 bytes.这个说法其实还是有点问题,详细可以查看下面链接

I/O queue of the terminal is set to 4096 bytes

扩展阅读:c - How to make scanf to read more than 4095 characters given as input? - Stack Overflow

Hacker’s Secret

作者

giglf

发布于

2017-10-25

更新于

2026-05-12

许可协议

评论