置顶传送门


旧坑未填,又挖新坑

pwnable

之后大概会更新这个叫做pwnable的专题,顺便记录我在pwn这条路上的学习历程

学习的网站是pwnable.kr,你可能需要翻墙

P.S. 并没有严格按网站上的顺序做题


Question1:fd

Mommy! what is a file descriptor in Linux?

* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link: https://www.youtube.com/watch?v=blAxTfcW9VU

ssh fd@pwnable.kr -p2222 (pw:guest)

Writeup:

首先连接ssh,查看目录发现一份C语言代码

pwn1

简单的解释一下,这个程序输入的第一个参数仅接受数字输入,将输入的第一个参数的值减去0x1234后将其作为read函数的第一个参数,此条件下read函数将接受输入的第二个参数。然后是一个关于第二个参数的字符串匹配,匹配成功就拿到flag。

查到fd的文档

filedes

可以看到当fd=0时,read函数可以读stdin里的数据,所以输入的第一个参数就应该是0x1234+0=4660.考虑到后面的字符串匹配,第二个参数输入”LETMEWIN”。成功拿到flag。

pwn1_2


Question2:collision

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

Writeup:

看到MD5碰撞吓了一跳(明明才第二题,所以其实并没有)。连接ssh,查看目录发现一份C语言代码

col2

整个程序很清晰,需要输入一个长度为20的字符串,将输入字符串转换为int传给check_password函数做运算,如果函数返回值=0x21DD09EC则给flag。

观察check_password函数发现,它把参数以四个字节的长度(int长度为4字节)分割后再全部相加返回unsigned long的总和(unsigned long在32/64位环境对应4/8字节,从题目看应该是4字节)。因为输入的字符串是20字节,所以就是把字符串分成5份然后加起来。

在加法运算中我们知道,如果不考虑进位的话,aa+bb=(a+b)(a+b),比如11+22=33,这对任何进制都是有效的。因为我们只能以字节为基本单位来输入,所以我们以字节为单位来分析,结果res中的第i个字节的值其实等于被分割后的输入字符串的第i个字符的值的总和sum-n*0xff左右(考虑到进位)。因为目标机是小端序,所以这个对应顺序要反一转。举个例子,把check_password函数写在本地编译执行,传入字符串abcdabcdabcdabcdabcd,输出结果0xf5f0ebe5,其中0xe5=0x61(a)*5-0xff,0xeb=0x62(b)*5-0xff-1(前一项进位),0xf0=0x63(c)*5-0xff-1(前一项进位),0xf5=0x64(d)*5-0xff-1(前一项进位)。

所以可以据此对0x21DD09EC进行逐字节碰撞,翻出ascii码表根据sum[i]-n*0xff≈res[5-i] (i=1,2,3,4)确定大概范围,然后再进行正负2以内的微调抵消进位就可以了。

最后我构造的字符串为”dh_9bh_:bh_9bh_:bh_:”,拿到flag。

col3


Question3:bof

Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?

Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c

Running at : nc pwnable.kr 9000


Writeup:

题目说的很清楚这是一个简单粗暴的缓冲区溢出(然而还不是花了一堆时间来装edb,这篇文章隔壁有安装心得)
这次给了一份C代码和一个elf程序,给的代码如下

很明确我们应该通过这个无限制的gets()来把key的值从0xdeadbeef覆写为0xcafebabe
为了查看栈的状态我们把它扔进edb里//edb安装传送门
bof2
这里我随便输了点东西进去(1234…),因为它的变量原本存的是0xdeadbeef,所以我们找到这个值的地址和我们输入的地址作分析。
可以从上图看出中间差了52字节,可以构造52字节的垃圾数据加上0xcafebabe作为输入即可
exp:

拿到shell
bof3


Question4:flag

Papa brought me a packed present! let’s open it.

Download : http://pwnable.kr/bin/flag

This is reversing task. all you need is binary

Writeup:

逆向题。
记事本打开

upx-1

看到upx壳。

扔进终端
upx -d flag

脱掉过后扔进ida

