-
-
[原创]新人PWN入坑总结(一)
-
发表于: 2021-8-2 16:20 17714
-
最近越学越傻,尤其是复杂网络论文看得我实在是难受,就来总结一下自己入坑PWN之后学到的各种东西吧。大部分从我自己原来的博客COPY过来的。
1.很简单的一个程序,IDA打开,32位程序,main函数-hello函数中
buf距离栈底有0x12h,而可以读入的数据有0x64h,所以可以栈溢出。
2.checksec一下,开了NX,不能shellcode,这里也不需要,因为我们的输入并不会被当成指令来执行。
3.程序中自带后门getShell函数,并且有栈溢出,那么直接覆盖hello函数的返回地址跳转即可。
4.编写payload:
payload = "a"*(0x12+0x04) #padding
(其中0x12是覆盖掉距离栈底的内容,0x04是覆盖掉hello函数返回的ebp,之后才是覆盖hello函数的返回地址)
payload += p32(0x0804846B) ##覆盖返回地址
5.之后输入,然后Interactive()即可。
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1.常规checksec下,只开了NX,之后IDA打开文件之后,有如下语句:
这里v7是输入的Buf,v5是mmap分配的内存空间。之后的语句:代表了将v5对应的内存空间强制转化为函数指针并且调用,在汇编代码中也可以看出来:这里的[ebp+var_18]就是我们输入的buf经过编码base64编码后存放的地方。
3.所以我们输入的内容就成了会被执行的汇编代码,也就是可以输入Shellcode,来执行我们需要的命令。这里可以看一个连接网址,从里面找shellcode:
http://shell-storm.org/shellcode/
可以通过linux/x86/sh/bash/execve/shellcode等等关键词来查找,这里直接给出一个可用的shellcode:
4.但是有Base64_encode,所以我们输入的需要会被base64编码,而base64编码只能由只由0-9,a-z,A-Z,+,/这些字符组成,(这里就是对应的ascii转换表中内容)所以常规的shellcode就不合格,我们这里选中的shellcode中某些字符就没办法被base64编码,所以这里需要用到msfvenom来选择一个可用的编码器,将我们常规的shellcode编码成可以被base64编码的shellcode。
5.打开Linux,输入msfvenom -l encoders可以查看编码器,后面有介绍,可以看一下,从中选择一个可用的编码器对shellcode进行编码即可。
6.查到x86/alpha_mixed这个编码器可以将我们输入的shellcode编码成大小写混合的代码,符合条件。
x86/alpha_mixed low Alpha2 Alphanumeric Mixedcase Encoder
运行编码器的代码如下:
7.输入这段代码运行之后可以看到当前文件夹目录下生成了一个payload文件,文本打开就可以看到编码后的shellcode:
PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA
8.之后需要将这段可以被Base64编码的进行Base64解码,得到的shellcode再被程序中的Base64编码后才是我们真正起作用的shellcode。利用python脚本即可。
▲
1.’import sys; sys.stdout.write(“shellcode”)’:这是导入包之后写入编码的shellcode。
2.由于msfvenom只能从stdin中读取,所以使用Linux管道符”|”来使得shellcode作为python程序的输出。
3.此外配置编码器为x86/alpha_mixed,配置目标平台架构等信息,输出到文件名为payload的文件中。
4.由于在b-64-b-tuff中是通过指令call eax调用shellcode的eax,所以配置BufferRegister=EAX。最后即可在payload中看到对应的被编码后的代码。这段shellcode代码就可以被base64编码成我们需要的汇编代码。
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1.常规check,发现这破程序啥保护也没开,而且还存在RWX段:
这不瞎搞嘛。之后IDA找漏洞,发现栈溢出:
2.这里就可以思考下攻击思路,存在栈溢出,还有RWX段,考虑用shellcode。虽然这个RWX段是随机生成的栈,地址没办法确定。再看看程序,发现程序自己给我们泄露了buf的栈地址:
也就是说紧跟再location后面的打印出来的就是buf的真实栈地址,这样我们就可以接受该栈地址,然后用栈溢出使得我们的main函数返回时可以跳转到对应的buf地址上,而buf地址上的内容就是我们的输入,也就是输入的shellcode,这样就可以执行我们的shellcode了。
3.但是写完shellcode会发现程序崩溃,这里进入IDA调试一下shellcode。可以发现程序运行过程中,Main函数return执行之后,跳转到shellcode的地方,然后运行shellcode。但是这一过程中,栈顶指向了main函数return的地址。所以在运行shellcode过程中,由于shellcode中有一个push rbx命令,导致rsp向上移动8个字节会覆盖掉shellcode的首地址。本来这没啥事,毕竟已经进入到shellcode当中去了,但是后面还有push rax和push rdi这两个改变rsp的命令,这就导致了rsp再次向低地址覆盖了16个字节,总共覆盖了24个字节。但是我们输入的shellcode有48个字节,顺序为shellcode+nop*10+addr_shellcode,也就是扣掉最后18个字节,还多出来6个字节覆盖掉了我们的执行代码shellcode的最后6个字节代码,导致我们的shellcode没办法完全执行,最终导致程序出错。
4.由于read函数允许我们输入0x40,也就是64个字节,也就是在覆盖掉返回地址之后,我们还能再输入64-48=16个字节。由于push rdi之后的片段为8个字节(包括了push rdi),小于16个字节,能够容纳下我们被覆盖掉的shellcode的代码,所以这里我们可以考虑用拼接的方式来把shellcode完美执行。
9.现在考虑如何把两段shellcode汇编代码连在一起。有call,return和jmp,但是前面两条指令中,call会push进函数地址,而return也会修改栈和寄存器的状态,ret指令的本质是pop eip,即把当前栈顶的内容作为内存地址进行跳转。所以只能选择jmp跳转。
5.可以查阅Intel开发者手册或其他资料找到jmp对应的字节码,或者这个程序中带了一条Jmp可以加以利用。为EB,jmp x总共2个字节:EB x.
6.将两段隔开,从push rdi开始,将push rdi和之后的代码都挪到下一个地方。这时第一段shellcode应该是22+2(jmp x)=24个字节,距离下段shellcode的距离应该是48-24=24,也就对应0x18h,所以总的shellcode应该是shellcode1+EB 18h+shellcode2,这样可以顺利执行需要的shellcode。
▲jmp的跳转计算距离是从jmp指令下一条开始计算的。
▲shellcode的两段执行:
1.需要泄露地址,读取泄露地址:
A.print io.recvuntil("Location:")#读取到即将泄露地址的地方。
B.shellcode_address_at_stack = int(io.recv()[0:14], 16)#将泄露出来的地址转换为数字流
C.log.info("Leak stack address = %x", shellcode_address_at_stack)#将泄露地址尝试输出,观察是否泄露成功。
2.需要跳转jmp命令或者是return/call,但是return会pop eip,call会push eip,都会修改掉栈中的内容。如果shellcode的两段执行计算偏移地址的话,可能需要将这两个内容也计算进入。但是jmp就不会需要,是直接无条件跳转,所以大多时候选择jmp比较好。
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1.常规checksec,开了Canary和NX,IDA查找漏洞,找到下列奇怪代码:
可以猜出来是输入到v4中,然后v4被强制转换成函数指针被调用。查看汇编代码也可以看到:
这里就没办法判断到底[esp+34h]是不是v4了,因为v4是通过mmap申请的一块内存,虽然在栈上,但是并不知道在哪,需要通过调试才能知道,调试之后发现确实是这样。
2.虽然最开始checksec程序,发现开了NX,那么这不就代表没办法shellcode了吗。调试也发现,除了代码段,其它段都没有X属性,都不可执行。但是我们看汇编代码,是call eax,调用的是寄存器,不是程序段,一定可以被调用的,然后eax中保存的内容就是我们输入的内容啊,所以直接输入shellcode就完事,连栈溢出什么的都不用考虑。
3.那么直接从http://shell-storm.org/shellcode/
查找获取就可以。给出一段可用shellcode:
\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80
由于这段shellcode调用的是int80来获取shell的,所以给出下列介绍
▲int 80h:128号中断
1.在32位Linux中该中断被用于呼叫系统调用程序system_call()。
2.read(), write(), system()之类的需要内核“帮忙”的函数,就是围绕这条指令加上一些额外参数处理,异常处理等代码封装而成的。32位linux系统的内核一共提供了0~337号共计338种系统调用用以实现不同的功能。
3.输入的shellcode也就汇编成了EAX = 0Xb = 11,EBX = &(“/bin//sh”), ECX = EDX = 0,等同于执行代码sys_execve("/bin//sh", 0, 0, 0),通过/bin/sh软链接打开一个shell。这里sys_execve调用的参数就是ebx的对应的地址。所以我们可以在没有system函数的情况下打开shell。64位linux系统的汇编指令就是syscall,调用sys_execve需要将EAX设置为0x3B,放置参数的寄存器也和32位不同
参考资料:
https://bbs.ichunqiu.com/forum.php?mod=collection&action=view&ctid=157
1.常规checksec,可以发现NX enabled,并且没有RAX字段。打开IDA后可以看到在hello函数中存在栈溢出:
然后分析程序,汇编代码什么的,没找到有call eax之类的操作,这里就选择ROP来getshell。
2.由于是64位程序,传参方式不同,依次为:rdi, rsi, rdx, rcx, r8, r9, 栈,而我们的目标是跳转system函数,让system函数读取binsh字符串,system函数又只有一个参数,所以这个参数必然需要在rdi中读取。我们的输入是位于栈上,所以需要一个pop rdi;和ret的操作命令,让我们的输入赋值给rdi寄存器。
3.在哪找pop rdi; ret;也是个问题,这里有个工具可以实现ROPgadget ,在linux下可以输入:以下代码来获取代码地址。
4.然后需要system函数的地址,这里today函数直接call了该函数,所以可以直接用IDA在汇编中看到该地址(行的前缀)。或者先ctrl + s,在got.plt中搜索一下,发现也能找到system函数。所以这里获取system地址我们可以有两种方法:
①pop rdi之后,让ret指向today函数中的call_system_地址:0x40075F
②pop rdi之后,让ret指向从elf = ELF('./pwn150')和system_addr = p64(elf.symbols['system'])中找到的地址system_addr,也就是plt表中的地址(这里其实可以直接在IDA中找到)
(但是需要注意的是,这是64位程序,system函数从rdi上取值,与栈无关系,所以call和直接跳转plt差不多,但是如果是32位程序,那么布置栈的时候就需要考虑到plt表和直接call system函数的不同了。如果是直接跳转plt表中的地址,那么栈的布置顺序应该是:
system函数-system函数的返回地址-sytem函数的参数。
但如果是跳转call system,那么由于call指令会自动push进eip,则栈布置应该为:
call system函数地址-system函数参数。
两者不太一样,需要加以区分。后面会有got表和plt的详细讲解)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
- [原创]CVE-2021-22555_Netfilter堆溢出提权漏洞 22416
- [原创]Kernel从0开始(四) 28156
- [原创]Kernel从0开始(三) 29251
- [原创]Kernel从0开始(二) 32429
- [原创]Kernel从0开始(一) 44925