upx0

看到程序会malloc一段内存,然后将flag拷贝进去。
打开edb,执行到malloc,监视这段地址,直到后面strcpy执行完毕后可以看到flag

upx


Question5:passcode

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh passcode@pwnable.kr -p2222 (pw:guest)

Writeup:

连接过后拿到一份源码

passcodepwn1

login()里面对两个passcode输入是有问题的。但是一时又不知道如何利用。

仔细分析第一次在welcome()里面的输入,输入的字符串是函数内的局部变量,当函数调用结束,这个变量就会失去价值,变为栈里的残留数据。那我们能不能通过控制这个残留的数据来影响到login()当中的passcode呢?

接下来用edb加载程序查看一下堆栈情况。
我们首先运行到输入passcode1的位置
pass01

passcode1随便输入一个值,然后scanf会将未初始化的passcode1的值作为一个地址去寻址,这里程序多半会挂,我们看这时的栈空间。
pass00

这时程序已经挂了,原因是不能访问0xf75becab这个地址。
在图中我们可以看到0xff9acb88是之前welcome()里输入数据的残留。而0xff9acbe8这里就是passcode1的地址,它们中间的偏移值是0x60 = 96。算上一个32位的地址占用4个字节。刚好能满足96 + 4 = 100,即name[]的占用空间。

能操控passcode1里的数据后我们就能往任意地址写入4字节的数据。
方便起见,这里选择往fflush()的GOT表里面写入登陆成功那段代码的地址。这样在输入passcode1后,紧跟着就能执行登录成功代码拿到flag。

所以进IDA查了一下GOT表信息,找到了fflush()的GOT表地址
pass03

然后是查看loginOK那段代码的地址
pass02

这里往GOT表写0x080485d7或者0x080485e3都是可以的,因为我本地调试的时候想要个回显,所以我写的是0x080485d7(0x080485d7=134514135)

根据以上写出payload


pass04


Question6:random

Daddy, teach me how to use random value in programming!

ssh random@pwnable.kr -p2222 (pw:guest)

Writeup

大水题,之后大概会把pwnable的文章整合一下,因为最近会刷一波。
下载代码
ran00

看到这里的随机数生成器没有添加种子,所以每次生成的数是一样的。

扔进edb,跑到rand(),看一下eax里存放的返回值
ran01

根据异或的原理解一下

exp:

ran03


Question7:leg

Daddy told me I should study arm.
But I prefer to study my leg!

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh leg@pwnable.kr -p2222 (pw:guest)


Writeup:

题目给了一份源码一份反汇编代码

C

asm

第一次看arm汇编,去查了下资料。
首先确定pc寄存器和lr寄存器是什么
pc寄存器(r15)是程序计数器,它很像X86里的ip寄存器,保存下一条指令的地址,严格来讲,r15(pc)总是指向“正在取指”的指令。这里下面会做说明。
lr寄存器(r14)是连接寄存器,它保存函数调用过程中的返回地址。也就是说函数返回的时候程序就是跳转到lr存的地址。
另外,arm用r0来保存函数返回的值。

由此我们来分析以上代码,key1返回pc的值,key2返回pc + 4,key3返回lr的值。

因为对pc的理解不完全正确,我当时在这里卡了很久,后来翻了ET的博客终于发现pc存的其实不是接下来要执行的地址。

PC 代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令);2.译码(识别将要被执行的指令);3.执行(处理 指令并将结果写回寄存器)。而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定 将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址 加8字节的地址,即:PC值=当前程序执行位置+8;
ARM指令是三级流水线,取指,译指,执行时同时执行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态 下,一个指令占4个字节),cpu正在执行的指令地址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。

这里再注意一下key2中的.code这样的伪指令。
.code 16表示下面的代码被声明为thumb指令集编译
.code 32表示下面的代码被声明为arm指令集编译
thumb指令集是16位而arm指令集是32位,所以在这里pc的值也要相应的修改。

综上我们可以计算出
key1(pc) = 0x00008cdc+0x8=0x00008ce4

key2(pc + 4) = 0x00008d04+0x4+0x4=0x00008d0c

key3(lr) = 0x00008d80

相加得到108400


Question8:mistake

We all make mistakes, let’s move on.
(don’t take this too seriously, no fancy hacking skill is required at all)

This task is based on real event
Thanks to dhmonkey

hint : operator priority

ssh mistake@pwnable.kr -p2222 (pw:guest)


Writeup:

水题,先看源码

hint已经说得很清楚是一个运算符优先级的问题,从头看下来在17行的位置就能发现问题,因为 < 的执行顺序是优先于 = 的,所以这里fd的值应该恒为0,后面的read变成从stdin里面读取数据。

所以第一次输入就是pw_buf, 第二次输入就是pw_buf2,中间那个XOR逆一下,假如以“0123456789”作为第一次输入,那个分别把’0’~’9’与1异或得到的就是第二次输入。
稍微注意一下这里的函数以字符串接受输入,但是照上面那个输入的话这里其实无所谓数字型或者字符串型,因为结果是一样的,pw_buf2就是“1032547698”。


Question9:shellshock

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure 🙂

ssh shellshock@pwnable.kr -p2222 (pw:guest)

Writeup:

Prerequisite:

Wiki-Shellshock

简单的说一下就是,比如你在shell里输入fun(){ echo HelloWorld; };会定义函数fun(写在一行里面的时候记得大括号旁留空格),如果是fun = ‘(){ echo HelloWorld; };’的话,正常的shell会把它当做一个变量,但是存在的漏洞的bash在fun被载入环境变量的时候会把它解释为一个函数。这样我们可以在函数体后面加上我们要执行的代码,bash在载入环境变量的时候会执行你追加的代码。相关的实例在plusls的博客里有介绍。
更深入的原理可以在这篇文章里看到: https://coolshell.cn/articles/11973.html

Solution

题目已经告诉你是shellshock漏洞那就可以直接打了。
env指令可以直接定义环境变量,我们定义一个空的函数fun = ‘(){ :; };’(:在shell里表示空语句),然后再这个函数后面追加要执行的代码就能拿到shell.

最后构造的payload: env fun='() { :;}; /bin/cat flag’ ./shellshock
这里用export写环境变量也可以。


Question10: unlink

Daddy! how can I exploit unlink corruption?

ssh unlink@pwnable.kr -p2222 (pw: guest)

Writeup:

Prerequisition

堆溢出中的unlink
做这个题了解一下文章中第一部分就可以

Solution

连上去拿到一份代码如下

第一个涉及到堆溢出的题目,这个题目简单还原了一个古老unlink场景(现代的unlink由于加入了新的安全检查机制,利用难度更高)。
很明显我们可以通过堆溢出来触发unlink获得一个有副作用的任意地址写。
最开始想到这里的时候的想法是直接把main函数的返回地址改到shell,但是动手写脚本之前反应过来,这样写的话会产生一个副作用,它将会修改返回地址的同时,unlink也会修改到shell函数的内容,这样就会引发异常,拿不到shell。而程序开了NX保护,这样写shellcode也没有办法执行。
然后我在这里卡了很久,最后在网络上找到了其它选手的解法,非常的妙。

这里利用的main函数最后的一段代码。

可以看到在retn之前有针对esp的几个操作,而retn其实就是pop eip,只要我们能控制最后esp里的东西,我们就能控制程序的执行流程。esp最后是取决于

中间那个leave命令对最终esp没什么影响,这样分析的话esp其实就取决于ebp-4这个地方的值,也就是说,我们只要把shell函数的地址写在某个地方,称之为shellAddr,然后把shellAddr+4写到ebp-4这个位置去,这样就可以让main在retn过后跳到shell函数去。

由于给出了栈地址和堆地址,假设我把shellAddr写在A->buf的开头,那shellAddr+4就等于A+12,而ebp这个看一下栈空间的结构也不难推出来,最后醉成payload的结构就是 shellAddr + padding + (shellAddr+4) + (ebp-4)

最后的脚本如下